AIDL学习

AIDL

AIDL的核心有两点

  • AIDL是一种跨进程通讯方式
    这种方式是基于Binder机制来进行的,Binder本质上是基于C/S架构,Service提供服务(方法),Client使用服务(方法调用)
  • AIDL本质上是一种代码生成的方式
    从这一点上来说它和apt代码生成方式没有本质区别,AIDL的特殊之处在于它生成的代码是Binder进程间通讯的最外层封装。

AIDL特点

AIDL的语法和Java基本类似,但是也有不同点,主要在以下几点

  • 支持的类型
  • Parcelable和AIDL接口都需要导包
  • 没有public等关键字
  • 所有非基本类型的都需要添加in、out、inout关键字,用于指明数据流向,默认为in

支持的类型

默认情况下,AIDL 支持下列数据类型:

  • Java 编程语言中的所有原语类型(如 int、long、char、boolean 等等)
  • String
  • CharSequence
  • Parcelable Parcelable必须显示加入一个 import 语句,即使这些类型是在与接口相同的软件包中定义。
  • List List 中的所有元素都必须是列表中支持的数据类型、其他 AIDL 生成的接口或Parcelable类型。 可选择将 List 用作“通用”类(例如,List<String>)。另一端实际接收的具体类始终是 ArrayList,但生成的方法使用的是 List 接口。
  • Map
    Map 中的所有元素都必须是列表中支持的数据类型、其他 AIDL 生成的接口或Parcelable类型。 不支持通用 Map(如 Map<String,Integer> 形式的 Map)。 另一端实际接收的具体类始终是 HashMap,但生成的方法使用的是 Map 接口。
  • AIDL对应的接口

AIDL生成文件解析

AIDL最终会生成一个继承自IInterface的接口,先简单看下这个类的结构。这里我做了一些结构优化,看上去更方便一些

public interface IMusicControler extends IInterface{

    public static abstract class Stub extends Binder implements IMusicControler{
        private static final String DESCRIPTOR = "site.yihome.ipc.IMusicControler";

        public static IMusicControler asInterface(IBinder obj){
           ......
        }

        @Override
        protected boolean onTransact(int code, Parcel data,  Parcel reply, int flags) throws RemoteException {
            ......
            return super.onTransact(code, data, reply, flags);
        }

        public static class Proxy implements IMusicControler{
            private IBinder mRemote;
            Proxy(IBinder remote) {
                mRemote = remote;
            }

            @Override
            public IBinder asBinder()
            {
                return mRemote;
            }

            @Override
            public void startMusic(Music music) throws RemoteException {
                ......
            }

            @Override
            public void stopMusic() throws RemoteException {
                ......
            }

            @Override
            public void setMusicStateCallBack(IMusicStateCallBack cb) throws RemoteException {
                ......
            }
        }

    }

    public void startMusic(Music music) throws RemoteException;

    public void stopMusic() throws RemoteException;

    public void setMusicStateCallBack(IMusicStateCallBack cb) throws RemoteException;
}

从上面的代码中,我们可以明显的看到IMusicControler中以包名定义一个标示DESCRIPTOR,这个标示贯穿整个通讯过程,同时定义一个三层结构

  • 最外层就是我们AIDL中定义的最原始的接口类型IMusicControler,和它定义的方法
  • 第二层是一个抽象类Stub,他是三层中唯一的Binder对象,用于和Binder交互完成真正的进程间通讯
  • 第三层是一个IMusicControler的实现类Proxy,和名字一样是Service端的代理类,进程间通讯时client首先调用的就是这个类的方法,然后在通过Binder调用远程对象。

如下图所示,Stub和Proxy本质是就是Binder两端的数据封装和解析层


image

Proxy主要完成Client调用时参数的转化,它把方法的参数转换成Parcel类型,用于Binder通信时传输数据,同时也把Binder传回的Parcel类型的返回值转换成对应我们需要的类型。

Stub和Proxy类似,但是Stub主要完成的是Server端的数据转化。具体的代码细节我们借助下面AIDL调用的全过程来讲解

AIDL调用全过程

Client调用过程

AIDL的调用过程,我们从ServiceConnectiononServiceConnected方法开始

override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
    musicControler = IMusicControler.Stub.asInterface(service)
    ......
}

一般来说,bindService后在onServiceConnected的回掉中,我们都会通过Stub的asInterface方法获取可以操作的IInterface对象,asInterface具体细节如下

public static IMusicControler asInterface(IBinder obj){
    if(obj==null){
        return null;
    }
    IInterface iIn = obj.queryLocalInterface(DESCRIPTOR);
    if(iIn!=null&&(iIn instanceof IMusicControler)){
        return (IMusicControler) iIn;
    }
    return new Proxy(obj);
}

asInterface中,首先通过DESCRIPTOR去查询本地接口(即非跨进程),如果是进程间通讯则会用这个IBinder对象去new一个Proxy对象。也就是说我们在Client中拿到的IMusicControler事实上就是Proxy。

Proxy(IBinder remote) {
    mRemote = remote;
}

如果这时调用startMusic(Music music)去调用远程服务,事实上是调用的Proxy的方法

public void startMusic(Music music) throws RemoteException {
    Parcel _data = Parcel.obtain();
    Parcel _reply = Parcel.obtain();

    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        if ((music!=null)) {
            _data.writeInt(1);
            music.writeToParcel(_data, 0);
        }
        else {
            _data.writeInt(0);
        }
        mRemote.transact(TRANSACTION_startMusic, _data, _reply, 0);
        _reply.readException();
    }
    finally {
        _reply.recycle();
        _data.recycle();
    }
}

