多进程之进程间通信---aidl原理回顾分析整理

概述

最近在学习Replugin源码时,遇到了其中的多进程部分。由于太久没使用,有点生疏,刚好重拾总结下。
AIDL是一个缩写,全称是Android Interface Definition Language,也就是Android接口定义语言。设计这门语言的目的是为了实现进程间通信。当然对于上层间应用想实现简单的进程间通信还可以使用Message,当然底层还是通过aidl实现,但操作起来会简单挺多,参见 Android中的Service:Binder,Messenger,AIDL(2)

源码分析

编写如下的BookManager.aidl文件

interface BookManager  {
 //所有的返回值前都不需要加任何东西,不管是什么数据类型
// 带有特定parcelable的情况会涉及到tag及序列化和反序列化,但总的原理一样,本文只是为了通过源码复习原理,故简单处理
    String getName();
    void setName(String name);
}

gradle编译时通过compileXXXXAidltask后会在工程build\generated\source\aidl\{FlavorName}\{BuildType}\{package}下生成对一个BookManager.java文件如下:

public interface BookManager extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.liyuange.liyuange.BookManager
{
private static final java.lang.String DESCRIPTOR = "com.liyuange.liyuange.BookManager";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
 * Cast an IBinder object into an com.liyuange.liyuange.BookManager interface,
 * generating a proxy if needed.
 */
public static com.liyuange.liyuange.BookManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.liyuange.liyuange.BookManager))) {
return ((com.liyuange.liyuange.BookManager)iin);
}
return new com.liyuange.liyuange.BookManager.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_getName:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _result = this.getName();
reply.writeNoException();
reply.writeString(_result);
return true;
}
case TRANSACTION_setName:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
this.setName(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.liyuange.liyuange.BookManager
{
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 java.lang.String getName() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void setName(java.lang.String name) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(name);
mRemote.transact(Stub.TRANSACTION_setName, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_setName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
//所有的返回值前都不需要加任何东西,不管是什么数据类型

public java.lang.String getName() throws android.os.RemoteException;
public void setName(java.lang.String name) throws android.os.RemoteException;
}

生成了很多模板性代码,其实真正实行多进程通信的就是这个生成的java文件。就算我们不写AIDL文件,直接按照它生成的 .java 文件那样写一个 .java 文件出来,在服务端和客户端中也可以照常使用这个 .java 类来进行跨进程通信,所以其实aidl可以理解为,只是帮android开发者节省了写模板性代码而已。

接下来我们分别从服务端和客户端看下是怎么利用这个文件进行通信的。
服务端 即将 生成的IBinder通过onbind返回给客户端

public class AIDLService extends Service {

    //由AIDL文件生成的BookManager
    private final BookManager.Stub mBookManager = new BookManager.Stub() {
       @Override
        public String getName() throws RemoteException {//aidl所定义的接口方法
            return null;
        }

        @Override
        public void setName(String name) throws RemoteException {//aidl所定义的接口方法
          ...
        }
     
    };

  
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
             return mBookManager.asBinder();
    }
}

客户端 即将返回的binder 调用asInterface() 方法,获得我们自定义的接口,然后调用对应方法。这样即可实行进程间通信

public class AIDLActivity extends AppCompatActivity {

    //由AIDL文件生成的Java类
    private BookManager mBookManager = null;

    //标志当前与服务端连接状况的布尔值,false为未连接,true为连接中
    private boolean mBound = false;

  

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_aidl);
    }

      /**
     * 尝试与服务端建立连接
     */
    private void attemptToBindService() {
        Intent intent = new Intent();
        intent.setAction("XXX");//androidmenifest中设置的service的action
        intent.setPackage("XXXX");//androidmenifest中设置的service的服务端package
        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (!mBound) {
            attemptToBindService();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (mBound) {
            unbindService(mServiceConnection);
            mBound = false;
        }
    }

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBookManager = BookManager.Stub.asInterface(service);
            mBound = true;

            if (mBookManager != null) {
                try {
                    String name= mBookManager.getName();
                 Log.e(getLocalClassName(), "get book name for remote server, name = "+ name);
                   } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
                mBound = false;
        }
    };
}

重点从生成的代码中看下asInterface方法如下

public static com.liyuange.liyuange.BookManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.liyuange.liyuange.BookManager))) {
return ((com.liyuange.liyuange.BookManager)iin);
}
return new com.liyuange.liyuange.BookManager.Stub.Proxy(obj);
}

BookManager.Stub,根据生成的代码可看出集成了Binder同时实现了我们自定义的接口public static abstract class Stub extends android.os.Binder implements com.liyuange.liyuange.BookManager,当服务端与客户端同在一个进程时iin!=null即 返回的是服务端创建的BookManager.Stub本身,但我们这种情况是在2个进程,故返回的是com.liyuange.liyuange.BookManager.Stub.Proxy.根据生成的代码,该类代码如下

