作者:UsherChen
声明:本文已获UsherChen
投稿发表,转发等请联系原作者授权
Android Study - 关于AIDL的使用学习
关于Android中多进程的了解
使用AIDL进行多进程通信
自定义代码模拟AIDL的工作流程
其他说明
关于Android中多进程的了解
进程:一般指一个执行单元,在平台上指一个程序或一个应用;
线程:CPU调度的最小单位,也是一种资源,一个应用可包含多个线程;
Android多进程通信通常指两种现象:
一个应用的组件
、、、可在清单文件 AndroidManifest.xml 中指定 android:process 属性名称,一个应用内多个线程通信的现象多个应用对应多个进程间通信的现象
以上两种现象本质上没有区别,本质上一个进程对应了一块内存区域,彼此之间需要通过一些特殊的方式来进行通信,不可直接通信。
使用AIDL进行多进程通信
上文说道,多进程彼此之间不可直接通信,需借助一些特殊的方式,AIDL是Android系统提供的一种多进程通信的方式。
AIDL:Android Interface Deifinition Laguage 安卓接口定义语言,本质就是一个接口文件,但是是用 .aild 后缀结尾的;
使用AIDL进行进程间通信牵扯到几个类:Service、Binder、IBinder、Intent、Parce 这里先不管这几个类是什么东西,下面会说明;
然后还牵扯到两个概念:服务端与客户端;
从业务层面理解以上元素的作用就是,客户端(含有进程)调用服务端(含有进程)中的方法业务,然后客户端会给一些数据到服务端,服务端再返回一些数据给客户端,如果不需要数据的话就不给或者不返回即可。
看起来似乎有一些抽象, 以下图例模拟一下进程间通信的原理:
两个应用(进程)之间的通信是通过Binder作为桥梁来完成的,Binder是Android提供的一种用于进程通信的方式,这里把它简单理解为一个类即可。
这里模拟两个app来演示AIDL的工作原理,服务端的app提供了三个方法:求和、求最大值、求最小值三个方法;客户端的app需要调用服务端里的这三个方法,并且获取到这三个方法的返回值。
服务端app
我们需要创建一个组件来保持服务常驻后台,并提供业务方法。首先需要创建一个AIDL文件,用来定义需要提供的业务方法,这里在AS中右键单击app module,选择创建一个AIDL文命名为ICalculateAIDL.aidl
// ICalculateAIDL.aidl
package com.usherchen.demo.server;
interface ICalculateAIDL {
// 计算x与y的和
int add(int x, int y);
// 对比x与y的最大值
int max(int x, int y);
// 对比x与y的最小值
int min(int x, int y);
}
系统生成的那个方法直接删掉即可,由于AIDL文件本质是一个接口文件,那么在这里定义三个抽象方法用来在其他代码中实现具体业务。需要注意的是,AIDL文件并不会在AS中出现代码提示,所有的代码需要手动敲上去,代码量比较少。
然后点击Build - Make Project,会发现,在app的build - generated - aidl_source_output_dir - debug 这个路径(路径可能会更长)下有一个 ICalculateAIDL.java 文件。有意思的事情来了,原来点了Make Project以后AS自动帮我们实现了ICalculateAIDL.aidl中的接口,可以看到 ICalculateAIDL.java 这个文件里的代码超级复杂,我们先不管这个文件,继续去编写我们其他的代码。
为了使应用置于后台仍旧可以继续工作,这里我们写一个ICalculateService继承Service用于提供服务:
public class ICalculateService extends Service {
private static final String TAG = "usherchen_server";
/**
* 这是一个实现了AIDL接口方法的IBinder实例
*/
private ICalculateAIDL.Stub mIBinder = new ICalculateAIDL.Stub() {
/**
* 实现了x与y求和的逻辑
*/
@Override
public int add(int x, int y) throws RemoteException {
return x + y;
}
/**
* 实现了x与y求最大值的逻辑
*/
@Override
public int max(int x, int y) throws RemoteException {
if (x > y) {
return x;
}
return y;
}
/**
* 实现了x与y求最小值的逻辑
*/
@Override
public int min(int x, int y) throws RemoteException {
if (x return x;
}
return y;
}
};
@Override
public void onCreate() {
Log.i(TAG, "onCreate: ");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand: ");
return super.onStartCommand(intent, flags, startId);
}
/**
* @param intent intent
* @return 这里返回了上面实现了AIDL内部方法的实例
*/
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "onBind: ");
return mIBinder;
}
@Override
public void onRebind(Intent intent) {
Log.i(TAG, "onRebind: ");
super.onRebind(intent);
}
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "onUnbind: ");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
Log.i(TAG, "onDestroy: ");
super.onDestroy();
}
}
代码不是很长,首先这里写了一个ICalculateAIDL.Stub的实例并实现了内部的方法。看这三个方法就知道这里是实现AIDL文件中抽象方法的业务的地方。然后我们点击Stub类,可以发现,这个类的类名如下所示:
public static abstract class Stub extends android.os.Binder implements com.usherchen.demo.server.ICalculateAIDL
这里可以看到Stub这个类继承了Binder,而Binder点进去可以看到:
public class Binder implements IBinder
因此Stub也是IBinder的字类,所以可以在Service的onBind方法中直接返回,onBind这个方法返回了一个IBinder的实例,是在启动这个Service的组件中用到的。这里不再解释怎么使用Service。接下来去清单文件中注册ICalculateService,如下所示:
<serviceandroid:name=".service.ICalculateService"tools:ignore="ExportedService">
<intent-filter>
<action android:name="com.usherchen.demo.aidl.custom" />
<category android:name="android.intent.category.DEFAULT" />
intent-filter>
service>
这里一目了然,对CustomICalculateService进行,因为我们需要在另一个app(另一个进程)中启动这个服务,因此这里需要采用隐式启动的方式对其进行属性声明。以上编码完成后,直接在设备跑起来,然后将这个app置于后台即可,接下来进行客户端的代码编写。
客户端app
对于客户端而言,我们需要调用服务端里面的方法,如果凭空什么都没有的话是肯定无法调用另一个app里面的方法的。所以我们需要把服务端的AIDL文件拷贝出来一份,即ICalculateAIDL.aidl这个文件,包含其所在路径,一起拷贝到客户端app的 src - main 文件夹下,然后点击Build - Make Project ,等待完成后,同样在客户端app的build - generated - aidl_source_output_dir - debug(路径可能更长)路径下看到有一个 ICalculateAIDL.java 文件,你会发现这个文件的内容与服务端的 ICalculateAIDL.java 文件中的代码一模一样,不用管他,我们继续写其他的代码。
这里我们写一个页面出来,有两个输入框,三个按钮,页面代码与样式如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical">
<EditTextandroid:id="@+id/et_number_x"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_margin="10dp"android:background="@android:drawable/edit_text"android:gravity="center"android:inputType="number"tools:ignore="UnusedAttribute" />
<EditTextandroid:id="@+id/et_number_y"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_margin="10dp"android:background="@android:drawable/edit_text"android:gravity="center"android:inputType="number"tools:ignore="UnusedAttribute" />
<Buttonandroid:id="@+id/btn_add"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_margin="10dp"android:text="add" />
<Buttonandroid:id="@+id/btn_max"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_margin="10dp"android:text="max" />
<Buttonandroid:id="@+id/btn_min"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_margin="10dp"android:text="min" />
LinearLayout>
然后在Activity里面去编写逻辑:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "usherchen_client";
private EditText mEtNumberX;
private EditText mEtNumberY;
private Button mBtnAdd;
private Button mBtnMax;
private Button mBtnMin;
private ICalculateAIDL mICalculateAIDLBinder;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mICalculateAIDLBinder = ICalculateAIDL.Stub.asInterface(service);
Log.i(TAG, "onServiceConnected: mICalculateAIDLBinder " + mICalculateAIDLBinder.hashCode());
}
@Override
public void onServiceDisconnected(ComponentName name) {
mICalculateAIDLBinder = null;
Log.i(TAG, "onServiceDisconnected: mICalculateAIDLBinder null");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
mEtNumberX = findViewById(R.id.et_number_x);
mEtNumberY = findViewById(R.id.et_number_y);
mBtnAdd = findViewById(R.id.btn_add);
mBtnMax = findViewById(R.id.btn_max);
mBtnMin = findViewById(R.id.btn_min);
mBtnAdd.setOnClickListener(this);
mBtnMax.setOnClickListener(this);
mBtnMin.setOnClickListener(this);
intentService();
}
private void intentService() {
Intent i = new Intent();
i.setPackage("com.usherchen.demo.server");
i.setAction("com.usherchen.demo.aidl.custom");
bindService(
i,
mConnection,
BIND_AUTO_CREATE
);
}
@Override
public void onClick(View v) {
try {
int id = v.getId();
int x = Integer.parseInt(mEtNumberX.getText().toString());
int y = Integer.parseInt(mEtNumberY.getText().toString());
if (id == mBtnAdd.getId()) {
Log.i(TAG, "onClick: add" + mICalculateAIDLBinder.add(x, y));
} else if (id == mBtnMax.getId()) {
Log.i(TAG, "onClick: max" + mICalculateAIDLBinder.max(x, y));
} else if (id == mBtnMin.getId()) {
Log.i(TAG, "onClick: min" + mICalculateAIDLBinder.min(x, y));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
这里的代码也很简单,首先创建一个 ICalculateAIDL 类的实例,然后创建一个ServiceConnection实例去实现内部类的方法,这是使用 bindService 方法去绑定一个服务的前提。当 ServiceConnection 绑定成功的时候,会接收到一个 IBinder 实例,这个实例内部实现了 ICalculateAIDL 的抽象方法,也就是add、max、min这三个方法。
在输入框中输入数字1和2,点击三个按钮可以看到打印的日志结果是3、2、1,说明我们确实是成功的调用了另一个进程中的方法业务,并且获取到了返回值。那么这一过程是怎么实现的,这里需要看一下 ICalculateAIDL.java 这个文件。
总结分析
这个文件的内容很多,我们选择性的来看两个地方,第一个地方是 Stub 字类中的 onTransact 方法:
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
String descriptor = DESCRIPTOR;
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(descriptor);
return true;
}
case TRANSACTION_add: {
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
int _result = this.add(_arg0, _arg1);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
case TRANSACTION_max: {
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
int _result = this.max(_arg0, _arg1);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
case TRANSACTION_min: {
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
int _result = this.min(_arg0, _arg1);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
这个方法看名字翻译成中文叫做传输,那么它里面肯定作了一些传输性的工作,我们来解释一下这个方法。首先它有四个入参:
code - 用于区分使用哪个方法;
data - 客户端传过来的数据;
reply - 从服务端返回的数据;
flags - 表明是否有返回值,0 - 有,1 - 无;
同时还有一个 DESCRIPTOR 常量,对于这个常量,我个人对其理解是,用于服务端和客户端对接的一个标识,后面我们自定义代码去实现以上功能的时候会说到。
然后我们选择一个 case TRANSACTION_add 去看一下里面的代码:
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
int _result = this.add(_arg0, _arg1);
reply.writeNoException();
reply.writeInt(_result);
这里一行行来看,第一行:data.enforceInterface(descriptor); 这里的作用即将 DESCRIPTOR 传入,表示一个认证的动作。
然后下面是定义了两个 int 值 _arg0 _arg1传入了 add 方法里,并返回了一个 _result,然后 reply 执行了两行代码,reply.writeNoException(); 表示这里没有异常发生,然后将 _result 写入,返回出去。
所以,当客户端调用 mICalculateAIDLBinder.add(x, y) 这行代码的时候,本质上是服务端的 ICalculateAIDL.java 的 Stub 的 onTransact 方法执行,然后将返回值递给了客户端。
从以上分析可以看出,数据的传输是集中在了 Stub 类里面的 onTransact 方法里,那么我们是不是也可以自己来写一个Binder来实现进程间的通信呢?
自定义代码模拟AIDL的工作流程
AIDL文件实现服务端app与客户端app通信的根本是Binder,那么我们模拟系统帮我们生成的 ICalculateAIDL.java 文件,自己去写代码模拟一下这个过程。
服务端app
自定义一个新的服务命名为CustomICalculateService,编写代码如下:
public class CustomICalculateService extends Service {
private static final String TAG = "usherchen_server";
@Override
public void onCreate() {
super.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "onBind: ");
return mCustomBinder;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
@Override
public void onRebind(Intent intent) {
super.onRebind(intent);
}
private static final String DESCRIPTOR = "com.usherchen.demo.descriptor";
/**
* 自定义一个binder实例用于提供服务
*/
private Binder mCustomBinder = new Binder() {
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code) {
case 1:
data.enforceInterface(DESCRIPTOR);
int x_1 = data.readInt();
int y_1 = data.readInt();
int s_1 = x_1 + y_1;
reply.writeNoException();
reply.writeInt(s_1);
return true;
case 2:
data.enforceInterface(DESCRIPTOR);
int x_2 = data.readInt();
int y_2 = data.readInt();
int s_2;
if (x_2 > y_2) {
s_2 = x_2;
} else {
s_2 = y_2;
}
reply.writeNoException();
reply.writeInt(s_2);
return true;
case 3:
data.enforceInterface(DESCRIPTOR);
int x_3 = data.readInt();
int y_3 = data.readInt();
int s_3;
if (x_3 s_3 = x_3;
} else {
s_3 = y_3;
}
reply.writeNoException();
reply.writeInt(s_3);
return true;
default:
return super.onTransact(code, data, reply, flags);
}
}
};
}
这里我没有用AIDL去让系统帮我生成java文件,而是直接写了一个Binder示例实现了onTransact方法,然后我们自己定义好 DESCRIPTOR 常量,code定义1 - add、2 - max、3 - min,然后在清单文件中注册
<serviceandroid:name=".service.CustomICalculateService"tools:ignore="ExportedService">
<intent-filter>
<action android:name="com.usherchen.demo.aidl.custom" />
<category android:name="android.intent.category.DEFAULT" />
intent-filter>
service>
服务端的代码编写完毕。
客户端app
这里页面就沿用之前的页面,然后需要对Activity里面的代码进行重新编写,如下所示:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "usherchen_client";
private EditText mEtNumberX;
private EditText mEtNumberY;
private Button mBtnAdd;
private Button mBtnMax;
private Button mBtnMin;
private IBinder mIBinder;
private ServiceConnection mCustomBinderConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mIBinder = service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
mIBinder = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
mEtNumberX = findViewById(R.id.et_number_x);
mEtNumberY = findViewById(R.id.et_number_y);
mBtnAdd = findViewById(R.id.btn_add);
mBtnMax = findViewById(R.id.btn_max);
mBtnMin = findViewById(R.id.btn_min);
mBtnAdd.setOnClickListener(this);
mBtnMax.setOnClickListener(this);
mBtnMin.setOnClickListener(this);
initCustomService();
}
private void initCustomService() {
Intent i = new Intent();
i.setPackage("com.usherchen.demo.server");
i.setAction("com.usherchen.demo.aidl.custom");
bindService(
i,
mCustomBinderConnection,
BIND_AUTO_CREATE
);
}
@Override
public void onClick(View v) {
try {
int id = v.getId();
int x = Integer.parseInt(mEtNumberX.getText().toString());
int y = Integer.parseInt(mEtNumberY.getText().toString());
if (id == mBtnAdd.getId()) {
add(x, y);
} else if (id == mBtnMax.getId()) {
max(x, y);
} else if (id == mBtnMin.getId()) {
min(x, y);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void showToast(String s) {
Toast.makeText(this, s, Toast.LENGTH_SHORT).show();
}
private static final String DESCRIPTOR = "com.usherchen.demo.descriptor";
private void add(int x, int y) {
if (mIBinder == null) {
return;
}
try {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(DESCRIPTOR);
data.writeInt(x);
data.writeInt(y);
mIBinder.transact(1, data, reply, 0);
reply.readException();
int s = reply.readInt();
showToast(String.valueOf(s));
} catch (RemoteException e) {
e.printStackTrace();
}
}
private void max(int x, int y) {
if (mIBinder == null) {
return;
}
try {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(DESCRIPTOR);
data.writeInt(x);
data.writeInt(y);
mIBinder.transact(2, data, reply, 0);
reply.readException();
int s = reply.readInt();
showToast(String.valueOf(s));
} catch (RemoteException e) {
e.printStackTrace();
}
}
private void min(int x, int y) {
if (mIBinder == null) {
return;
}
try {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(DESCRIPTOR);
data.writeInt(x);
data.writeInt(y);
mIBinder.transact(3, data, reply, 0);
reply.readException();
int s = reply.readInt();
showToast(String.valueOf(s));
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
代码稍微有点多,但相比之前的AIDL案例,并没有改动太多。首先创建一个IBinder实例,然后在ServiceConnection里面去获取对象。这个时候,我们去调用add方法,我们来看一下这里面的代码:
private void add(int x, int y) {
if (mIBinder == null) {
return;
}
try {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(DESCRIPTOR);
data.writeInt(x);
data.writeInt(y);
mIBinder.transact(1, data, reply, 0);
reply.readException();
int s = reply.readInt();
showToast(String.valueOf(s));
} catch (RemoteException e) {
e.printStackTrace();
}
}
首先我们获取到两个序列化对象(关于序列化不懂自行百度),一个data用于将客户端的数据传输出去,一个reply用于获取服务端返回的数据,然后这一行代码 data.writeInterfaceToken(DESCRIPTOR); 对应了AIDL案例中的 data.enforceInterface(DESCRIPTOR); 前文已经说明,这是一种认证行为,不再做过多解释。然后按照应有的顺序执行,不可随意更改代码顺序,否则会造成异常。这里需要说明 mIBinder.transact(1, data, reply, 0); 这行代码执行后,实际上就是调用了服务端的 onTransact 方法,那么我们接下来就可以获取到服务端执行业务后的结果了。
其他说明
另外AIDL需要注意的有以下几点:
服务端与客户端均需要一份相同路径的XXX.aidl文件
AIDL的调用过程是同步的,但是它的内部会开启线程,这一点比较迷糊。(简单来说,客户端的ICalculateAIDL.java文件的Stub字类中还有一个类Proxy,这个类内部会调用transact方法,在这个时候,客户端就等待服务端的结果回来,那么这个时候实际上是处于主线程的,如果服务端处理的过程比较慢,那么客户端就有可能报ANR了)