AIDL概述
AIDL是安卓为进程间通讯留下的接口,和其他RPC框架一样,AIDL也包括代理层,协议层和通讯层。代理层是安卓的跨进程通讯机制Binder,协议层是安卓接口定义语言AIDL,代理层除了AIDL自动生成的存根代码之外,还需要手动实现服务的绑定以及服务并将存根转换为服务接口。这就构成了整个AIDL的框架。
具体实现AIDL的过程分为以下几步:
- 创建服务端项目,编写AIDL
- 注册服务,实现服务方法并回传服务存根
- 创建客户端项目,拷贝AIDL
- 设计客户端UI
- 在客户端实现业务逻辑
- 在客户端注册queries
- 在客户端实现服务绑定并用AIDL存根作为通讯接口
- 利用存根中的方法实现需求功能
在本例中,我们将实现一个进行四则运算计算器,以及一个计算器的服务器。计算器通过绑定到服务器上的服务来完成计算功能
阅读须知
本文可以仅需要RPC基础和c语言基础,对于java中类的继承、抽象类、方法等概念只需初步了解,无需Android开发经验
示例开发
安装android虚拟机的方法
- 首先找到设备管理器
- 然后点击创建设备
- 没有特殊需求就选默认硬件,直接点next
- 操作系统映像选默认的(和硬件匹配的)点Download,许可协议选accept然后再点next就开始下载了。我这里是已经装好了
开启USB调试的方法
这个各个设备不太一样,一般要先在设备信息界面点击某个信息十次解锁,比如我是点击设置-设备信息-软件信息-编译编号解锁开发者模式
解锁了之后在设置-开发者选项里找到USB调试打开就可以了。
1.创建服务端项目,编写AIDL代码
创建新项目
从左上角File-New-New Project新建项目,注意选择"Empty Activity"
接下来可以点next,但在最后的界面记得把语言改成java,项目名字我这里后来改成了AidlServer
创建新的包
在项目列表里选择java目录,右键,点击new,点击Package,在目录结构里选main
给它起个名字,注意过会需要创建这个包的一个副本,所以名字别起太怪,不方便检查。我这里是叫AIDLPackage
创建新的AIDL文件
在刚才的创建的包上右键创建AIDL文件,名字也别起太怪,我这里叫IAidlDemo
应该能在新建的Aidl文件中看到如下的代码:
// IAidlDemo.aidl
package AidlPackage;
// Declare any non-default types here with import statements
interface IAidlDemo {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
编写接口定义
我们要把计算的过程委托给远程服务器,也就是传递两个运算数和一个运算符,接受一个结果。方便起见,规定运算是整数运算,并且用1
2
3
4
表示+
-
*
/
,这样我们就形成了如下接口:
// IAidlDemo.aidl
package AidlPackage;
// Declare any non-default types here with import statements
interface IAidlDemo {
int calculate(int v1, int v2, int op);
}
然后需要点击Build-Rebuild Project或者点击右上角配置栏左侧的绿色小锤子进行Make Project。注意,每次修改AIDL文件都要进行rebuild
到此为止,我们的服务应用程序框架就创建完成。
2.注册服务,实现方法
在APP里右键创建新的Service,注意不要选Service(Intent Service)
,在接下来的窗口里enabled
和exported
都要选中
新建存根
打开新建的服务的java代码app/java/AidlPackage/<ServiceName>.java
,为<ServiceName>
类添加一个存根变量
private final IAidlDemo.Stub
...
注意,这里按下.
之后,应该提示后面的Stub类型,如果没有提示就说明刚才没有进行Rebuild!点击下图所示绿色小锤子进行Rebuild
确认自己rebuild完成之后继续写。
private final IAidlDemo.Stub = new IAidlDemo.
现在应该能自动补全Stub了,按Tab键之后,就应该出现类似下面的内容:
private final IAidlDemo.Stub stub = new IAidlDemo.Stub() {
@Override
public int calculate(int v1, int v2, int op) throws RemoteException {
return 0;
}
};
//不会自动打分号
注意到calculate正是我们之前定义的接口定义语言,因为AIDL生成的存根类型是个抽象类,因此系统会自动提醒我们复写其中的calculate
方法。
实现方法
接下来,我们就来修改这个存根的内部的方法,这里就简单的复制一份代码吧!
private final IAidlDemo.Stub stub = new IAidlDemo.Stub() {
@Override
public int calculate(int v1, int v2, int op) throws RemoteException {
switch (op) {
case 1:
return v1 + v2;
case 2:
return v1 - v2;
case 3:
return v1 * v2;
case 4:
return v1 / v2;
default:
Log.e("Error: ","Invalid Operator");
return 0;
}
}
};
//记得打分号
由于这是一个远程方法,因此默认可能会抛出一个远程异常,不过我们不必管他,一般不会出现这种情况。
传递存根
在<ServiceName>.java
中应该还有一个默认的方法onBind
:
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
这是因为我们的服务继承了Service抽象类,因此需要实现onBind方法,也就是指定当服务绑定时要进行的操作。我们只需要返回刚才创建的存根即可。
@Override
public IBinder onBind(Intent intent) {
return stub;
}
到此为止我们的服务端项目就编写完成,可以跑一下试试看:
虽然没有UI界面,但是服务已经就绪了~
3.创建客户端项目,拷贝AIDL
新建客户端项目可以单开一个project也可以就在这个project里操作,这里选择单开一个project。过程就不再赘述,注意仍然用Empty Activity
作为界面。
这里仍然在app\java
上右键,选New-Package
新建包,注意包名要与刚刚创建的包名相同
在包上右键,新建Aidl文件,注意文件名要和刚刚创建的文件相同
复制刚刚的Aidl文件内容,粘贴到新的Aidl文件里
包名、文件名、文件内容完全相同。
4.设计客户端UI
打开activity_main.xml
默认在创建项目的时候就会有这个文件生成,如果不小心关掉了就从左侧项目目录app/res/layout/
目录下打开。
请检查一下这个目录,确保其中只有activity_main.xml
。在右上角选择以Code
方式查看该文件的内容:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MvkQBALs-1649828149801)(开发AIDL示例项目的完整流程/layout.bmp)]
文件内容应该如下
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
这个xml制定了应用程序主界面的UI,如果layout
文件夹里有多个文件或者这个.xml
文件的内容和上述内容不一样,那么很有可能是因为你没有选择EmptyActivity
类型的项目,你可自行判断是否需要重建项目。如果你不清楚这个.xml
文件的具体行为,可以考虑重建。
接下来我们还是在右上角将视图切换到View,或者你也可以直接复制本项目的.xml
文档,直接跳到实现业务逻辑的部分
修改布局
默认的布局是constraint
,这样图标的上下左右位置都需要指定,很麻烦,于是我们把所有的androidx.constraintlayout.widget.ConstraintLayout
修改为RelativeLayout
<?xml version="1.0" encoding="utf-8"?>
- <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ <RelativeLayout xmlns: xmlns:android="http://schemas.android.com/apk/res/android"
...
- </androidx.constraintlayout.widget.ConstraintLayout>
+ </RelativeLayout>
如果修改成功,Android应该没有任何Warning,并且你会发现Hello World从原来的居中变为出现在左上角。(要查看.xml的效果,你可以在右上角选择Split
或View
模式)
添加TextView
我们需要一个文本框来显示结果,把下面这段话粘贴到.xml中,并且删除Hello World(从<
开始删除到 >
为止)
<TextView
android:id="@+id/display_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_margin="20dp"
android:background="#FDF171"
android:hint="Result"
android:padding="15dp"
android:textSize="20sp"
android:textStyle="bold" />
现在你应该看到这样的文本框:
所以TextView就是一个文本框,我们设置了默认显示的字符串(hint)、背景颜色、字号、字体等内容信息,还设置了长宽、水平居中、边距等定型定位尺寸。我们可以在java代码中和TextView进行沟通,比如通过setText方法来设置要显示的内容,这样我们就可以把结果输出到这个文本框里。
还有值得注意的一点是,每个组建都拥有自己的id,比如@+id/display_result
就对应我们刚刚的文本框,因为我们的第一行就指明了他的id。这个id1可以在xml中描述组件之间的相对关系,也可以在java中用于绑定组件信息。
添加EditText
我们需要两个文本框来接受输入的运算数
<EditText
android:id="@+id/enter_first_value"
android:layout_width="200dp"
android:layout_height="50dp"
android:layout_below="@+id/display_result"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:ems="10"
android:hint="Enter First Value"
android:imeOptions="actionNext"
android:inputType="number"
android:maxLength="5"
android:singleLine="true" />
<EditText
android:id="@+id/enter_next_value"
android:layout_width="200dp"
android:layout_height="50dp"
android:layout_below="@+id/enter_first_value"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="20dp"
android:autofillHints=""
android:ems="10"
android:hint="Enter Next Value"
android:inputType="number"
android:maxLength="5"
android:singleLine="true" />
开头是一个id号,而所有含有layout的都是定型尺寸和定位尺寸,这里注意layout_below
指明它要放在上面那个文本框的下方。这里还限制了输入类型是数字,最多输入5个且只接受单行输入。
添加按钮
首先添加四则运算,这四个按钮之间的位置关系通过layout_below
和layout_toEndOf
来指定,可以看到加法减法位于同一行,乘法除法位于同一行,减法除法在加法乘法右侧。
按钮默认全字大写,所以这里关闭了textAllCaps
<Button
android:id="@+id/add"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_below="@+id/enter_next_value"
android:layout_marginLeft="20dp"
android:background="#0288D1"
android:text="Add"
android:textAllCaps="false"
android:textColor="#ffff" />
<Button
android:id="@+id/subtract"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_below="@+id/enter_next_value"
android:layout_marginStart="20dp"
android:layout_toEndOf="@+id/add"
android:background="#0288D1"
android:text="Subtract"
android:textAllCaps="false"
android:textColor="#ffff" />
<Button
android:id="@+id/multiply"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_below="@+id/add"
android:layout_marginLeft="20dp"
android:layout_marginTop="10dp"
android:background="#0288D1"
android:text="Multiply"
android:textAllCaps="false"
android:textColor="#ffff" />
<Button
android:id="@+id/divide"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_below="@+id/subtract"
android:layout_marginStart="20dp"
android:layout_marginTop="10dp"
android:layout_toEndOf="@+id/multiply"
android:background="#0288D1"
android:text="Divide"
android:textAllCaps="false"
android:textColor="#ffff" />
这两个按钮是用于清零和绑定服务的,让他们居中。
<Button
android:id="@+id/clear_data"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/division"
android:layout_centerHorizontal="true"
android:layout_marginTop="10dp"
android:text="Clear Data" />
<Button
android:id="@+id/bind_service"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/clear_data"
android:layout_centerHorizontal="true"
android:layout_marginTop="10dp"
android:text="Bind Service" />
整体效果应该如图:
注意,各个按钮之间的相对关系是通过id指定的,如果更改了id多半会有error,修改id修复error即可
另外会有一些warning,比如叫你把字符串做编码之类的,建议不懂的话忽略就好
这就是我们的ui界面了!AndroidUI的设计还是很简洁的,但是背后是项目自动为我们生成的许多文件~
更多内容请期待下一篇~