可以看到Proxy事实上只是把参数封装到了Parcel对象中,同时会去获取reply中的数据。同时会远程服务IBinder的transact,这个IBinder事实上是一个BinderProxy对象。这里就会调用BinderProxy的transact方法

public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
    Binder.checkParcel(this, code, data, "Unreasonably large binder buffer");
    ......
    try {
        return transactNative(code, data, reply, flags);
    } finally {
        ......
    }
}

可以看到这个transact方法最终会调用native方法通过Binder完成真正的跨进程。Client的流程到此结束。

Server端调用过程

在IPC的Service中,我们需要在onBind方法中需要返回一个Stub的实现类

return new IMusicControler.Stub() {......};

这个对象就是Server端的入口类,Client中transactNative方法最终会调用Stub的onTransact这个由它自己实现的方法

 protected boolean onTransact(int code, Parcel data,  Parcel reply, int flags) throws RemoteException {
    switch (code){
        case INTERFACE_TRANSACTION:
            reply.writeString(DESCRIPTOR);
            return true;
        case TRANSACTION_startMusic:
            data.enforceInterface(DESCRIPTOR);
            Music music;
            if ((0!=data.readInt())) {
                music = Music.CREATOR.createFromParcel(data);
            }else {
                music = null;
            }
            this.startMusic(music);
            reply.writeNoException();
            break;
        ......
    }
    return super.onTransact(code, data, reply, flags);
}

这个方法会对在transact传入的code进行不同的处理,同时从Parcel中获取实际参数,最后调用Service中的对应方法。

AIDL异步调用

默认情况下AIDL的调用是同步的,即必须等到Server端执行完成后才会返回。如何实现异步调用呢,可以看到在前面的transact中有四个参数,我们用了其中三个,而第四个参数flag正是用来标示调用方式的,默认0标示同步调用,如果需要异步调用,则需要把flag设置为FLAG_ONEWAY(这个过程想要用AIDL文件进行的话只需要在对应的方法前添加oneway关键字)

但是通过异步调用,我们是获取不到返回值的,因此需要接口回掉,但是普通的接口回掉无法满足跨进程通讯的需求,这种情况怎么处理呢?

还是Binder,前面一般情况下Service是充当Server端提供方法,Activity作为Client调用方法,Binder完成Client对Server的调用,当需要接口回掉时,两者身份就会反转,Activity作为Service提供IBinder对象,供Service调用,而这个IBinder对象则调用Service的方法通过参数传递(前面也说过,AIDL是支持这种IBinder对象传递的)

Binder的回掉方法是执行在子线程中的,这一点可能和Binder的机制有关,有待进一步研究

RemoteCallbackList

上面这一种异步回掉只是一种简单的情况,当一个Service需要对很多Client进行回掉时,就需要对注册的回掉进行一个系统的管理了,RemoteCallbackList正是用来处理这种情况的,它内部使用了一个ArrayMap来存储所有的回掉,同时会在需要的时候进行调用,核心的代码如下

public class RemoteCallbackList<E extends IInterface> {
    ArrayMap<IBinder, Callback> mCallbacks
        = new ArrayMap<IBinder, Callback>();
    public boolean register(E callback) {
        return register(callback, null);
    }
    
    public boolean register(E callback, Object cookie) {
        synchronized (mCallbacks) {
            if (mKilled) {
                return false;
            }
            // Flag unusual case that could be caused by a leak. b/36778087
            logExcessiveCallbacks();
            IBinder binder = callback.asBinder();
            try {
                Callback cb = new Callback(callback, cookie);
                binder.linkToDeath(cb, 0);
                mCallbacks.put(binder, cb);
                return true;
            } catch (RemoteException e) {
                return false;
            }
        }
    }
    public boolean unregister(E callback) {
        synchronized (mCallbacks) {
            Callback cb = mCallbacks.remove(callback.asBinder());
            if (cb != null) {
                cb.mCallback.asBinder().unlinkToDeath(cb, 0);
                return true;
            }
            return false;
        }
    }
    
     public void broadcast(Consumer<E> action) {
        int itemCount = beginBroadcast();
        try {
            for (int i = 0; i < itemCount; i++) {
                action.accept(getBroadcastItem(i));
            }
        } finally {
            finishBroadcast();
        }
    }
}

这个类并不复杂,它主要封装了对callback常见的操作,同时帮助我们做了很多线程同步上的工作。

下步目标

了解Binder通讯原理

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

推荐阅读更多精彩内容

  • 原文链接: http://weishu.me/2016/01/12/binder-index-for-newer/...
    miniminiming阅读 720评论 1 6
  • 毫不夸张地说,Binder是Android系统中最重要的特性之一;正如其名“粘合剂”所喻,它是系统间各个组件的桥梁...
    weishu阅读 17,811评论 29 246
  • Jianwei's blog 首页 分类 关于 归档 标签 巧用Android多进程,微信,微博等主流App都在用...
    justCode_阅读 5,893评论 1 23
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,596评论 18 139
  • 原文:http://weishu.me/2016/01/12/binder-index-for-newer/ 要点...
    指尖流逝的青春阅读 2,603评论 0 13