参考:
- Ruheng: 关于 AIDL 使用和 Binder 机制详解,你只需要看这一篇即可
- 指间沙似流年: 【Android源码】Binder机制和AIDL分析
- TellH: 浅析 AIDL 的使用和工作原理
- silencezwm: 【漫画技术】Android跨进程通信
前言
对于AIDL, 我不是很熟悉, 因为在工作中没有用到过.但是AIDL确实是Android跨进程通信中最常见的方式, 所以学习一下是十分有必要的.
AIDL简介
AIDL (Android Interface Definition Language) 是一种接口定义语言,用于生成可以在 Android 设备上两个进程之间进行进程间通信 (interprocess communication, IPC) 的代码。如果在一个进程中(例如 Activity)要调用另一个进程中(例如 Service)对象的操作,就可以使用 AIDL 生成可序列化的参数,来完成进程间通信。
简言之,AIDL 能够实现进程间通信,其内部是通过 Binder 机制来实现的。
AIDL的使用
AIDL文件
首先建立一个AIDL文件IBookManager.aidl
这个文件我们先不去管, 待会儿再来修改.
定义一个Book类, 实现Parcelable接口.
1 | public class Book implements android.os.Parcelable { |
AIDL支持的数据类型:
- 基本数据类型
- String, CharSequence
- Parcelable
- ArrayList
- HashMap
虽然可以使用 ArrayList 与 HashMap, 但是其类型也只能用被AIDL支持的数据类型.
自定义的Parcelable对象和AIDL对象必须要显式的import进来
所以最终IBookManager的实现:
1 | // IBookManager.aidl |
另外, 如果AIDL文件中用到了自定义的Parcelable对象, 必须新建一个和它同名的AIDL文件, 并在其中声明它为Parcelable类型
1 | // IBookManager.aidl |
除此之外, AIDL中除了基本数据类型, 其他类型的参数必须标上方向: in表示输入型参数, out表示输出型参数, inout表示输入输出型参数
为了方便AIDL的开发, 建议把所有和AIDL相关的类和文件全部放入一个包中, 方便整个复制
看一下我的分包结构
现在我们Make Project, Android Studdio自动为我们生成Binder类
该类中有个重要的内部类Stub, 继承了Binder类, 并实现了IBookManager接口
服务端
为了实现进程间的通信, 我们需要创建一个独立进程的service, 来模拟进程间的通信, service充当服务端
“:remote”表示在当前的进程名前面附加上当前的包名, 并且是当前应用的私有进程
1 | <service |
在service中定义一个我上面说的那个重要的内部类IBookManager.Stub的对象, 并定义具体实现
1 | private IBookManager.Stub mBinder = new Stub() { |
当Activity调用bindService()方法时, 会回调Service中的onBind()方法, 在onBind()方法中将上面定义的mBinder对象返回
1 |
|
这样Activity就能得到Stub实现对象, 这个对象是个Binder对象, 可以实现进程间通信.
客户端
Activity充当服务端, 首先需要绑定service
1 | public void bindService(View view) { |
注意这个mServiceConnection是我们自定义的
1 | private ServiceConnection mServiceConnection = new ServiceConnection() { |
可以看到在onServiceConnected()方法里返回了我们得到了一个IBookManager对象, 这其实就是service.onBind()方法中返回的Binder对象, 利用这个Binder对象我们既可以为所欲为了, 比如addBook()
1 | public void addBook(View view) { |
效果图
而且可以看到, 确实起了两个进程, 实现的是进程间通信
两个应用之间的通信
下面来演示一下真正的两个应用之间的通信
Server
首先新建一个aidlserver的module
创建AIDL文件, 定义一个login()方法
1 | // IMyAidlInterface.aidl |
Make Module, 和上面一样, Android Studio会自动生成一个IMyAidlInterface的.java文件
新建一个Service, 在onBind()的时候返回一个继承了IMyAidlInterface.Stub的binder对象
1 | public class AIDLService extends Service { |
在AndroidManifest为Service设置默认的category和action, 使得能够通过隐式启动来启动这个Service
1 | <service |
再看一下我们在MainActivity中的逻辑
1 | public class MainActivity extends AppCompatActivity implements OnLoginListener { |
Client
接在再建一个Client的module
把服务端 aidl 整个文件夹拷贝到客户端 src/main 目录下, 同样的, Make Project一下.
接着再完善一下MainActivity中逻辑, 首先通过bindService(), 在onServiceConnected()的时候获取一个binder对象, 在点击登录按钮的时候将用户名和密码传递过去.
1 | public class MainActivity extends AppCompatActivity { |
看一下效果, 可以看到在Client端传递了用户名和密码, 在Server端获取了用户名和密码, 实现了两个应用之间的通信, 很显然是真正的跨进程通信
AIDL的原理浅析
下面以第一个Book的例子浅析一下AIDL的原理
首先看服务端的这段代码
1 |
|
我们从源码解析的角度, 看一下这个Stub的构造方法
1 | public static abstract class Stub extends android.os.Binder implements IBookManager { |
可以看到这个内部类Stub是一个抽象类, 跟踪this.attachInterface(this, DESCRIPTOR);
1 | public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) { |
可以看到只是将它自己本身和DESCRIPTOR这个字符串标识作为Binder中的全局变量保存了起来.DESRIPTOR作为Binder 的唯一标识,一般用当前 Binder 的全类名表示。
然后我们看客户端的这段代码
1 | private ServiceConnection mServiceConnection = new ServiceConnection() { |
跟踪IBookManager.Stub.asInterface(service);
这个方法
1 | /** |
注释是说将IBinder接口对象强转为IBookManager对象, 如果需要的话, 生成一个代理.
首先会根据标识符去 IBinder 的本地去查找是否有该对象,也就是调用 obj.queryLocalInterface(DESCRIPTOR) 方法,继续源码中 Binder.java
1 | public IInterface queryLocalInterface(@NonNull String descriptor) { |
意思就是如果本地存在这个标识符的 IInterface 对象,那就直接返回之前构造方法中初始化的 mOwner 对象,否则返回 null.因为我们这里涉及到了跨进程通信,虽然服务端在初始化mBinder进行了attachInterface(this, DESCRIPTOR)
(可以再看看上面的源码), 但那时另一个进程的, 所以这里会直接返回 null。接着就return一个 new Proxy(obj);
, 我们继续跟踪这个Proxy类
1 | private static class Proxy implements IBookManager { |
在客户端中, 我们调用的是IBookManager的addBook()方法, 也就是这个Proxy对象的addBook()方法, 观察这个addBook()方法.
这里就涉及到了一个重要的类 Parcel,Parcel 天生具备跨进程传输数据能力.
把需要传递的数据写入 Parcel 中,然后到达目标进程后,将 Parcel 中的数据读出即可,所以可以将 Parcel 称为数据传输载体。
这里的_data就是一个Parcel对象, 我们将Book写入Parcel, 然后调用mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
这个mRemote其实就是之前传进来的也就是Service返回的一个IBookManager.Stub对象, 我们还是再跟踪一下这个transact()方法吧, 在Binder类中
1 | /** |
可以看到onTransact()方法会被调用, 这里其实是这样
client 端:BpBinder.transact() 来发送事务请求;
server 端:BBinder.onTransact() 会接收到相应事务。
所以服务端的onTransact()方法会被调用, 其实就是IBookManager.Stub.onTransact()会被调用
1 |
|
case TRANSACTION_addBook的时候, 调用addBook()方法, 这样自然就完成了服务端中收到响应之后的操作.返回true表示这个事务是成功的.
当 onTransact 返回 true,调用成功,从 reply 对象中取出返回值,返回给客户端调用方。
总结
高度概括 AIDL 的用法,就是服务端里有一个 Service,给与之绑定 (bindService) 的特定客户端进程提供 Binder 对象。客户端通过 AIDL 接口的静态方法asInterface 将 Binder 对象转化成 AIDL 接口的代理对象,通过这个代理对象就可以发起远程调用请求了。
用这张图来表述再清楚不过了.
值得一提的是, 在这里ADDL是系统自动生成的, 实际上我们手写AIDL也是完全可以的, 只要完全理解Binder的原理, 只不过使用系统生成更加方便快速而已.
下面总结一下几种常见的IPC方式
名称 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
Bundle | 简单易用 | 只能传输Bundle支持的数据类型 | 四大组件的进程间通信 |
文件共享 | 简单易用 | 不适合高并发场景, 并且无法做到进程间的即时通信 | 无并发访问情形, 交换简单的数据, 实时性不高的场景 |
AIDL | 功能强大, 支持一对多并发通信, 支持实时通信 | 使用稍复杂, 需要处理好线程同步 | 一对多通信且有RPC需求 |
Messenger | 功能一般, 支持一对多串行通信, 支持实时通信 | 不能很好处理高并发情形, 不支持RPC, 数据通过Message进行传输, 因此只能传输Bundle支持的数据类型 | 低并发的一对多即时通信, 无RPC需求, 或者无需要返回结果的RPC需求 |
ContentProvider | 在数据源访问方便功能强大, 支持一对多并发数据共享, 可通过Call方法扩展其他操作 | 可以理解为受约束的AIDL, 主要提供数据源的CURD操作 | 一对多的进程间的数据共享 |
Socket | 功能强大, 可以通过网络传输字节流, 支持一对多并发实时通信 | 实现细节有点繁琐, 不支持直接的RPC | 网络数据交换 |
关于Binder, 如果想了解更多一些, 我推荐weishu的这一篇Binder 学习指南
demo地址
https://github.com/mundane799699/AndroidProjects/tree/master/AIDLdemo