一、IPC机制
IPC(Inter-Process Communication),即进程间通信。此技术并非Android独创,其他系统也存在IPC机制。比如,Linux系统有:socket,pipe,signal,trace等。Android虽然继承了Linux内核,但是几乎没有使用这些,取而代之的是大量使用Binder机制,主要原因是其通信效率和安全性更高。
序列化,当我们通过Intent或Binder传输数据的时候需要对数据进行序列化。可以通过Serialiazable或Parcelable接口完成序列化:
- Serialiazable Java提供的接口,使用简单,内存开销大,适用于数据持久化或网络传输。
- Parcelable Android提供的接口,使用复杂,内存开销小,适用于内存间数据传输。
Android主要的IPC方式:
- Bundle
- 文件共享
- Messenger
- AIDL
- ContentProvider
Bundle本身实现了Parcelable接口,只需要先将数据放入Bundle,再通过Intent就可以方便地在进程间传输。然而Intent机制传输效率低,大多用于应用层的功能整合。
Binder带来更高效率的进程间通信,主要用于系统层的功能整合。Messenger、AIDL、ContentProvider底层都是基于Binder机制。
二、Binder原理
Binder是Android提供的一种效率更高、更安全的基于C/S架构的IPC通信机制,其本质也是调用系统底层的内存共享实现。
从进程角度来看Binder进程间通信原理:
在Android系统中
- 用户空间彼此不能共享
- 内核空间可以共享
- 用户空间进程通过open/ioctl等方式与内核空间通信
- Client与Server通信的实现,是由Binder驱动在内核空间完成
在android中,有很多Service都是通过binder来通信的。这里要引入另一个重要的角色:ServiceManager。ServiceManager负责管理Server所提供的服务,同时响应Client的请求并为之分配相应的服务。
可以把ServiceManager(以下简称SM)比作通讯站,Client和Server是电话用户,Server首先要向通讯站注册号码,当Client拨打号码时,通讯站首先检索是否有该号码,如果有则转接给Client,否则不响应。
需要注意的是,Client一般认为是数据发送方,Server是数据接收方,两者并非固定不变的。如果Server在收到数据后主动向Client方发送数据,则两者身份互换。
Binder通信的完整流程如下:
- Server向ServiceManager注册服务
- Client向ServiceManager申请服务
- SM作为守护进程,处理Client端请求,并管理所有Server端服务
- BinderDriver位于Kernel层,是一切运作的基础
(1)Server向SM注册服务
Server在自己的进程向Binder驱动申请创建某服务Service的Binder实体。
Binder驱动收到申请后创建该Service的Binder实体和引用,并将该Service的名字和引用发送给SM。
SM收到数据后,提取该Service的名字和引用,存入查询表。
这时如果有Client向SM申请该Service服务,SM从查询表查找该Service的引用,并发送给Client。
注意:SM保存的只是Service的引用,Service的Binder实体位于Binder驱动中。我们将实体称为本地对象,引用称为远程对象。
(2)Client从SM获取Service的远程对象
上面说了,Client进程向SM发送消息获取Service引用,进而调用Server进程的Service服务,从而实现了Client与Server进程间通信。
那么,问题来了,Client和SM本身就是两个不同的进程。当Client向SM申请服务的时候,SM实际上扮演了Server进程的角色。也就是说,我们要利用Client和SM进程间通信来完成Client和Server进程间通信。这岂不是很荒谬么,Client和Server之间可以通过SM来协调,那Client和SM之间又怎么协调呢?问题似乎变成了先有鸡还是先有蛋的悖论。
或许凡人会纠结先有鸡还是先有蛋,但上帝不会。因为上帝可以创造,他随便创建一个鸡或者蛋,问题不就解决了。Google的程序员就是Android的上帝,果然,他们创造了一只“鸡”:当Binder驱动启动后,SM会通过BINDER_SET_CONTEXT_MGR命令将自己注册为SM,此时,Binder驱动会创建一个SM的Binder实体。而且,这个Binder实体不需要生成引用,他们和Client和Server达成了协议:handle值为0的引用就是指向SM。所以,Client和Server不需要再借助第三者就能自己生成SM的引用。也就是说,只要Binder驱动启动后,Client就可以跨进程向SM申请服务,Server也可以跨进程向SM注册服务。
详细的进程间通信流程如下:
- Binder驱动启动,并生成SM的Binder实例
- Server端通过handle为0的引用向SM注册自己的Service名字和引用
- Client端通过handle为0的引用向SM申请访问某个Service
- SM通过名字查询对应的Service引用,并返回给Client端,如果有更多的Client访问该Service,则每个Client都会获得这个Service的引用
- Client端通过Service引用向Server端发送数据,此时Server端也获得了Client引用
- Server端通过Client引用向Client端返回数据
至此,一个完整的C/S通信架构建成了!
三、AIDL概述
上面讲的Binder进程间通信原理已经普遍存在于Android系统层,比如SystemServer中的PowerManagerService
、ActivityManagerService
、DisplayManagerService、
PackageManagerService
等都是通过Binder机制向外提供跨进程服务的。
以上都是系统级服务,如何在应用层通过Binder机制实现自己的IPC通信呢?
答案是AIDL,AIDL是Binder机制向外提供的接口,是Java层Binder实现的便利工具。
AIDL全称为Android接口定义语言,是一种IDL语言,它可以生成一段代码,通过预先定义的接口达到两个进程内部通信(IPC)的目的。比如一个进程中的Activity想访问另一个进程中Service,就可以通过AIDL生成代码来实现两者之间的数据交互。
从整体上来看看AIDL在Android系统进程间通讯的位置:
Android系统IPC架构中:
- 最底层依然调用Kernel层的内存共享
- Binder作为Android提供的最主要IPC机制
- AIDL是Binder的Java层实现
- Messager对AIDL进行封装,且是线程安全的
- Intent、ContentProvider、BroadcastReceiver是对Binder更高层级的封装
四、AIDL实例
下面通过一个简单的AIDL实例,来验证Android的Binder机制。
创建两个app:Client和Server。Client端界面可以输入两个数值a和b,点击“计算”按钮,调用Server端的SumService服务计算a+b,并将结果返回给Client端显示。
(1)创建Server项目
其中ISumServiceInterface.aidl是声明的aidl接口,编译后会在build目录下自动生成对应的ISumServiceInterface.java类。
// ISumServiceInterface.aidl package com.example.server; // Declare any non-default types here with import statements interface ISumServiceInterface { /** * 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); //此处声明对外提供的服务方法 int sum(int a, int b); }
SumService是本地服务类,sum方法的真正执行者。
public class SumService extends Service { @Nullable public static final String TAG = "aidl"; @Override public IBinder onBind(Intent intent) { Log.i(TAG, "server端:onBind"); return iBinder; } //ISumServiceInterface.Stub是AIDL自动生成的静态内部类,iBinder将返回给client端 private IBinder iBinder = new ISumServiceInterface.Stub() { @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { } @Override public int sum(int a, int b) throws RemoteException { Log.e(TAG,"server端:收到Client端请求:a=" + a + ",b=" + b ); return a + b; } }; }
AndroidManifest中配置SumService
<service android:name=".SumService" android:process=":remote" android:exported="true"> <intent-filter> <action android:name="com.example.server.sum_service"/> <!--client端绑定服务时需使用相同的action--> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> </service>
(2)创建Client项目
ISumServiceInterface.aidl和server端是一样的(注意包名也必须一样),编译后同样自动生成ISumServiceInterface.java类。
MainActivity允许client端用户输入两个数值,然后远程调用server端的sum方法,最后将server端返回的结果显示在client端。
public class MainActivity extends AppCompatActivity { private static final String TAG = "aidl"; private EditText a = null; private EditText b = null; private TextView display; private Button sum; private Button bind; private ISumServiceInterface sumServiceInterface; private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.i(TAG, "client端:服务已绑定!"); //这里调用Stub.asInterface实际上获得了server端ISumServiceInterface.Stub的远程对象 sumServiceInterface = ISumServiceInterface.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { Log.i(TAG, "client端:服务已断开!"); sumServiceInterface = null; } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); a = findViewById(R.id.a); b = findViewById(R.id.b); sum = findViewById(R.id.sum); bind = findViewById(R.id.bind); display = findViewById(R.id.display); sum.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { int n1 = Integer.parseInt(a.getText().toString().trim()); int n2 = Integer.parseInt(b.getText().toString().trim()); //sumServiceInterface是server端ISumServiceInterface.Stub的远程对象 //通过它就可以调用server端的sum方法 int result = sumServiceInterface.sum(n1, n2); Log.i(TAG, "client端:收到server端返回结果:" + result); display.setText("结果:" + result); } catch (Exception e) { e.printStackTrace(); } } }); bind.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { bindService(); } }); } private void bindService() { Log.i(TAG, "client端:开始绑定SumService..."); Intent intent = new Intent(); //这里的Action和Server端配置的必须保持一致 intent.setAction("com.example.server.sum_service"); //要求显示调用,指定包名 intent.setComponent(new ComponentName("com.example.server", "com.example.server.SumService")); //绑定并自动创建服务 bindService(intent, conn, Context.BIND_AUTO_CREATE); } @Override protected void onDestroy() { super.onDestroy(); unbindService(conn); } }
(3)安装并运行
安装client和server,运行client输入55和45,点击BIND,点击SUM,结果显示:
Log打印如下:
I aidl : client端:开始绑定SumService... I aidl : server端:onBind I aidl : client端:服务已绑定! E aidl : server端:收到Client端请求:a=55,b=45 I aidl : client端:开始绑定SumService... I aidl : server端:onBind I aidl : client端:服务已绑定! E aidl : server端:收到Client端请求:a=55,b=45 I aidl : client端:收到server端返回结果:100
可以看出,成功从client端调用server端sum方法并返回结果。
五、AIDL底层逻辑
从上面的实例已经知道如何使用AIDL,下面分析一下AIDL真正的底层逻辑,看它是如何使用Binder机制实现IPC通信的。
当我们声明ISumServiceInterface.aidl并编译后,自动生成了ISumServiceInterface.java类。这个类才是真正实现Java层与Binder驱动交互的。
下面详细分析ISumServiceInterface.java代码(删减部分冗余代码):
public interface ISumServiceInterface extends android.os.IInterface { /** * Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.example.server.ISumServiceInterface { //DESCRIPTOR是Binder服务的唯一标志,一般用完全类名表示 private static final java.lang.String DESCRIPTOR = "com.example.server.ISumServiceInterface"; /** * Construct the stub at attach it to the interface. * Stub构造函数,在SumService的onBind()时实例化并作为IBinder返回给client */ public Stub() { //表示将Stub实例绑定为ISumServiceInterface的本地实现,此后,queryLocalInterface(DESCRIPTOR)将返回此本地实现,而不是null this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.example.server.ISumServiceInterface interface, * generating a proxy if needed. * 这个方法在client端调用,用于将SumService返回的IBinder转化成ISumServiceInterface实例,分两种情况: * 1.client有ISumServiceInterface的本地对象,那就直接返回,也就不需要IPC了 * 2.本例中Stub构造函数是在Server端SumService的onBind()时调用的,client端没有本地对象, * 所以需要生成并返回一个Proxy,即远程对象,然后,远程对象和本地对象通过transact方法通信(由Binder驱动实现) */ public static com.example.server.ISumServiceInterface asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); //判断是否有本地实现,有,则返回本地对象,不需要IPC if (((iin != null) && (iin instanceof com.example.server.ISumServiceInterface))) { return ((com.example.server.ISumServiceInterface) iin); } //没有,则生成并返回Proxy,即远程对象,然后,远程对象和本地对象通过transact方法通信(由Binder驱动实现) return new com.example.server.ISumServiceInterface.Stub.Proxy(obj); } @Override public android.os.IBinder asBinder() { return this; } /** * 这是本地对象接收数据的方法,当远程对象发起请求后,经过Binder驱动的封装处理,最终会调到server端的这个方法 * * @param code server端通过code来确定client端请求的目标方法,比如TRANSACTION_sum表示调用client端希望调用server端的sum方法 * @param data 方法参数的封装,比如,data.readInt()得到参数a,再次调用data.readInt()得到参数b * @param reply 结果返回的地方,比如,sum方法得到a+b=100,就将100写入replay,replay通过Binder驱动的传送,返回给正在挂起等待结果的client端 * @param flags 一般没什么卵用 * @return * @throws android.os.RemoteException */ @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { java.lang.String descriptor = DESCRIPTOR; switch (code) { //client希望调用sum方法 case TRANSACTION_sum: { data.enforceInterface(descriptor); int _arg0; _arg0 = data.readInt(); //读取参数a int _arg1; _arg1 = data.readInt(); //读取参数b int _result = this.sum(_arg0, _arg1); //调用本地对象的sum方法,这个this很明白了吧,就是Server端在SumService的onBind()时实例化的那个Stub reply.writeNoException(); reply.writeInt(_result); //结果写入reply,经Binder驱动传送给正在挂起等待的client return true; } } return true; } //Proxy是远程对象,通过transact向Binder驱动发送数据,并最终通过onTransact发送给Server端 private static class Proxy implements com.example.server.ISumServiceInterface { private android.os.IBinder mRemote; /** * Proxy构造函数,client端调用 * * @param remote 指明是哪个Binder的代理,这里的remote是Server端SumServcie的onBind()方法返回的IBinder,即Server端的Stub * @return */ Proxy(android.os.IBinder remote) { mRemote = remote; } @Override public android.os.IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } @Override //client端调用远程对象的sum方法,最终会调到server端的sum方法 public int sum(int a, int b) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); int _result; try { _data.writeInterfaceToken(DESCRIPTOR); //指明向哪个Binder请求服务 _data.writeInt(a); //写入参数a _data.writeInt(b); //写入参数b boolean _status = mRemote.transact(Stub.TRANSACTION_sum, _data, _reply, 0); //通过transact向server端发起TRANSACTION_sum请求,即sum方法 if (!_status && getDefaultImpl() != null) { return getDefaultImpl().sum(a, b); } _reply.readException(); _result = _reply.readInt(); //读取server端返回的结果,至此,一个完整的IPC通信结束。 } finally { _reply.recycle(); _data.recycle(); } return _result; } public static com.example.server.ISumServiceInterface sDefaultImpl; } static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_sum = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); } }
分析ISumServiceInterface.java类,实际上主要包含Stub和Proxy两个静态内部类。
一个完整的AIDL通信流程如下:
- Client端绑定Server端的SumService服务
- Server端的SumService返回IBinder,即ISumServiceInterface.Stub对象
- Client端调用ISumServiceInterface.Stub.onInterface(IBinder ibinder)方法,得到ISumServiceInterface.Stub.Proxy对象
- Client端调用Proxy的sum()方法,Proxy的sum()方法会调用transact()方法通过Binder驱动回调到Server端的onTransact()方法
- Client端挂起
- Server端通过onTransact()方法读取方法名和参数,进而回调本地Stub实例的sum方法
- Server端将sum()方法结果写入reply
- Client端读取reply结果
有两个需要注意的地方:
- 因为Client端调用Proxy的sum方法后会挂起,所以如果预计server端是耗时操作,Client端应该做好异步操作
- 因为Server端的onTransact()方法运行在Binder线程池,所以即使sum()方法是耗时操作,也不用另起线程
至此,AIDL的底层逻辑分析完毕!