Android 中的 Binder

Binder简析

直观来说,Binder 是 Android 中的一个类,是继承了 IBinder 接口;从 IPC 角度考虑 Binder 是进程间通信的一种方式;从 Framework 层,Binder 是 连接 ServiceManager 和 各种 Manager(AM,WM) 以及各种 ManagerService 的桥梁;从应用层来说,Binder 是客户端和服务器端进行通信的媒介,当 bindService 的时候服务器端会返回一个包含了服务端业务调用的 Binder 对象,客户端就可以获取服务端提供的数据或服务。

应用层的使用 AIDL

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: D:\\MyApplication\\ServiceDemo\\app\\src\\main\\aidl\\com\\renxl\\servicedemo\\aidl\\MyWorker.aidl
 */
package com.renxl.servicedemo.aidl;
// Declare any non-default types here with import statements

public interface MyWorker extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.renxl.servicedemo.aidl.MyWorker {
        private static final java.lang.String DESCRIPTOR = "com.renxl.servicedemo.aidl.MyWorker";

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

        /**
         * Cast an IBinder object into an com.renxl.servicedemo.aidl.MyWorker interface,
         * generating a proxy if needed.
         */
        public static com.renxl.servicedemo.aidl.MyWorker asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.renxl.servicedemo.aidl.MyWorker))) {
                return ((com.renxl.servicedemo.aidl.MyWorker) iin);
            }
            return new com.renxl.servicedemo.aidl.MyWorker.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_doWork: {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    int _result = this.doWork(_arg0);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.renxl.servicedemo.aidl.MyWorker {
            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 doWork(java.lang.String str) 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.writeString(str);
                    mRemote.transact(Stub.TRANSACTION_doWork, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        static final int TRANSACTION_doWork = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }

    public int doWork(java.lang.String str) throws android.os.RemoteException;
}

介绍

我们自定义的 AIDL 数据类继承 IInterface 接口,因为可以在 Binder 中传输的数据接口都需要继承 IInterface 并实现其 asBinder 方法

Stub 是 AIDL 接口中的抽象类,Stub 类继承了 Binder 和我们需要在进程中传递的数据类型接口,Stub 类中还有 Proxy 子类,该类也继承了需要在进程间传递的数据类型

服务端

首先看客户端绑定时服务端的返回的客户端所需的 AIDL 接口类型对象,服务端返回的 Binder 对象继承的是 AIDL 中内部类 Stub 类,因为 Java 中多态的存在,我们需要 IBinder 对象时返回 IBinder 的子类即可。说一下为什么 服务端 返回的是 AIDL 中的内部类 Stub 类的对象的子类,因为,该抽象类的子类实现了我们定义的 .aidl 接口中的方法,所以返回的应该是这个 Stub 类的子类。Stub 子类实现了 .aidl 中声明的方法的抽象方法。

服务器端 Stub 的子类实例化时,因为同时是 Binder 的子类,需要调用 Binder 的 attachInterface 方法,将本身和 IInterface 的描述字符串添加到 Binder 类中,Binder 类中会根据描述字符串匹配相应的 IInterface 对象

客户端

根据绑定模式的 Service 绑定过程,最终服务端返回的 Binder 对象会传递到客户端的 ServiceConnection 的 onServiceConnected 方法中。

客户端得到服务端返回的 Binder 后,会通过自定义 AIDL 接口类的 asInterface 将该 Binder 转化为客户端需要的接口类型对象,asInterface 方法会从 Binder 类中根据描述字符串寻找相应的 IInterface,客户端和服务端不在统一进程时,由于 Binder 类的加载也是分开的,所以在客户端的 Binder 中不会找到匹配的 IInterface

在同进程请求时 asInterface 返回的是 Stub 的实现类的对象,也就是直接返回的服务端返回的继承了 Stub 类的对象。同进程不用考虑 Binder 传输数据,所以直接调用 Stub 实现类的各种方法即可实现客户端调用服务端的数据和服务的功能。

在跨进程调用时,由于客户端 Binder 中不能找到对应的 IInterface,所以 asInterface 中返回的是根据 Stub 的子类对象构造的 Stub.Proxy 类的对象。Stub.Proxy 类中调用服务器端方法时会将参数和返回值都使用序列化处理,经过跨进程通信后最终传递到客户端。从而完成客户端调研服务端服务和数据的功能。

为什么服务器端返回的 Binder 是客户端需要的类对象,但是跨进程时需要转换为 Proxy

因为不同进程间的对象不能相互调用,虽然绑定玩成时客户端拿到了服务器端对象的引用但是并不能直接操作该对象,所以需要通过 Proxy 来代理,Proxy 类的方法执行时会在 Native 层通过系统进程的协助调用服务端对象来执行任务,所以跨进程时客户端拿到的 Binder 对象不可以直接强制转型为需要的对象。

进程间通信

跨进程调用时客户端的调用逻辑:上面说了跨进程时得到的AIDL接口数据类型是 Stub.Proxy ,所以直接分析 Stub.Proxy 类中的方法调用。Stub.Proxy 中调用方法并不会直接调用服务端的方法,会先创建 Parcel 类型的输入对象和输出对象以及返回值。如果方法有参数就将参数写入输入型 Parcel 对象中,这个过程还是在客户端,接下来会将序列化后的参数,序列化的返回值,以及表示客户端调用的是哪个方法的 int 值传入 Binder 的 transact 方法,客户端线程挂起。

Binder 的 transact 方法中会调用 Native 层的方法,Native 层会根据 IInterface 描述字符串从系统中所有进程的 Binder 中找到客户端 Proxy 对应的服务器端的 IInterface ,然后通过其 asBinder 方法得到服务器端的 Binder 对象也就是 Stub 对象,在通过调用其 onTransact 方法,通过系统进程将客户端跟服务端连接,并将客户端线程挂起

说一下这个 Binder , 创建 Proxy 时构造方法传入的 IBinder 也就是客户端绑定时得到的 Binder 类对象,通过系统进程调用服务端的 onTransact 方法,执行也就切换到了服务端,服务端也就拿到了序列化后的参数,返回值以及代表哪个方法的 int 值。transact 方法中会把存储参数和返回值的可序列化数据传递到 onTransact 方法中

onTransact 方法中会将序列化的参数反序列化,再根据代表客户端调用哪个方法的 int 值,将参数传入相应方法得到返回值。 这里需要注意,传入相应的方法,其实是调用的 Stub 类的子类的方法,也就是服务端实现了 Stub 类中抽象方法的那个类的对象的方法,这样最终任务的执行实在 Stub 中执行的,并不是在 Proxy 类中。 再将返回值进行序列化后写入参数中传来的输出型 Parcel 中,这时候,注意,服务端就执行结束了。

服务端执行结束之后,系统进程会返回一个 Boolean 值表示是否成功调用,系统进程收到这个返回值时后将该返回值和及 将输出型的 Pacel 返回到客户端进程,客户端进程的 Binder 线程会被唤醒,客户端线程唤醒后,会根据是否执行成功的返回值接收执行结果

注:

  1. 客户端发起请求时当前线程就会挂起,所以要是执行任务耗时则需要客户端在子线程中调用

  2. 服务端相应请求执行过程在 Binder 的线程池中,即已经在子线程中了,所有 Binder 中执行过程无需再开启子线程

Binder 的死亡代理

通过 linkToDeath 和 unlinkToDeath 方法可以为 Binder 绑定和解绑死亡代理
死亡代理是一个 DeathRecipient 类,内部又一个 binderDied 方法,我们需要实现这个方法,在 Binder 死亡的时候,系统就会回调 binderDied 方法,我们就可以移除之前绑定的 binder 代理并重新绑定完成服务。

总结

  • Worker 需要传递的对象需要实现的接口,继承 IInterface 接口

  • Stub 服务器端需要实现的对象的父类继承 Binder 类

  • Proxy 代理类,客户端调用服务器端对象时使用的类,继承 Binder

当 Stub 的子类实例化时,Binder 中会存起来,并将该 Binder 返回到客户端,客户端拿到这个 Binder 会根据是否是同进程操作,不同进程时会通过 Binder 构造

AIDL原理

Binder 在 AIDL 中应用,服务器端将客户端需要的对象转换成一个 Binder 对象

AIDL 原理,即客户端需要一个服务器端的对象,客户端和服务器端不在同一进程,服务端返回一个 Binder 对象给客户端,客户端根据将 Binder 对象转换成需要的对象的 Proxy 代理,需要调用服务端执行任务时就调用该 Proxy 代理的方法,Proxy 代理的方法执行时会通过 Native 层通过系统进程找到其他进程中匹配的 Stub 子类,并调用其执行任务方法,从而实现客户端和服务端的进程间通信。

工作过程

首先,我们服务器端有一个可以执行任务的对象需要传递到客户端,然而不同进程之间不可以直接调用对象的方法,从而通过 Binder 来实现进程间的通信。

  1. aidl文件 定义 .aidl 类型文件,其中声明要实现的功能方法

  2. 定义接口 首先,定义 AIDL 类型的接口,也就是需要传递的对象类型接口,继承 IInterface ,并在其中根据 aidl 文件的内容定义了这个类的可以实现功能的抽象方法

  3. 接口中定义内部类 Stub 在接口中定义 Stub 类,该类实现了我们需要的接口,并继承了 Binder

  4. 服务端实现接口 服务端要定义类实现 Stub 类,然后完成我们自己定义的接口的抽象方法

  5. Stub 类的实现 Stub 类中定义了 asInterface 可以返回一个我们需要的类对象,如果是同进程则返回本身,如果是非同进程则返回 Stub 的内部类 Proxy 代理类对象

  6. 客户端请求 客户端绑定时服务器返回服务端实现的接口类型的对象,客户端得到的 Binder 对象就是 Stub 对象,客户端调用 Stub 对象的 asInterface 方法得到需要的类型对象

  7. 同进程对象工作 客户端得到需要的对象后,调用其方法,如果是同进程,参数可以直接传递,同时 asInterface 得到的就是服务端实现的 Stub 对象,可以直接执行方法返回结果

  8. 跨进程对象工作 如果是跨进程时,asInterface 得到的就是 Stub 的内部类 Proxy 类的对象,该对象继承了我们需要的接口,并实现了其抽象方法,Proxu 的构造需要一个 Stub 类对象

  9. Proxy 的工作过程 其实现的抽象方法执行时,会先将方法参数序列化,再调用 Stub 方法的 transact 方法将序列化后的参数,方法 int 类型的标识传入,线程挂起,服务端在 Binder 进程中执行任务后将序列化后的结果返回。Proxy 的工作方法中,得到结果后,将序列化后的结果反序列化成方法的返回值类型,返回。

  10. transact 方法 transact 方法最后会调用 Stub 的 onTransact 方法

  11. Stub 的 onTransact 方法 onTransact 方法执行在服务器进程的 Binder 线程池中,根据方法标识,将序列化后的参数反序列化,在服务端执行,得到返回值后序列化,最后将结果返回,传回到客户端。客户端处理后完成。

Binder 机制在系统中的应用

Android 系统启动之后会启动很多的 Service,例如 ActivityManagerService 等,在开发中我们可能需要调用这些 Service 的方法,但是由于是不同进程是不可以直接调用的,这时候通过 ServiceManager 我们可以 Binder 机制跨进程拿到 AMS 的代理 ActivityManagerProxy ,通过代理即可实现调用 AMS 的方法。

上面提到了 ServiceManager,那 ServiceManager 又是怎么工作的呢,ServiceManager 其实是 ServiceManagerProxy 的代理,ServiceManagerProxy 是 ServiceManagerNative 的代理,ServiceManagerNative 继承了 IServiceManager ,我们看出来了,原来 ServiceManager 也是通过 Binder 实现跨进程的

我们在应用中使用 ServiceManager 在获取系统服务时,如果 ServiceManager 代理的 ServiceManagerNative 没有初始化,则会通过 Natice 层来通过 Binder 机制完成 ServiceManagerNative 初始化,此时的服务端是系统进程,之后我们就可以使用 ServiceManager 来协助我们获取其他的系统服务了。

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

推荐阅读更多精彩内容