Android进阶笔记:AIDL内部实现详解 (一)
AIDL内部实现详解 (一)
AIDL的作用是实现跨进程通讯使用方法也非常的简单,他的设计模式是典型的C/S架构。使用AIDL只要在Client端和Server端的项目根目录下面创建一个aidl的文件夹,在aidl文件夹的下面用java代码编写一个后缀名为.aidl的接口文件然后重新编译一下就会在gen目录下生成相对应的java文件。这里主要研究aidl的运作流程以及原理。
aidl结构
首先我在Server端去实现了一个aidl的接口文件代码如下:
//IMyAidl.aidl
interface IMyAidl {
int add(int arg1, int aarg2);
}
重新编译(rebuild)就会在generated目录下面生成相应的IMyAidl.java的文件代码如下:
public interface IMyAidl extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.coder_f.aidlserver.IMyAidl {
private static final java.lang.String DESCRIPTOR = "com.coder_f.aidlserver.IMyAidl";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.coder_f.aidlserver.IMyAidl interface,
* generating a proxy if needed.
*/
public static com.coder_f.aidlserver.IMyAidl asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.coder_f.aidlserver.IMyAidl))) {
return ((com.coder_f.aidlserver.IMyAidl) iin);
}
return new com.coder_f.aidlserver.IMyAidl.Stub.Proxy(obj);
}
@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_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;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.coder_f.aidlserver.IMyAidl {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public int add(int arg1, int arg2) 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);
_data.writeInt(arg1);
_data.writeInt(arg2);
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public int add(int arg1, int arg2) throws android.os.RemoteException;
}
主要就是根据aidl文件自动生成了stub的内部类,这个内部类不仅继承了Binder还继承了父类。这里继承父类主要是为了能够获得回调add接口方法,stub类并没有去真正的实现add方法。
stub类结构很清楚里面只有3个方法加上1个内部类分别是:
- asInterface 根据传入的binder来判断当前是否是跨进程,如果跨进程则返回一个Proxy对象,如果不是跨进程则直接返回当前接口实现类(想当是调用本地接口);
- asBinder 返回当前binder对象,继承IInterface必须实现的方法(满足内部跨进程)
- onTransact Binder的方法当client端的Binder对象调用了transact方法时此方法会被回调
- Proxy类 一个代理类,主要作用是配置Binder对象的transact方法(这个方法继承自IBinder)需要的参数以及调用transact方法。Proxy类也继承了我们自己定义的aidl接口,主要为了保证接口中的方法都有相对应transact去调用真正的server端方法(应为继承了接口,如果不是抽象类那就必须得去实现这些方法吧)。
这里还有一个特别的参数就是TRANSACTION_add这么一个常量,他是由一个常量+0构成的,这个常量其实就是0,这个常量主要来标记方法的如果有多个方法:
//当前只有一个方法所以是+0,当更多的方法时每个方法都会对应一个数字。例如有两个时那就会如下
static final int TRANSACTION_xxx = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_yyy = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
...
这些方法(类)现在有一个印象就可以,后面会详细的分析。
以上是一个aidl的完整结构,当然真正要使用还需要去有一个类来继承stub类并且实现最初aidl文件中定义的所有接口(注意stub类是一个抽象类,它继承了aidl的接口但却没有真正的去实现它,这里需要我们来实现)。当然这个java类也需要在client端实现一遍(也就是自动生成一遍)。
aidl流程
要分析aidl流程所以我先创建了一个Service(MyService),在里面创建了一个内部类MyAidlImpl来继承IMyAidl.Stub并且实现我们最初定义在aidl文件中一直未被实现的add方法。在Service的onBind方法中返回这个内部类的实例(因为Stub是继承了Binder类所以MyAidlImpl的实例也会是Binder类型),具体代码如下:
Server端:MyService.java
//MyService.java
public class MyService extends Service {
private MyAidlImpl myAidlImpl;
public MyService() {
}
@Override
public void onCreate() {
super.onCreate();
myAidlImpl = new MyAidlImpl();
}
@Override
public IBinder onBind(Intent intent) {
return myAidlImpl;
}
}
class MyAidlImpl extends IMyAidl.Stub {
@Override
public int add(int arg1, int arg2) throws RemoteException {
return arg1 + arg2;
}
}
接口的实现方法很简单,就是把传进来的两个参数加一下再返回回去。现在这个Service就相当于是一个Server端,来远程处理一些任务(这里就是arg1 + arg2)并且返回结果。
至于Client端代码就是最普通的一个Activity里面去绑定刚刚实现的Service,这里我为了保证是跨进程我重新创建了一个项目。(也可以在当前项目里面来做,只要在manifest文件中把Service的android:process设置为“:remote”就可以了,这样Service就不会和activity运行在一个进程中)
Client端:MainActivity.java
public class MainActivity extends AppCompatActivity {
private static final String TAG = "AIDLClient";
private ServiceConnection serviceConnection;
private IMyAidl myAidl;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myAidl = IMyAidl.Stub.asInterface(service);
try {
Log.d(TAG, "onServiceConnected: data from server = " + myAidl.add(123, 456));
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
myAidl = null;
}
};
bindService(new Intent("com.coder_f.aidlserver.MyService"), serviceConnection, BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(serviceConnection);
}
}
根据上面代码我们现在看一下,首先是Client端来bindService,这里绑定成功以后就会返回一个Binder对象,这个对象就是Server端onBind的时候返回的,是MyAidlImpl的实例,通过IMyAidl.Stub.asInterface(service)方法来获得一个Proxy类的对象(这里考虑的是跨进程调用的情况所以返回的是proxy对象),然后我们调用了proxy对象里面的add方法,那我们就跟着去看一下根据aidl自动生成的java类中proxy的add方法里面具体是什么逻辑。
private static class Proxy implements com.coder_f.aidlserver.IMyAidl {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public int add(int arg1, int arg2) 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);
_data.writeInt(arg1);
_data.writeInt(arg2);
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
这里的remote就是asInterfa方法中传入的Binder对象,通过这个Binder对象,接着调用了add方法。这里来仔细看看这个add方法的逻辑,总共就三步。
创建了两个Parcel对象(跨进程只能传递Parcel以及Binder类型的对象)_data用来存放参数,_reply用来存放返回值。这里传入的DESCRIPTOR是一个包名。这个DESCRIPTOR也起到标识的作用,只有transact和onTransact方法中parcel对象的一致时才能成功调用。这也是为什么我们要求Server端和Client端aidl存放目录结构一致的原因,不一致的话这个自动生成DESCRIPTOR也会不一样;
调用了Binder的transact方法,这个方法其实就是跨进程调用的核心,他的第一个参数是一个int值,用来告诉Server端(onTransact方法)我要调用的具体是哪个方法,这里Stub.TRANSACTION_add标志的就是add方法,第二个和第三个之前已经说了分别是参数和返回值,第四个是个标志位,当0的时候代表有返回值,当1的时候则代表没有返回值(此时就算你的方法真的有返回值_reply中也不会有结果)
读取_reply中返回的结果。
这里要注意的是当调用transact方法时当前线程会被挂起。此时如果这个方法运行在主线程时,而server端又因为各种原因或者方法本身就是耗时的导致不能及时返回结果就会使得ui线程卡住。而我上面Client端方法中myAidl.add(123,456)是放在了onServiceConnected方法里面,而onServiceConnected方法其实是运行在主线程的(具体可以看我之前bindservice流程分析文章中最后面的说明)
这里调用Binder的transact方法以后经过一系列的流程(这里具体什么流程没有仔细研究过,涉及到jni底层native的binder方法)最后会回调到Server端的onTransact方法。
好了那我们就看看自动给我们生成的java文件中onTransact方法的逻辑代码如下。
@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_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;
}
}
return super.onTransact(code, data, reply, flags);
}
这里的onTransact方法的参数其实和transact方法的参数是一一对应的,第一个就是标志方法的code默认会有有一个INTERFACE_TRANSACTION用来验证之前说的DESCRIPTOR,另外一个就是TRANSACTION_add,这里我们接口中只有一个所以总共onTransact方法中只对这一个方法处理。
这个add方法和proxy类中add方法的结构很类似,就是从_data中取出(proxy.add是创建写入)参数调用真正add方法(在service中实现的add方法)然后把结果写入到reply(proxy.add是创建读取)中。注意这个方法有一个返回值,如果返回false则Client端会调用失败。
上面就是aidl的一整套流程。
总结:
aidl其实就是利用Binder来实现跨进程调用。而这个系统根据.aidl文件生成的java文件就是对binder的transact方法进行封装。
onTransact方法是提供给server端用的,transact方法(内部类proxy封装了transact方法)和asInterface方法是给client端用的。系统自动生成不知道你要把哪个当做server端哪个当做client端所以就都生成了。
DESCRIPTION是根据aidl文件位置(包名)来生成的,DESCRIPTION是binder的唯一标识,这也解释了为什么要保证client端和server端.adil文件的包结构一样;
aidl的transact的调用会导致当前线程挂起,因此如果Server端的方法耗时最好另起线程来调用transact方法;
aidl通过Parcel来传递参数和返回值,因为binder只支持Binder对象和parcel对象的跨进程调用;
Binder,IBinder,IInterface三个的关系。Binder是继承自IBinder对象的,IBinder是远程对象的基本接口。另外IBinder的主要API是transact(),与它对应另一方法是Binder.onTransact(),而IInterface主要为aidl服务,里面就一个方法——asBinder,用来获得当前binder对象(方便系统内部调用)。
最后说白了aidl的核心就是client调用binder的transact方法,server通过binder的onTransact方法了来执行相应的方法并返回。
Android进阶笔记:AIDL内部实现详解 (一)的更多相关文章
- Android进阶之AIDL的使用详解
原文首发于微信公众号:jzman-blog,欢迎关注交流! AIDL(Android 接口定义语言),可以使用它定义客户端与服务端进程间通信(IPC)的编程接口,在 Android 中,进程之间无法共 ...
- Android进阶笔记:AIDL内部实现详解 (二)
接着上一篇分析的aidl的流程解析.知道了aidl主要就是利用Ibinder来实现跨进程通信的.既然是通过对Binder各种方法的封装,那也可以不使用aidl自己通过Binder来实现跨进程通讯.那么 ...
- 我的Android进阶之旅------>HTTP Header 详解
HTTP(HyperTextTransferProtocol)即超文本传输协议,目前网页传输的的通用协议.HTTP协议采用了请求/响应模型,浏览器或其他客户端发出请求,服务器给与响应.就整个网络资源传 ...
- 【Android进阶】Application对象的详解
1:Application是什么? Application和Activity,Service一样,是android框架的一个系统组件,当android程序启动时系统会创建一个 application对 ...
- Android进阶笔记:Messenger源码详解
Messenger可以理解为一个是用于发送消息的一个类用法也很多,这里主要分析一下再跨进程的情况下Messenger的实现流程与源码分析.相信结合前面两篇关于aidl解析文章能够更好的对aidl有一个 ...
- Android笔记-2-TextView的属性详解
[Android 基础]TextView的属性详解 android:autoLink :设置是否当文本为URL链接/email/电话号码/map时,文本显示为可点击的链接.可选值(none/web / ...
- Android 多线程之IntentService 完全详解
关联文章: Android 多线程之HandlerThread 完全详解 Android 多线程之IntentService 完全详解 android多线程-AsyncTask之工作原理深入解析(上) ...
- Android 多线程之HandlerThread 完全详解
关联文章: Android 多线程之HandlerThread 完全详解 Android 多线程之IntentService 完全详解 android多线程-AsyncTask之工作原理深入解析(上) ...
- 探索Redis设计与实现6:Redis内部数据结构详解——skiplist
本文转自互联网 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial ...
随机推荐
- Keepalived高可用配置
Keepalived简介 Keepalived基于VRRP协议在服务器之间建立了主备关系,通常称之为高可用对.VRRP中文叫虚拟路由冗余协议,目的是解决静态路由的单点故障问题.高可用对之间通过IP多播 ...
- gradle eclipse 配置
http://blog.csdn.net/caolaosanahnu/article/details/17022321 从gradle官网下载 解压,配置环境变量,gradle -v 验证 gradl ...
- mysql-备份及关联python
阅读目录 IDE工具介绍 MySQL数据库备份 mysqldump实现逻辑备份 回复逻辑备份 备份/恢复案例 自动化备份 表的导出和导入 数据库迁移 pymysql模块 一 链接.执行sql.关闭(游 ...
- 机器学习方法(四):决策树Decision Tree原理与实现技巧
欢迎转载,转载请注明:本文出自Bin的专栏blog.csdn.net/xbinworld. 技术交流QQ群:433250724,欢迎对算法.技术.应用感兴趣的同学加入. 前面三篇写了线性回归,lass ...
- 给定一列数字将其平移n位
原题的意思是给定一个指定长度的数组,然后接受一个数字m,将原数组前m位移动到最后,且顺序不变. 看到这个题,想到的第一个方法就是在用一个数组来储存改变后的数字,代码如下 int func(){ int ...
- Pygame-依葫芦画瓢之兔獾大战
Pygame-依葫芦画瓢之兔獾大战 前几天看到国外一个12岁的孩子写的兔獾大战游戏,心生敬佩,想当年我还是12岁的时候还不知电脑为何物,连小霸王都未曾玩过.自己也未曾想去搞游戏开发,纯属自娱自乐.在此 ...
- STL容器 -- Stack
核心:后进后出, LIFO. 头文件: #include <stack> 常用的构造方法: stack<int> st1; //构造一个空的存放 int 型的栈 stack&l ...
- RabbitMQ (八) 队列的参数详解
代码中,我们通常这样声明一个队列: //声明队列 channel.QueueDeclare ( queue: QueueName, //队列名称 durable: false, //队列是否持久化.f ...
- 安卓 内存泄漏检测工具 LeakCanary 使用
韩梦飞沙 yue31313 韩亚飞 han_meng_fei_sha 313134555@qq.com 配置 build.gradle dependencies { debugCompile 'com ...
- 初见Python<7>:Python操作mysql
1.基本介绍: python标准数据库接口为python DB-API,它为开发人员提供了数据库应用编程接口,可以支持mysql.Oracle.MSSQL.Sybase等多种数据库,不同的数据库需要下 ...