Android Binder 机制

Android Binder机制的文章非常多,这篇文章主要是理一下我对Binder的理解。本文不是一篇介绍Binder的文章,也不是一篇探讨Binder实现的文章。
本文会以AndroidStudio根据aidl接口自动产生的java文件来看Binder,进而来理解Binder机制

其实Android的Binder机制类似于:RPC(远程过程调用)。如果你理解它,相信Binder机制就更容易理解了。

首先我们使用AndroidStudio来定义一个aidl接口:

interface IUserManager {
    int getUserAge(in String userName);
}

然后我们来直接看一个由AndroidStudio根据自定义的aidl接口IUserManager产生的IUserManager.java文件。

这个文件我们来分3个部分看:

IUserManager接口结构

public interface IUserManager extends android.os.IInterface {

    public static abstract class Stub extends android.os.Binder implements com.susion.demo.aidl.IUserManager {..}

    public int getUserAge(java.lang.String userName) throws android.os.RemoteException;
}

这个接口的结构还是很简单的:

  1. 它继承自android.os.IInterface
  2. 定义了一个待实现的方法int getUserAge()
  3. 定义了一个Stub类。这个类继承自Binder,并实现了IUserManager接口。

int getUserAge()这个方法就是我们IUserManager接口的方法。而android.os.IInterface是什么呢?先看一下它在源码中的定义:

/**
 * Base class for Binder interfaces.  When defining a new interface,
 * you must derive it from IInterface.
 */
public interface IInterface
{
    /**
     * Retrieve the Binder object associated with this interface.
     * You must use this instead of a plain cast, so that proxy objects
     * can return the correct result.
     */
    public IBinder asBinder(); //IBinder是Binder的抽象接口
}

即他是所有Binder都要实现的接口, 为什么呢?举一个我们都熟悉的场景 :

比如ApplicationThreadActivityManagerService(运行在服务端进程)就可以通过它来调用我们客户端的方法。我们会把这些方法抽象为一个接口(IApplicationThread),这个接口可以理解为我们告诉服务端,你可以对客户端执行哪些操作。

我们还知道ApplicationThread其实他就是一个Binder。所以这两者一结合就可以这么说ApplicationThread: 客户端提供给服务端一个Binder,通过这个Binder服务端可以对客户端做一些操作,这些操作具体定义在IApplicationThread接口中。

我们称IApplicationThreadApplicationThread这个Binder的功能。 所以Binder除了可以理解为系统给我们提供的一个跨进程通信的对象。 我们在用Binder通信时,还可以说Binder是一个具有某些功能的一个对象。

那么怎么表示Binder有功能呢? 即要继承IInterfaceIInterface可以表示Binder有功能, 不然你想一个,那么多Binder都只实现自己的接口, 那么系统层就不好操作了,它总不能向下强转为Binder吧,所以Android定义了一个更高层级的接口IInterface。描述Binder功能的接口必须继承自这个接口。

重点: Binder、Binder的功能(IApplicationThread)、IInterface它们都在同一个对象上 -> ApplicationThread

Stub

它是IUserManager的内部静态类,看一下它的具体声明:

static abstract class Stub extends android.os.Binder implements com.susion.demo.aidl.IUserManager 

即它是一个Binder,可以用来跨进程通信。它具有IUserManager定义的功能。

看一下它的具体结构:

    public static abstract class Stub extends android.os.Binder implements com.susion.demo.aidl.IUserManager {

        private static final java.lang.String DESCRIPTOR = "com.susion.demo.aidl.IUserManager";

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

        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

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

        @Override
        public android.os.IBinder asBinder() {retun this;}

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {...}

        private static class Proxy implements com.susion.demo.aidl.IUserManager {...}

    }

我们还是一个一个的看一下:

DESCRIPTOR

基于我们前面的解释,我们知道在跨进程通信中Binder对象具有某种功能->IInterface。但是Binder通信机制中那么多Binder都有IInterface。那么系统怎么识别哪个Binder是哪个Binder呢?所以IInterface只是一个能力的抽象,DESCRIPTOR就是来表示具体是哪一个功能IInterface

TRANSACTION_userCount

即功能下的哪个操作。

Stub构造函数

    public Stub() {
        this.attachInterface(this, DESCRIPTOR);
    }

即一个Stub(Binder)在构造的时候,就标识好了自己的具体功能IInterface(IUserManager)。来看一下attachInterface(this, DESCRIPTOR)做了什么:

