Binder框架

Binder是一种架构,分为三个部分组成:

  • 服务端接口
  • Binder驱动
  • 客户端接口

对于服务端,一个Binder服务端就是一个Binder类的对象,该对象一旦创建,内部就启动一个隐藏线程。该线程接下来会接收Binder驱动发送过来的消息,收到消息后,会执行到Binder对象中的onTransact方法,根据按照不同的参数执行不同的服务端方法。

服务端重载onTransact方法主要是把该方法的参数转化为服务端方法的参数,而该方法的参数来源于客户端调用transact方法时的输入。所以,如果transact有固有格式的输入,那么onTransact就会有固定格式的输出。

对于Binder驱动,任意一个服务端Binder对象被创建时,同时会在Binder驱动中创建一个mRemote对象,该对象的类型也是Binder。客户端调用远程服务时,调用的其实是mRemote对象。在Binder驱动中,mRemote被重载,主要做以下事情:

  • 以线程间消息通信的模式,向服务端发送客户端传过来的参数
  • 挂起当前线程(客户端线程),等待服务端线程执行完特定的服务端函数后通知
  • 接收服务端线程的通知,然后继续执行客户端线程,返回客户端代码区

从上面的流程来看,对于客户端似乎是直接调用到了服务端的Binder,但实际上是通过Binder驱动进行了中转,即存在两个Binder对象。

服务端设计

对于服务端,需要做的事情是重写onTransact方法:

protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
}

其中的code就是与客户端约定好的调用哪个方法的标识。比如约定当code的值为100时,调用服务端的play方法;datareplay分别是传递给方法的参数和方法的返回值,需要根据具体方法读取和写入。

客户端设计

对于客户端,第一步应该是从Binder驱动中获得与远程服务端对应的mRemote引用。比如在Activity中通过调用bindService,如果绑定服务成功,可以获得一个IBinder对象,这个就是Binder驱动中的mRemote。调用mRemote引用的transact方法,Binder驱动会挂起调用的当前客户端线程,然后向远程服务发送一个消息,该消息中包含了客户端传递的数据。服务端调用完具体的方法后将返回值放入reply(如果有返回值的话),并向Binder发送一个notify的消息,使得客户端线程重新被唤醒,完成调用。

AIDL生成的代码分析

从上文的分析可以看出,其实对于远程服务端的调用,都是基于约定的。比如对于codedatareply我们需要明确地赋予其意义。

  • code的值对应哪个服务端方法,相应的data中有哪些值、需要往reply中写入哪些值
  • datareplay的数据写入顺序是怎样的

而AIDL工具就是帮我们处理这些繁琐事情的。以一个简单的AIDL分析:

// 文件名:IRemoteService.aidl
package com.demo;

interface IRemoteService {
    void sayHello();

    String getValue();
}

看起来和Java的接口定义差不多,只不过文件的拓展名是.aidl不是.java

通过AIDL工具会在com.demo包下生成一个IRemoteService.java文件,这个才是给程序使用的。生成的文件主要包括三个部分的内容:

  • 一个IRemoteService接口
  • 一个IRemoteService.Stub的抽象类
  • IRemoteService.Stub中定义一个Proxy的内部类

下面具体分析:

// IRemoteService接口部分
package com.demo;

public interface IRemoteService extends android.os.IInterface {
    public void sayHello() throws android.os.RemoteException;

    public java.lang.String getValue() throws android.os.RemoteException;
}

这部分没什么好说的,就是将AIDL中的方法转化为Java接口的方法。

public static abstract class Stub extends android.os.Binder implements com.demo.IRemoteService {
    // 规定code的值
    static final int TRANSACTION_sayHello = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_getValue = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    private static final java.lang.String DESCRIPTOR = "com.demo.IRemoteService";

    /**
     * Construct the stub at attach it to the interface.
     */
    public Stub() {
        this.attachInterface(this, DESCRIPTOR);
    }

    /**
     * Cast an IBinder object into an com.demo.IRemoteService interface,
     * generating a proxy if needed.
     */
    public static com.demo.IRemoteService asInterface(android.os.IBinder obj) {
        if ((obj == null)) {
            return null;
        }
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin != null) && (iin instanceof com.demo.IRemoteService))) {
            return ((com.demo.IRemoteService) iin);
        }
        return new com.demo.IRemoteService.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_sayHello: {
                data.enforceInterface(DESCRIPTOR);
                this.sayHello();
                reply.writeNoException();
                return true;
            }
            case TRANSACTION_getValue: {
                data.enforceInterface(DESCRIPTOR);
                java.lang.String _result = this.getValue();
                reply.writeNoException();
                reply.writeString(_result);
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }

    private static class Proxy implements com.demo.IRemoteService {
        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 void sayHello() throws android.os.RemoteException {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                mRemote.transact(Stub.TRANSACTION_sayHello, _data, _reply, 0);
                _reply.readException();
            } finally {
                _reply.recycle();
                _data.recycle();
            }
        }

        @Override
        public java.lang.String getValue() 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_getValue, _data, _reply, 0);
                _reply.readException();
                _result = _reply.readString();
            } finally {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        }
    }
}

代码看起来很长,但职责还是很明确的,一开始就规定了几个对应的int值,通过该值去映射到具体的服务端方法。

Stub类继承了Binder,重写了onTransact方法,帮我们完成了code值到对应方法的映射以及方法参数的读取与返回值的写入。Stubimplements了IRemoteService,但并没有实现,显然这部分是让子类去实现的。

Proxy类实现了IRemoteService中的两个方法,方法中实现的数据写入和StubonTransact的数据读取是对应的,解决了数据顺序的问题。

对于服务端,我们只需要继承Stub,然后实现IRemoteService接口中的方法;对于客户端,我们拿到IBinder后,可以通过StubasInterface方法,得到一个接口的实现类,其实也就是Proxy的实例,之后就可以通过这个实例直接调用IRemoteService的方法了。

小结:通过AIDL,帮我们生成的代码,我们就不用考虑transactonTransact该如何实现,从而可以把精力放在具体的接口逻辑实现上。

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

推荐阅读更多精彩内容