【Android】IPC、AIDL、Binder
IPC:Inter Process Communication,跨进程间通信。
AIDL:Android Interface Definition Language,Android接口定义语言,用于生成可以在Android设备上两个进程之间进行进程间通信的代码。
Binder:是Android中一种跨进程通信的方式。从Android FrameWork角度来说,Binder是ServiceManager连接各种Manager和相应ManagerService的桥梁。从Android应用层的角度来说, BInder是客户端和服务端进行通信的媒介。
三者的关系:
AIDL是基于Binder机制实现Android上的IPC。
一、AIDL实践
先从例子出发,从例子理解aidl。AIDL涉及一个服务端和一个客户端。
1、服务端
(1)新建一个工程,AIDLServer。
(2)右键新建一个AIDL File,删掉里面的方法,写上自己的方法声明。
// IMyAidlInterface.aidl
package com.cm.aidlserver;
interface IMyAidlInterface {
void callF1();
int callF2();
}
(3)选择Build -> Rebuild Project,会自动根据接口生成Java文件。位置在app/build/generated/source/aidl下面。可以先大概看一下生成的内容,本质还是一个继承了接口的接口。里面的内容等下解释。
(4)上面生成的只是个接口,是必须要实现的。
新建一个服务service。
package com.cm.aidlserver;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
public class MyService extends Service {
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
return myBinder;
}
private IBinder myBinder = new IMyAidlInterface.Stub() {
@Override
public void callF1() throws RemoteException {
Log.i("chenming server", "callerF1");
}
@Override
public int callF2() throws RemoteException {
Log.i("chenming server", "callerF2");
return 0;
}
};
}
在onBind方法中返回对应的binder对象,binder对象为IMyAidlInterface.Stub的实例对象,实现了其中的抽象方法,即我们定义的aidl中声明的方法。
为了可以在客户端调用,需要在xml中将外界调用设置为true。
<service
android:name=".MyService"
android:enabled="true"
android:exported="true">
</service>
2、客户端
(1)新建一个工程,AIDLClient。
(2)将服务器的AIDL文件复制到该工程中,选择Build -> Rebuild Project,会自动根据接口生成和服务端一样的Java接口文件。
(3)为了可以调用服务端的方法,需要通过binder进行调用,因此在客户端中要定义该binder。
IMyAidlInterface iMyAidlInterface;
(4)然后绑定服务端的提供的服务。
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.cm.aidlserver", "com.cm.aidlserver.MyService"));
bindService(intent, conn, BIND_AUTO_CREATE);
(5)绑定成功之后会回调对应的方法,在方法中初始化binder。
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
iMyAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
iMyAidlInterface = null;
}
};
通过IMyAidlInterface.Stub.asInterface(iBinder)初始化binder。
(6)调用对应的方法,以调用callF1方法为例,调用如下。
private void callF1() {
try {
iMyAidlInterface.callF1();
} catch (RemoteException e) {
e.printStackTrace();
}
}
从log可以看出,可以通过客户端调用服务端的方法。
注意:原生系统中,可以通过这个方法进行保活,即当Client端启动的时候,会通过binder service拉起Server端,但是由于国产手机的系统都经过了优化,会拦截关联启动,解决方法有两个,一个是先启动server再用client去调用,另一个是打开手机里面的关联启动允许即可(以华为手机为例,打开手机管家->启动管理,找到AIDLServer改成手动管理,允许关联启动)。
二、AIDL原理分析
客户端怎么跨进程调用server端的方法的呢?
这个需要从AIDL自动生成的Java类分析。
查看生成的Java类IMyAidlInterface.java,接下来一行行解读。
首先,该类是一个继承自接口的接口,从上面实现可以知道具体的是实现是在service中。
然后,定义了一个抽象类Stub,这个类本质就是一个binder,这个类里面有以下的成员与方法:
(1)DESCRIPTOR成员,是Binder的唯一标识
(2)asInterface方法
传入参数为一个Binder,返回一个IMyAidlInterface,是用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象。
里面先调用queryLocalInterface方法,这个方法可以从源码里面看见当客户端和服务端为同一个进程的时候,返回服务端的AIDL接口类型的对象,不同进程的时候返回null。所以根据iin进行判断,如果返回不为null,说明在同一个进程内调用,直接返回AIDL接口类型的对象,为null的时候说明不在同一个进程内调用,返回封装后的对象Stub.Proxy。
(3)asBinder方法,返回当前的binder对象。
(4)onTransact方法。当客户端发起跨进程请求的时候,远程请求会通过系统底层封装后交到该方法处理。原型如下:
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
code为请求参数,根据这个参数来确定调用哪个方法,data用以传递方法参数,reply用以传递结果即返回值。该方法的返回值标记是否调用成功。
(5)对于每个方法定义一个code。
static final int TRANSACTION_callF1 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_callF2 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
(6)从上面知道,如果是同一个进程调用的是Stub的方法,如果不同进程,调用的是Stub.Proxy里面的方法,以其中一个方法Stub.Proxy的callF1方法为例,分析如下。
首先创建输入参数_data和输出参数_reply,然后将该方法的参数写入_data,然后调用transact方法发送RPC(远程过程调用),同时线程挂起,然后服务端的onTransact会被调用,等到结果返回之后继续执行,将结果写入_reply中。
可以看见,AIDL是基于Binder机制实现的,通过AIDL自动生成的接口里面的Binder的子类实现远程接口调用,整个流程如下图所示。
注意:由于客户端在通过binder调用服务端的时候,会把线程挂起,因此,一般需要另开线程执行,如果在主线程执行,如果这个方法是耗时方法,会导致ANR情况。
调用流程图.png三、深入理解Binder
上面从主要从应用层的角度分析AIDL,可以看出AIDL底层是基于Binder实现的,那么Binder底层原理是怎么样的呢?
首先,先理解进程内存空间的关系,如下图所示。
进程间地址空间关系.png可以看见虽然不同进程的用户空间是非共享的,但是内核空间是共享的,Client进程向Server进程通信,恰恰是利用进程间可共享的内核内存空间来完成底层通信工作的。
Binder通信采用C/S架构,基本架构图如下图所示。
Binder架构图.jpgService Manager用于管理系统中的各种服务。图中Client/Server/ServiceManage之间的相互通信都是基于Binder机制,所以三个步骤都是C/S架构。
首先,注册服务,这个过程Server是客户端,ServiceManager是服务端。
然后,Client端进行服务获取,这个过程Client是客户端,ServiceManager是服务端。
最后,使用服务,Client根据得到的Service信息建立与Service所在的Server进程通信的通路,然后就可以直接与Service交互。该过程:client是客户端,server是服务端。
四、例子回顾
首先,启动AIDLServer应用后,MyService启动,向Service Manager注册。然后启动AIDLClient通过绑定服务获取对应的服务,服务连接成功之后,调用IMyAidlInterface.Stub.asInterface获取客户端所需的AIDL接口类型的对象,由于是不同的进程,因此会获取到对应的代理类。调用方法callF1方法的时候,会去调用对应service里面真正实现的callF1()。