//Binder.java
public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) {
    mOwner = owner;  
    mDescriptor = descriptor;
}

即,Binder在内部会用IInterface来保存自己的功能。和这个功能更对应的唯一描述descriptor,方便在通信的时候寻找。

asBinder()

自己返回自己,因为自己本身就是个Binder呀。

onTransact()

当其他进程想跨进程调用我这个Binder的功能时,必须通过这个方法来沟通。这个方法我们最后再来看。

asInterface(android.os.IBinder obj)

即接收一个IBinder(这个IBinder是系统传入的), 把这个IBinder转化为它所具有功能接口。其实这里就是Binder跨进程通信的一个核心 。那怎么转化的呢?

  • 调用者和Binder对象位于同一个进程

那么系统就会直接传给你在这个进程创建的Stub(Binder)。所以 obj.queryLocalInterface(DESCRIPTOR):

public  IInterface queryLocalInterface(String descriptor) {
    if (mDescriptor.equals(descriptor)) {
        return mOwner;
    }
    return null;
}

即如果参数descriptor和这个Binder的功能唯一描述相同。就会返回Binder的功能mOwner

  • 调用者和Binder对象不在同一个进程

这时系统实际传的是一个BinderProxy, 你可以理解为它是另一个进程中的Binder的替身。我们就可以把它当成另一个进程的Binder。我们看一下BinderProxyqueryLocalInterface()方法:

/**
* Retrieve a local interface - always null in case of a proxy
*/
public IInterface queryLocalInterface(String descriptor) {
    return null;
}

所以此时asInterface()返回的是: IUserManager.Stub.Proxy(obj), 即代理对象,它代理了BinderProxy

IUserManager.Stub.Proxy

它是Stub的静态内部类,如果调用者和Binder不在同一个进程的话,调用者拿到的实际是它:

    private static class Proxy implements com.didi.virtualapk.demo.aidl.IUserManager {
        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 getUserAge(java.lang.String userName) 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(userName);
                mRemote.transact(Stub.TRANSACTION_getUserAge, _data, _reply, 0);
                _reply.readException();
                _result = _reply.readInt();
            } finally {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        }
    }

我们前面说了它其实是BinderProxy的代理。为什么要对BinderProxy加这个代理呢?看一下getUserAge():

    public int getUserAge(java.lang.String userName) 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(userName);
            mRemote.transact(Stub.TRANSACTION_getUserAge, _data, _reply, 0);
            _reply.readException();
            _result = _reply.readInt();
        } finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }

即是调用mRemote.transact()(BinderProxy的)方法。Stub.TRANSACTION_getUserAge是要调用的远程Binder方法的getUserAge()对应的描述符。

_data是序列化后的入参、_reply是序列化后的返回值。可以看到_data所携带的参数是需要序列化的,_reply所带的内容是被序列化的,所以读取要反序列化。

所以IUserManager.Stub.Proxy类的作用就是在跨进程调用时对传给mRemote(BinderProxy)的参数做序列化,对mRemote(BinderProxy)返回值做反序列化。参数的接受者和返回者是BinderProxy

具体调用Binder的能力是使用BinderProxytransact()方法,它是跨进程通信的核心 , 我们来看一下这个方法:

public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
    ...
    return transactNative(code, data, reply, flags); // native 方法
}

省略了不重要的代码。即BinderProxy是通过transactNative来与远程Binder跨进程通信的。具体怎么实现,这里就不追究了。

Stub.onTransact()

我们前面没有看这个方法,这里我们来看一下:

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_getUserAge: {
            data.enforceInterface(DESCRIPTOR);
            java.lang.String _arg0;
            _arg0 = data.readString();
            int _result = this.getUserAge(_arg0);
            reply.writeNoException();
            reply.writeInt(_result);
            return true;
        }
    }
    return super.onTransact(code, data, reply, flags);
}

根据IUserManager.Stub.Proxy我们知道,如果不在同一个进程,那么参数是被序列化后传过来的,所以这个方法是用来对入参做反序列化,并对返回值做序列化的

最后我们用一张图来总结Binder进程通信机制 :

Binder机制.png

欢迎关注我的Android进阶计划看更多干货

参考:

Android 进阶9:进程通信之 AIDL 解析

Binder学习指南

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

推荐阅读更多精彩内容