Android 进阶9:进程通信之 AIDL 解析
读完本文你将了解:
在 Android 进阶7:进程通信之 AIDL 的使用 中我们使用 AIDL 实现了跨进程的通信,但是不清楚 AIDL 帮我们做了什么。
AIDL 的本质是简化我们 IPC 开发,它使用的是 Binder 机制,于是在上篇文章 Android 进阶8:进程通信之 Binder 机制浅析 中我们简单了解了 Binder 机制的流程。
有了前面的铺垫,这篇文章我们就一起来看看 AIDL 究竟帮我们做了什么。
AIDL
前面我们讲了,在使用 AIDL 编写 IPC 代码时,我们只需要编写简单的 接口 aidl 文件:
Make Project 后系统就会帮我们生成 Java 文件:
AIDL 生成文件分析
AIDL 帮我们生成内容:
代码如下:
可以看到,生成的接口 IMyAidl 继承了 IInterface,Android 进阶8:进程通信之 Binder 机制浅析 中我们介绍了,IInterface 是进程间通信定义的通用接口。
同时 IMyAidl 中也包含了我们在 aidl 文件中声明的两个方法。
除此外,IMyAidl 中还包括一个抽象类 Stub,它是一个 Binder,实现了 IMyAidl 接口:
Stub
代码如下:
public static abstract class Stub extends android.os.Binder implements net.sxkeji.shixinandroiddemo2.IMyAidl {
//唯一标识,一般为完整路径
private static final java.lang.String DESCRIPTOR = "net.sxkeji.shixinandroiddemo2.IMyAidl";
/**
* 将当前接口与 Binder 绑定
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* 将一个 IBinder 转换为 IMyAidl,如果不在一个进程就创建一个代理
*/
public static net.sxkeji.shixinandroiddemo2.IMyAidl asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
//拿着标识从本地查询接口
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof net.sxkeji.shixinandroiddemo2.IMyAidl))) {
return ((net.sxkeji.shixinandroiddemo2.IMyAidl) iin);
}
//查不到就返回代理
return new net.sxkeji.shixinandroiddemo2.IMyAidl.Stub.Proxy(obj);
}
//覆盖 IInterface 的方法,获取当前接口对应的 Binder 对象
@Override
public android.os.IBinder asBinder() {
return this;
}
//关键的方法,处理操作,返回
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: { //获取当前接口的描述符
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_addPerson: { //执行 addPerson 方法
data.enforceInterface(DESCRIPTOR);
net.sxkeji.shixinandroiddemo2.bean.Person _arg0;
if ((0 != data.readInt())) { //反序列化传入的数据
_arg0 = net.sxkeji.shixinandroiddemo2.bean.Person.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
//调用 addPerson 方法,这个方法的实现是在服务端
this.addPerson(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_getPersonList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<net.sxkeji.shixinandroiddemo2.bean.Person> _result = this.getPersonList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
//不在一个进程时返回的代理
private static class Proxy implements net.sxkeji.shixinandroiddemo2.IMyAidl {...}
//用于 onTransact 方法的两个 code,分别标识要进行的操作
static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_getPersonList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
Stub 的几个关键内容介绍:
构造函数- 调用了
attachInterface()方法 - 将一个描述符、特定的 IInterface 与当前 Binder 绑定起来,这样后续调用 queryLocalInterface 就可以拿到这个
- 需要创建一个
DESCRIPTOR,一般是类的具体路径名,用于唯一表示这个 IInterface
- 调用了
asInterface()
- 将
IBinder转换为IMyAidl,这用于返回给客户端 - 不在一个进程的话,客户端持有的是一个代理
- 将
onTransact()
- Binder 关键的处理事物方法
- 根据传入的 code,调用本地/服务端的不同方法
其中可以看到,在不同进程时返给客户端的是代理。
Proxy
代码:
private static class Proxy implements net.sxkeji.shixinandroiddemo2.IMyAidl {
private android.os.IBinder mRemote; //代理的远端 IBinder
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
//获取代理的 Binder
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
/**
* 代理嘛,处理下数据后直接调用实际 Binder 来处理
*/
@Override
public void addPerson(net.sxkeji.shixinandroiddemo2.bean.Person person) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((person != null)) {
_data.writeInt(1);
person.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
//调用远端
mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public java.util.List<net.sxkeji.shixinandroiddemo2.bean.Person> getPersonList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<net.sxkeji.shixinandroiddemo2.bean.Person> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getPersonList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(net.sxkeji.shixinandroiddemo2.bean.Person.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
AIDL 生成的内容小结
IInterface类型的接口,包括:Stub抽象类- aidl 接口定义的操作方法
Stub,是一个Binder,同时也是一个IInterface,包括:
- 将 Binder 转成 IInterface 的
asInterface()方法 - 处理调度的
onTransact()方法 - 用于在
onTransact()中标识要进行的操作的两个标志 - 一个
IInterface类型的代理
- 将 Binder 转成 IInterface 的
Proxy,IInterface类型的代理,包括:
- 接口定义方法的伪实现,实际调用的是真正的 Binder 的方法
一句话总结:AIDL 帮我们生成了 Binder 和 跨平台接口的转换类 Stub,以及在不同进程时,客户端拿到的代理 Proxy。
现在回去看一下 AIDL 的使用,就会多了些理解。
AIDL 的使用回顾
服务端
使用时先在另一个进程的 Service 中实现 AIDL 生成文件中的 Stub 类,然后在 onBind() 中返回:
结合上面的分析,可以知道,我们在服务端实例化的是 Stub 的实体,它既是 Binder 也是 IInterface。在其中实现了接口定义的方法,然后在 onBind() 中返回自己。
客户端
在 Activity 中使用 bindService() 绑定服务,然后再回调中,调用 Stub.asInterface() 将拿到的远端 Binder 转换为定义的接口,跨进程的话这里拿到的实际是代理接口:
然后就可以调用 Service 中方法了。
小结
根据上面的分析,我们可以看到,AIDL 帮我们做了以下几件事:
- 根据定好的接口生成不同进程都可以共同访问的接口类
- 在接口类中提供了 Binder 和接口的共同载体
Stub - 在
Stub中创建了代理类,用于映射调用实际接口实现
有了 AIDL,我们编写跨进程操作就变得十分简单,我们只需要关注业务接口的实现即可。
手动写个 Binder
我们可以模仿 AIDL 创建的文件,手动写个 Binder 来加深下理解。
首先是定义跨进程接口,实现 IInterface
在其中定义要跨进程做的操作,以及标识这两个操作的 code:
public interface IMyAidlDiy extends IInterface {
static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_getPersonList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
public void addPerson(Person person) throws RemoteException;
public List<Person> getPersonList() throws RemoteException;
}
然后在其中创建这个接口与对应 Binder 的转换类 Stub
既然要两头讨好,那它就需要继承 Binder 的同时实现前面定义的接口,同时提供 Binder 和接口转换的方法,以及作为接口处理事物的方法:
public static abstract class Stub extends Binder implements IMyAidlDiy {
private static final String DESCRIPTOR = "net.sxkeji.shixinandroiddemo2.activity.ipc.IMyAidlDiy";
public Stub() {
attachInterface(this, DESCRIPTOR);
}
@Override
public IBinder asBinder() {
return this;
}
public static IMyAidlDiy asInterface(IBinder binder){
if (binder == null){
return null;
}
IInterface localInterface = binder.queryLocalInterface(DESCRIPTOR);
if (localInterface != null && localInterface instanceof IMyAidlDiy){
return (IMyAidlDiy) localInterface;
}else {
return new Stub.Proxy(localInterface);
}
}
@Override
protected boolean onTransact(final int code, final Parcel data, final Parcel reply, final int flags) throws RemoteException {
switch (code){
case TRANSACTION_addPerson:
data.enforceInterface(DESCRIPTOR);
Person _arg0;
if (data.readInt() != 0){
_arg0 = Person.CREATOR.createFromParcel(data); //反序列化参数
}else {
_arg0 = null;
}
this.addPerson(_arg0);
reply.writeNoException();
return true;
case TRANSACTION_getPersonList:
data.enforceInterface(DESCRIPTOR);
List<Person> personList = this.getPersonList();
reply.writeNoException();
reply.writeTypedList(personList);
break;
}
return super.onTransact(code, data, reply, flags);
}
}
最后创建代理接口,在不同进程中,客户端持有的是代理
它的作用就是伪装成真的 Binder,实际被调用时将数据处理成 Parcel,然后让被代理的 Binder 去处理:
private static class Proxy implements IMyAidlDiy {
private IBinder mRemote;
public Proxy(final IBinder obj) {
mRemote = obj;
}
public java.lang.String getInterfaceDescriptor() { //伪装的和真的 Binder 名一样
return DESCRIPTOR;
}
@Override
public void addPerson(final Person person) throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if (person != null) {
_data.writeInt(1);
person.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(TRANSACTION_addPerson, _data, _reply, 0); //这里调用实际的实现
_reply.readException();
} finally {
_data.recycle();
_reply.recycle();
}
}
@Override
public List<Person> getPersonList() throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
List<Person> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(TRANSACTION_getPersonList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(Person.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public IBinder asBinder() {
return mRemote;
}
}
自己写完实现对整个 Binder 实现的跨进程调用流程是否理解更深了呢,这里用《Android 开发艺术探索》的一张图总结下:
需要注意的是,客户端在发起远程请求时,当前线程会被挂起直到服务端返回,因此尽量不要在 UI 线程发起远程请求。
而在服务端,Binder 方法是运行在 Binder 线程池中的,因此可以直接使用同步的方式实现。
Thanks
《Android 开发艺术探索》
http://weishu.me/2016/01/12/binder-index-for-newer/
Android 进阶9:进程通信之 AIDL 解析的更多相关文章
- 图文详解 Android Binder跨进程通信机制 原理
图文详解 Android Binder跨进程通信机制 原理 目录 目录 1. Binder到底是什么? 中文即 粘合剂,意思为粘合了两个不同的进程 网上有很多对Binder的定义,但都说不清楚:Bin ...
- Android 进阶7:进程通信之 AIDL 的使用
读完本文你将了解: AIDL 是什么 AIDL 支持的数据类型 AIDL 如何编写 AIDL 实例 创建 AIDL 编写服务端代码 编写客户端代码 运行结果 总结 代码地址 Thanks 记得 201 ...
- Android上的进程通信(IPC)机制
Interprocess Communication Android offers a mechanism for interprocess communication (IPC) using rem ...
- Android 进阶13:几种进程通信方式的对比总结
不花时间打基础,你将会花更多时间解决那些不必要的问题. 读完本文你将了解: RPC 是什么 IDL 是什么 IPC 是什么 Android 几种进程通信方式 如何选择这几种通信方式 Thanks RP ...
- Android 进阶8:进程通信之 Binder 机制浅析
读完本文你将了解: IBinder Binder Binder 通信机制 Binder 驱动 Service Manager Binder 机制跨进程通信流程 Binder 机制的优点 总结 Than ...
- 详解 CmProcess 跨进程通信的实现
CmProcess 是 Android 一个跨进程通信框架,整体代码比较简单,总共 20 多个类,能够很好的便于我们去了解跨进程实现的原理. 个人猜测 CmProcess 也是借鉴了 VirtualA ...
- Android 进阶10:进程通信之 Messenger 使用与解析
读完本文你将了解: Messenger 简介 Messenger 的使用 服务端 客户端 运行效果 使用小结 总结 代码地址 Thanks 前面我们介绍了 AIDL 的使用与原理,这篇文章来介绍下 A ...
- Android 进阶11:进程通信之 ContentProvider 内容提供者
学习启舰大神,每篇文章写一句励志的话,与大家共勉. When you are content to be simply yourself and don't compare or compete, e ...
- Android学习笔记_23_服务Service之AIDL和远程服务实现进程通信以及进程间传递自定义类型参数
一.了解AIDL语言: 在Android中, 每个应用程序都有自己的进程,当需要在不同的进程之间传递对象时,该如何实现呢? 显然, Java中是不支持跨进程内存共享的.因此要传递对象, 需要把对象解析 ...
随机推荐
- image_Magic
http://www.charry.org/docs/linux/ImageMagick/ImageMagick.html mogrify -sample 25% *.jpg 批量处理图片 conv ...
- Java 基础总结(一)
本文参见:http://www.cnblogs.com/dolphin0520/category/361055.html 1. String,StringBuffer,StringBuilder 1) ...
- shell 学习一
一.shell脚本 打开文本编辑器(可以使用vi/vim命令来创建文件),新建一个文件test.sh,扩展名为sh(sh代表shell),扩展名并不影响脚本执行 #!/bin/bash echo &q ...
- QT5.6.0 鼠标支持
QT5用QPA换了QWS之后,USB鼠标就不知道怎么支持,网上搜啊搜,各种尝试,终于可以了. export TSLIB_ROOT=/mnt/sdcard/tslib export TSLIB_PLUG ...
- python3爬虫全国地址信息
PHP方式写的一团糟所以就用python3重写了一遍,所以因为第二次写了,思路也更清晰了些. 提醒:可能会有502的错误,所以做了异常以及数据库事务处理,暂时没有想到更好的优化方法,所以就先这样吧.待 ...
- 20165101刘天野 2018-2019-2《网络对抗技术》Exp5 MSF基础应用
目录 20165101刘天野 2018-2019-2<网络对抗技术>Exp5 MSF基础应用 1. 实践内容 1.1一个主动攻击实践,如ms08_067; (1分) 1.2 一个针对浏览器 ...
- c语言的按位运算符
& 按位与 | 按位或 ^ 按位异或 1. 按位与运算 按位与运算符"&"是双目运算符.其功能是参与运算的两数各对应的二进位相与.只有对应的两个二进位均为1时,结果 ...
- Effective C++ 条款06:若不想使用编译器自动生成的函数,就该明确拒绝
规则一 将成员函数声明为private而且故意不实现他们 class HomeForSale { public: ... private: ... HomeForSale(const HomeForS ...
- Ubuntu 1210怎么获得root权限登录
Ubuntu 12.10 怎么用Root 登录?以下是Ubuntu 12.10 启用Root 登录的方法吗,希望对大家有些帮助吧! 方法如下: 1.先设定一个 Root 密码 sudo passwd ...
- ubuntu 编译 vim+lua
mac $ brew install vim --with-cscope --with-lua --override-system-vim 安装spf13-vim见下面linux部分. 如果安装mac ...