理解AIDL原理以及系统生成的源码
2016-12-16 15:31
465 查看
工具好用,但是会有一个缺点,就是会让你不得不依赖说明书。对于我这个记忆力很差的人来说,每次使用工具都要去翻说明书是一件极其痛苦的事情,而且还有很多坑会忘记怎么填。基于此,我很多时候会觉得不如自己去理解一下工具制造的方法,然后自己造一个好了,好像我的大脑是对这种东西印象特别深刻,所以下次遇到问题我写个工具直接用的效率,比查说明书再重新趟一遍坑要高不少。今天,我要把aidl这个工具给拆了。
Part1. 原理
讲到进程间通信的原理,要先提到一个COM的概念——Proxy/Stub结构(代理/存根结构) 上图:
说个好理解的例子:
你到自动取款机上去取款;你就是客户,取款机就是你的代理;你不会在乎钱具体放在那里,你只想看到足够或更多的钱从出口出来(这就是com的透明性)。你同银行之间的操作完全是取款机代理实现。(这里就是Activity)
你的取款请求通过取款机,传到另一头,银行的服务器(这里就是远程的Service),他也没有必要知道你在哪儿取钱,他所关心的是你的身份,和你取款多少。当他确认你的权限,就进行相应的操作,返回操作结果给取款机,取款机根据服务器返回结果,从保险柜里取出相应数量的钱给你。你取出卡后,操作完成。
取款机不是直接同服务器连接的,他们之间还有一个“存根”,取款机与存根通信,服务器与存根通信。从某种意义上说存根就是服务器的代理。这个存根的英文就会Stub,我们生成的aidl类里,就是Stub + Proxy,后面会看到。
Android就是在传统的C/S架构中加入了一层,实现IPC。
Part2. 源码分析
先看一个标准的通过工具生成的aidl类和它里面的javabean,大家都知道这个bean必须继承Parcelable接口,至于aidl类怎么生成,文件怎么写,如果你不知道我觉得这文章你不必往下看了:
好,我们一点点来分析:
1.首先我们的接口要继承IInterface这个接口,这个接口干嘛的呢,看注释:Base class for Binder interfaces. When defining a new interface, you must derive it from IInterface。简单来说就是只要跨进程,就必须继承这个接口。
2.这个接口中除了我们定义的方法外,还帮我们生成了两个内部类,一个是Stub,一个是Proxy,stub是存根的意思,具体怎么理解不要纠结,这种东西意会就好了。先说结论,Stub是给服务端用的,Proxy是给客户端用的。事实也是这样,我们看一下Service和Activity中的代码。
3.回来我们继续看源码,先看Stub,Stub继承了Binder,所以具有了跨进程能力。
1)首先看构造函数,里面调用了attachInterface方法,这个方法传入了两个参数,一个是IInterface,也就是IPersonManager本身,一个是DESCRIPTOR,是一个包名,同时也起到标识的作用,只有transact和onTransact方法中parcel对象的一致时才能成功调用。这也是为什么我们要求Server端和Client端aidl存放目录结构一致的原因,不一致的话这个自动生成DESCRIPTOR也会不一样;
2)第二个方法是系统为我们自己生成的,asInterface,这个方法非常重要,我看可以看到客户端是通过这个方法拿到一个“IPersonManager”的,到底拿到的是个啥呢,看源码:
可以看到这里的逻辑是,asInterface根据传入的binder来判断当前是否是跨进程,如果跨进程则返回一个Proxy对象,如果不是跨进程则直接返回当前接口实现类(相当于调用本地接口),这个结论我相信很多人都知道,但源码里是怎么运行得到这个结果的呢?这里我们可以顺便看下queryLocalInterface这个方法的源码:
Binder中:
BinderProxy中:
这里也就能确定客户端拿到的一定是一个Proxy了,因为ServiceConnection返回的IBinder就是一个BinderProxy,queryLocalInterface后必然返回null,分支必然走到返回Proxy的那段代码。
3)asBinder是IInterface的回调方法,这里返回自己就行了。
4)下面是最重要的onTransact方法,这个方法会在客户端调用tansact方法时被回调,先放着,等讲Proxy之后再一起分析。
4.下面来看Proxy,这个类学过模式的童鞋一眼就能看出来是一个代理模式,里面放的IBinder就是客户端拿到的BinderProxy,主要看save方法:
首先准备好两个Parcel对象,一个存放传参,一个存放结果。
首先在data中写入DESCRIPTOR,确保对象正确,然后传参不为null的话,写入1并调用传参的writeToParcel方法来把具体类的数据写入data,最后就调用mRemote的transact方法,注意这个方法的第一个参数,其本质就是个方法flag,因为我们一个接口往往具有多个方法,所以需要flag作为唯一标识,这个flag在哪里被用到呢?没错就是Stub中的onTransact方法。
5.回去看Stub中的onTransact方法:
看到switch的case分支的时候,刚才那个方法的flag就对上号了吧。没错,最终就是这里被回调,TRANSACTION_save分支会首先根据proxy之前在data中写入的int值来判断传参是否为空,如果不是,会调用我们自己写Parcel类时里面的CREATOR的createFromParcel方法,其本质,就是生成了一个内存不一样但内部数据一样的Person对象,最后把这个对象传给了我们真正的save方法,执行回调。
其实中Proxy的transact到Stub的onTransact的过程中,android系统帮我们做了很多很复杂的事情,有兴趣的朋友可以追到native层去看transactNative方法,这里就不赘述了。
Part3:知道原理有啥用
最直观的作用就是可以让我们跳出工具,自己去实现一个aidl的接口,在例如SDK这样只允许使用代码的条件下。
其实最重要的是,在我们app运行的过程中会有大量的跨进程调用,无论四大组件甚至是window的操作,熟悉aidl源码可以让我们更好的理解这些源码的调用过程,理解操作系统的运行机理和设计思想,这都是一个优秀的程序员必须具备的素质。
Part1. 原理
讲到进程间通信的原理,要先提到一个COM的概念——Proxy/Stub结构(代理/存根结构) 上图:
说个好理解的例子:
你到自动取款机上去取款;你就是客户,取款机就是你的代理;你不会在乎钱具体放在那里,你只想看到足够或更多的钱从出口出来(这就是com的透明性)。你同银行之间的操作完全是取款机代理实现。(这里就是Activity)
你的取款请求通过取款机,传到另一头,银行的服务器(这里就是远程的Service),他也没有必要知道你在哪儿取钱,他所关心的是你的身份,和你取款多少。当他确认你的权限,就进行相应的操作,返回操作结果给取款机,取款机根据服务器返回结果,从保险柜里取出相应数量的钱给你。你取出卡后,操作完成。
取款机不是直接同服务器连接的,他们之间还有一个“存根”,取款机与存根通信,服务器与存根通信。从某种意义上说存根就是服务器的代理。这个存根的英文就会Stub,我们生成的aidl类里,就是Stub + Proxy,后面会看到。
Android就是在传统的C/S架构中加入了一层,实现IPC。
Part2. 源码分析
先看一个标准的通过工具生成的aidl类和它里面的javabean,大家都知道这个bean必须继承Parcelable接口,至于aidl类怎么生成,文件怎么写,如果你不知道我觉得这文章你不必往下看了:
/* * This file is auto-generated. DO NOT MODIFY. * Original file: D:\\Develop\\workspaceForJob\\AndroidTest\\src\\com\\amuro\\aidl\\IPersonManager.aidl */ package com.amuro.utils; public interface IPersonManager extends android.os.IInterface { /** * Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.amuro.aidl.IPersonManager { private static final String DESCRIPTOR = "com.amuro.aidl.IPersonManager"; /** * Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.amuro.aidl.IPersonManager interface, * generating a proxy if needed. */ public static com.amuro.aidl.IPersonManager asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof com.amuro.aidl.IPersonManager))) { return ((com.amuro.aidl.IPersonManager) iin); } return new com.amuro.aidl.IPersonManager.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_save: { data.enforceInterface(DESCRIPTOR); com.amuro.entity.Person _arg0; if ((0 != data.readInt())) { _arg0 = com.amuro.entity.Person.CREATOR.createFromParcel(data); } else { _arg0 = null; } this.save(_arg0); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); } private static class Proxy implements com.amuro.aidl.IPersonManager { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; } @Override public android.os.IBinder asBinder() { return mRemote; } public String getInterfaceDescriptor() { return DESCRIPTOR; } @Override public void save(com.amuro.entity.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_save, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } } static final int TRANSACTION_save = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); } public void save(com.amuro.entity.Person person) throws android.os.RemoteException; }
package com.amuro.entity; import android.os.Parcel; import android.os.Parcelable; public class Person implements Parcelable { private int id; private String name; public Person() { } public Person(int id, String name) { this.id = id; this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) {// 把javanbean中的数据写到Parcel dest.writeInt(this.id); dest.writeString(this.name); } // 添加一个静态成员,名为CREATOR,该对象实现了Parcelable.Creator接口 public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() { @Override public Person createFromParcel(Parcel source) {// 从Parcel中读取数据,返回person对象 return new Person(source.readInt(), source.readString()); } @Override public Person[] newArray(int size) { return new Person[size]; } }; }
好,我们一点点来分析:
1.首先我们的接口要继承IInterface这个接口,这个接口干嘛的呢,看注释:Base class for Binder interfaces. When defining a new interface, you must derive it from IInterface。简单来说就是只要跨进程,就必须继承这个接口。
2.这个接口中除了我们定义的方法外,还帮我们生成了两个内部类,一个是Stub,一个是Proxy,stub是存根的意思,具体怎么理解不要纠结,这种东西意会就好了。先说结论,Stub是给服务端用的,Proxy是给客户端用的。事实也是这样,我们看一下Service和Activity中的代码。
public class TestService extends Service { @Nullable @Override public IBinder onBind(Intent intent) { return new IPersonManager.Stub() { @Override public void save(Person person) throws android.os.RemoteException { //do your work } }; } }
/** * Created by Amuro on 2016/12/15. */ public class TestActivity extends Activity { ServiceConnection connection1 = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { IPersonManager manager = IPersonManager.Stub.asInterface(service); try { manager.save(new Person()); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); findViewById(R.id.bt).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(TestActivity.this, TestService.class); bindService(intent, connection1, BIND_AUTO_CREATE); } }); } @Override protected void onDestroy() { super.onDestroy(); unbindService(connection); } }
3.回来我们继续看源码,先看Stub,Stub继承了Binder,所以具有了跨进程能力。
1)首先看构造函数,里面调用了attachInterface方法,这个方法传入了两个参数,一个是IInterface,也就是IPersonManager本身,一个是DESCRIPTOR,是一个包名,同时也起到标识的作用,只有transact和onTransact方法中parcel对象的一致时才能成功调用。这也是为什么我们要求Server端和Client端aidl存放目录结构一致的原因,不一致的话这个自动生成DESCRIPTOR也会不一样;
2)第二个方法是系统为我们自己生成的,asInterface,这个方法非常重要,我看可以看到客户端是通过这个方法拿到一个“IPersonManager”的,到底拿到的是个啥呢,看源码:
public static com.amuro.aidl.IPersonManager asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof com.amuro.aidl.IPersonManager))) { return ((com.amuro.aidl.IPersonManager) iin); } return new com.amuro.aidl.IPersonManager.Stub.Proxy(obj); }
可以看到这里的逻辑是,asInterface根据传入的binder来判断当前是否是跨进程,如果跨进程则返回一个Proxy对象,如果不是跨进程则直接返回当前接口实现类(相当于调用本地接口),这个结论我相信很多人都知道,但源码里是怎么运行得到这个结果的呢?这里我们可以顺便看下queryLocalInterface这个方法的源码:
Binder中:
public IInterface queryLocalInterface(String descriptor) { if (mDescriptor.equals(descriptor)) { return mOwner; } return null; }
BinderProxy中:
public IInterface queryLocalInterface(String descriptor) { return null; }
这里也就能确定客户端拿到的一定是一个Proxy了,因为ServiceConnection返回的IBinder就是一个BinderProxy,queryLocalInterface后必然返回null,分支必然走到返回Proxy的那段代码。
3)asBinder是IInterface的回调方法,这里返回自己就行了。
4)下面是最重要的onTransact方法,这个方法会在客户端调用tansact方法时被回调,先放着,等讲Proxy之后再一起分析。
4.下面来看Proxy,这个类学过模式的童鞋一眼就能看出来是一个代理模式,里面放的IBinder就是客户端拿到的BinderProxy,主要看save方法:
@Override public void save(com.amuro.entity.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_save, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } }
首先准备好两个Parcel对象,一个存放传参,一个存放结果。
首先在data中写入DESCRIPTOR,确保对象正确,然后传参不为null的话,写入1并调用传参的writeToParcel方法来把具体类的数据写入data,最后就调用mRemote的transact方法,注意这个方法的第一个参数,其本质就是个方法flag,因为我们一个接口往往具有多个方法,所以需要flag作为唯一标识,这个flag在哪里被用到呢?没错就是Stub中的onTransact方法。
5.回去看Stub中的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_save: { data.enforceInterface(DESCRIPTOR); com.amuro.entity.Person _arg0; if ((0 != data.readInt())) { _arg0 = com.amuro.entity.Person.CREATOR.createFromParcel(data); } else { _arg0 = null; } this.save(_arg0); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); }
看到switch的case分支的时候,刚才那个方法的flag就对上号了吧。没错,最终就是这里被回调,TRANSACTION_save分支会首先根据proxy之前在data中写入的int值来判断传参是否为空,如果不是,会调用我们自己写Parcel类时里面的CREATOR的createFromParcel方法,其本质,就是生成了一个内存不一样但内部数据一样的Person对象,最后把这个对象传给了我们真正的save方法,执行回调。
其实中Proxy的transact到Stub的onTransact的过程中,android系统帮我们做了很多很复杂的事情,有兴趣的朋友可以追到native层去看transactNative方法,这里就不赘述了。
Part3:知道原理有啥用
最直观的作用就是可以让我们跳出工具,自己去实现一个aidl的接口,在例如SDK这样只允许使用代码的条件下。
其实最重要的是,在我们app运行的过程中会有大量的跨进程调用,无论四大组件甚至是window的操作,熟悉aidl源码可以让我们更好的理解这些源码的调用过程,理解操作系统的运行机理和设计思想,这都是一个优秀的程序员必须具备的素质。
相关文章推荐
- 深度理解Android InstantRun原理以及源码分析
- 深度理解Android InstantRun原理以及源码分析
- 深度理解Android InstantRun原理以及源码分析
- 深度理解Android InstantRun原理以及源码分析
- jQuery源码分析以及从jQuery对象创建的角度理解extend方法的原理
- 深度理解Android InstantRun原理以及源码分析
- 深度理解Android InstantRun原理以及源码分析
- 在Unix系统下编译Opencv源码生成android so库以及android-sdk
- AIDL自动生成源码分析以及根据自动生成的源码来自定义类实现IPC
- Javascript笔记:jQuery源码分析以及从jQuery对象创建的角度理解extend方法的原理
- Android系统原理与源码分析(1):利用Java反射技术阻止通过按钮关闭对话框
- Android系统原理与源码分析(1):利用Java反射技术阻止通过按钮关闭对话框
- struts2 文件上传 和部分源码解析,以及一般上传原理
- Android系统原理与源码分析(1):利用Java反射技术阻止通过按钮关闭对话框
- 深入理解i++、++i以及赋值操作符原理
- Struts2源码粗略分析四:理解xwork工作原理
- 动态生成表格以及增删行和列—基于prototype(附源码)
- 为了更加好的理解ANDROID 以及widows mobile系统,在这里重新整理了一下ARM的理解
- 深入理解数据库原理系列(1)---日志系统原理(摘自老杨)
- RedGlovePermission 权限管理系统源码(支持自定义权限、模块,支持角色权限,用户独立权限以及多角色权限)