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方法的逻辑,总共就三步。

  1. 创建了两个Parcel对象(跨进程只能传递Parcel以及Binder类型的对象)_data用来存放参数,_reply用来存放返回值。这里传入的DESCRIPTOR是一个包名。这个DESCRIPTOR也起到标识的作用,只有transact和onTransact方法中parcel对象的一致时才能成功调用。这也是为什么我们要求Server端和Client端aidl存放目录结构一致的原因,不一致的话这个自动生成DESCRIPTOR也会不一样;

  2. 调用了Binder的transact方法,这个方法其实就是跨进程调用的核心,他的第一个参数是一个int值,用来告诉Server端(onTransact方法)我要调用的具体是哪个方法,这里Stub.TRANSACTION_add标志的就是add方法,第二个和第三个之前已经说了分别是参数和返回值,第四个是个标志位,当0的时候代表有返回值,当1的时候则代表没有返回值(此时就算你的方法真的有返回值_reply中也不会有结果)

  3. 读取_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内部实现详解 (一)的更多相关文章

  1. Android进阶之AIDL的使用详解

    原文首发于微信公众号:jzman-blog,欢迎关注交流! AIDL(Android 接口定义语言),可以使用它定义客户端与服务端进程间通信(IPC)的编程接口,在 Android 中,进程之间无法共 ...

  2. Android进阶笔记:AIDL内部实现详解 (二)

    接着上一篇分析的aidl的流程解析.知道了aidl主要就是利用Ibinder来实现跨进程通信的.既然是通过对Binder各种方法的封装,那也可以不使用aidl自己通过Binder来实现跨进程通讯.那么 ...

  3. 我的Android进阶之旅------>HTTP Header 详解

    HTTP(HyperTextTransferProtocol)即超文本传输协议,目前网页传输的的通用协议.HTTP协议采用了请求/响应模型,浏览器或其他客户端发出请求,服务器给与响应.就整个网络资源传 ...

  4. 【Android进阶】Application对象的详解

    1:Application是什么? Application和Activity,Service一样,是android框架的一个系统组件,当android程序启动时系统会创建一个 application对 ...

  5. Android进阶笔记:Messenger源码详解

    Messenger可以理解为一个是用于发送消息的一个类用法也很多,这里主要分析一下再跨进程的情况下Messenger的实现流程与源码分析.相信结合前面两篇关于aidl解析文章能够更好的对aidl有一个 ...

  6. Android笔记-2-TextView的属性详解

    [Android 基础]TextView的属性详解 android:autoLink :设置是否当文本为URL链接/email/电话号码/map时,文本显示为可点击的链接.可选值(none/web / ...

  7. Android 多线程之IntentService 完全详解

    关联文章: Android 多线程之HandlerThread 完全详解 Android 多线程之IntentService 完全详解 android多线程-AsyncTask之工作原理深入解析(上) ...

  8. Android 多线程之HandlerThread 完全详解

    关联文章: Android 多线程之HandlerThread 完全详解 Android 多线程之IntentService 完全详解 android多线程-AsyncTask之工作原理深入解析(上) ...

  9. 探索Redis设计与实现6:Redis内部数据结构详解——skiplist

    本文转自互联网 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial ...

随机推荐

  1. 初学者学习Javascript很吃力怎么办?到底该如何学习Js?

      Js给初学者的印象总是那么的“杂而乱”,相信很多初学者都在找轻松学习Js的途径.在这里给大家总结一些学习Js的经验,希望能给后来的学习者探索出一条“轻松学习Js之路”. Js给人那种感觉的原因多半 ...

  2. python初学--文件操作、字典

    文件读写 1.先打开文件 2.读取/写入内容 3.保存文件   文件的open模式有三种 1.w 写模式,它是不能读的 只要用w打开文件,文件中的东西都会被清空 w+, 写读模式,只要沾上w 就会清空 ...

  3. Disruptor 线程间共享数据无需竞争

    队列的作用是缓冲 缓冲到 队列的空间里.. 线程间共享数据无需竞争 原文 地址  作者  Trisha   译者:李同杰 LMAX Disruptor 是一个开源的并发框架,并获得2011 Duke’ ...

  4. 常见的四种Content-Type类型

    application/x-www-form-urlencoded 常见的form提交 multipart/form-data 文件提交 application/json 提交json格式的数据 te ...

  5. hdu 2955(概率转化,01背包)

    Hot~~招聘——巴卡斯(杭州),壹晨仟阳(杭州),英雄互娱(杭州) (包括2016级新生)除了校赛,还有什么途径可以申请加入ACM校队? Robberies Time Limit: 2000/100 ...

  6. JAVA 语言类的特性(成员、重载、构造方法、静态成员)

    一.类的私有成员和公有成员 1.私有成员 修饰符private    如果在类的声明前加上修饰符private,则无法从该类的外部访问到该类的内部成员,而只能被该类自身访问和修改,而不嗯那个被其他类, ...

  7. 微信小程序radio组件 - 如何改变默认样式大小?

    今天在写小程序的时候用到radio组件,但是很懊恼并未提供修改radio组件大小属性,第一感觉准备用css width , height 改变radio的大小,但是怎么搞也无法改变. 但是又不愿意搞个 ...

  8. jmeter+Jenkins 持续集成中发送邮件报错:MessagingException message: Exception reading response

    已经配置好了发送邮件的相关信息,但是执行完脚本出现报错:MessagingException message: Exception reading response 1.查看Jenkins本次构建的控 ...

  9. 使用PuTTY连接树莓派

    这是 meelo 原创的 玩转树莓派 系列文章 PuTTY是一个支持Telnet.SSH协议,实现远程登录的软件.树莓派的官方操作系统Raspbian默认开启了SSH协议进行登录,这样即使没有专门的显 ...

  10. Codeforces 1082 A. Vasya and Book-题意 (Educational Codeforces Round 55 (Rated for Div. 2))

    A. Vasya and Book time limit per test 2 seconds memory limit per test 256 megabytes input standard i ...