private static class Proxy implements com.liyuange.liyuange.BookManager//实现我们自定义接口
{
private android.os.IBinder mRemote; //我们传入的ibinder
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
//所有的返回值前都不需要加任何东西,不管是什么数据类型

@Override public java.lang.String getName() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
 mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
_reply.readException();
// 如果需要读取,服务端返回数据,从_reply中取出服务端执行方法的结果
_result = _reply.readString();
}
finally {
_reply.recycle();
_data.recycle();
}
//需要返回结果则将结果返回
return _result;
}
@Override public void setName(java.lang.String name) throws android.os.RemoteException
{
//很容易可以分析出来,_data用来存储流向服务端的数据流,
  //_reply用来存储服务端流回客户端的数据流
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
//如果需要传参参流向服务端,则写入
_data.writeString(name);
//调用 transact() 方法将方法id和两个 Parcel 容器传过去
mRemote.transact(Stub.TRANSACTION_setName, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_setName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}

大概流程大同小异,注释打在了代码上做分析。大致做的事就是,因为在不同进程不能直接读取对方内存对象,所以需要进行序列化和反序列化(通过Parcelable实现),通过binder进行传递通信。

  • 关于 _data 与 _reply 对象:一般来说,我们会将方法的传参的数据存入_data 中,而将方法的返回值的数据存入 _reply 中——在没涉及定向 tag 的情况下。如果涉及了定向 tag ,情况将会变得稍微复杂些,具体是怎么回事请参见这篇博文:你真的理解AIDL中的in,out,inout么?

  • 关于 Parcel :简单的来说,Parcel 是一个用来存放和读取数据的容器。我们可以用它来进行客户端和服务端之间的数据传输,当然,它能传输的只能是可序列化的数据。具体 Parcel 的使用方法和相关原理可以参见这篇文章:Android中Parcel的分析以及使用

  • 关于 transact() 方法:这是客户端和服务端通信的核心方法。调用这个方法之后,客户端将会挂起当前线程,等候服务端执行完相关任务后通知并接收返回的 _reply 数据流。关于这个方法的传参,这里有两点需要说明的地方:

    • 方法 ID :transact() 方法的第一个参数是一个方法 ID ,这个是客户端与服务端约定好的给方法的编码,彼此一一对应。在AIDL文件转化为 .java 文件的时候,系统将会自动给AIDL文件里面的每一个方法自动分配一个方法 ID。本例子中生成ID如下
static final int TRANSACTION_getName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_setName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
*   第四个参数:transact() 方法的第四个参数是一个 int 值,它的作用是设置进行 IPC 的模式,为 0 表示数据可以双向流通,即 _reply 流可以正常的携带数据回来,如果为 1 的话那么数据将只能单向流通,从服务端回来的 _reply 流将不携带任何数据。
    注:AIDL生成的 .java 文件的这个参数均为 0。

接下来看下客户端是如何响应的?调用 mRemote.transact后,服务端就会调用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_getName:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _result = this.getName();
reply.writeNoException();
//将方法执行的结果写入 reply 
reply.writeString(_result);
return true;
}
case TRANSACTION_setName:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
//调用服务端中`BookManager.Stub`本身setName方法, 即我们服务端service中`onbind`方法返回的的`BookManager.Stub`子类方法。
this.setName(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}

直接通过客户端调用的方法传入ID 进行 switch 判断是掉用户服务端哪个方法,然后调用我们服务端service中onbind方法返回的的BookManager.Stub子类方法。

我们没有看到关于将 reply 回传到客户端的相关代码,也没有看到它将相关参数传向服务端的相关代码——它只是把这些参数都传入了一个方法,其中过程同样是对我们隐藏的——服务端也同样,在执行完 return true 之后系统将会把 reply 流传回客户端,具体是怎么做的就在于IBinder的源码了。如果想深入研究IBinder机制,可参考老罗及其它大神对该机制更底层的分析。
通过隐藏了这些细节,我们在 transact() 与 onTransact() 之间的调用以及数据传送看起来就像是发生在同一个进程甚至同一个类里面一样。我们的操作就像是在一条直线上面走,根本感受不出来其中原来有过曲折——也许这套机制在设计之初,就是为了达到这样的目的。

总结

一图以辟之


2491431-425990373820aaf6.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 193,968评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,682评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,254评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,074评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,964评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,055评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,484评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,170评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,433评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,512评论 2 308
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,296评论 1 325
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,184评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,545评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,150评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,437评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,630评论 2 335

推荐阅读更多精彩内容