Binder简析

1.Binder简介

我们知道,在Android操作系统中,每个进程都拥有属于自己的独立虚拟地址空间(用户空间+内核空间)。一般情况下,进程的用户空间是各自独立的,无法进行数据共享;而内核空间却是所有进程都可以共同访问的。


进程空间

对于Android系统来说,其特有的IPC机制是Binder。正如其名字所喻(粘粘剂),通过Binder作为桥梁,将系统各个进程组件进行连接。

2.Binder优点

1.Binder更加简洁和快速,消耗的内存资源更小。
2.传统的进程间通信可能会增加进程的开销,而且有进程过载和安全漏洞等方面的风险,Binder正好能解决和避免这些问题。
3.用驱动程序来推进进程间的通信。
4.通过共享内存来提高性能。
5.为进程请求分配每个进程的线程池。
6.针对系统中的对象引入了引用计数和跨进程的对象引用映射。
7.进程间同步调用。

3.Binder实现IPC通讯原理

3.1 Binder IPC基础组件

Binder通讯采用C/S架构,其进行IPC涉及到的组件主要包含Client,Server,ServerManager和binder驱动。

Client:客户端
Server:服务端
ServerManager:用于管理系统中的各种服务。提供服务注册功能和查询服务功能。(此处的ServerManager是Native层的ServiceManager(c++),不是framework层的ServerManager(java))。
binder驱动:位于内核空间,提供Client,Server,ServerManager跨进程通讯功能。

3.2实例讲解

我们知道,Android提供的AIDL通讯底层采用的就是Binder机制。现在我们采用AIDL方式创建2个应用进行进程间通讯。
(1)首先定义aidl接口:

// IMath.aidl
package com.yn.bindertest.aidl;

// Declare any non-default types here with import statements

interface IMath {
    int add(in int a,in int b);
    void result(in int justTestWithoutReturnValue);
}

(2)服务端代码编写:

public class Server extends Service {
    private static final String TAG = "ynServer";
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new IMath.Stub(){
            @Override
            public int add(int a, int b) throws RemoteException {
                Log.d(TAG, "add: "+Thread.currentThread().getName());
                return a+b;
            }

            @Override
            public void result(int justTestWithoutReturnValue) throws RemoteException {
                Log.d(TAG, "result: "+Thread.currentThread().getName());
            }
        };
    }
}

(3)客户端代码编写:

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "ynClient";
    private TextView tvShow;
    private IMath mServer;
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(TAG, "onServiceConnected: "+Thread.currentThread().getName());
            mServer = IMath.Stub.asInterface(service);
            try {
                mServer.asBinder().linkToDeath(mDeathRecipient,0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG, "onServiceDisconnected: "+Thread.currentThread().getName());
        }
    };
    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient(){

        @Override
        public void binderDied() {
            Log.d(TAG, "binderDied: "+Thread.currentThread().getName());
            bindService(new Intent(MainActivity.this, Server.class),mServiceConnection , Context.BIND_AUTO_CREATE);
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tvShow = (TextView)findViewById(R.id.tvShow);
        bindService(new Intent(this, Server.class),mServiceConnection , Context.BIND_AUTO_CREATE);


    }

    public void onClick(View view)
    {
        try {
            int sum = mServer.add(10,30);
            Log.d(TAG, "onClick: sum="+sum);
            tvShow.setText(sum+"");
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

(4)记得在AndroidManifest.xml中将Service设置到另一个进程中。

 <service
            android:name=".Server"
            android:process="com.yn.bindertest.server" />

通过以上步骤,我们就实现了使用AIDL进行跨进程通讯功能,结果如下:

AIDL跨进程通讯

当程序启动时,bindService()成功时就会回调onServiceConnected,然后手动调用add可以看到add过程是运行在Server端的binder线程池中的;由于为Server设置了死亡代理,当服务端异常被kill时,会回调死亡代理IBinder.DeathRecipient.binderDied和ServiceConnection.onServiceDisconnected,两者的区别就是binderDied是运行在客户端的binder线程池中的,而onServiceDisconnected是运行在主线程中的
更关键的一点是:当客户端调用远程函数时,客户端进程会被挂起,等待远程服务端执行完成后才重新运行客户端进程,因此,这是一个同步调用,应小心因为服务端的耗时方法导致客户端发生ANR

3.3 aidl的binder机制讲解

当我们创建完成IMath.aidl文件后,rebuild一下,可以看到IDE为我们生成了一个.java文件,路径为:app\build\generated\source\aidl\debug\com\yn\bindertest\aidl\IMath.java
该IMath.java的具体内容如下:


IMath.java

可以看到,IDE为我们自动生成的其实就是一个继承了IInterface的接口,生成的接口类名和方法与我们aidl中声明的完全一致,只是另外增加了一个Stub内部抽象静态类,而这个Stub类继承了Binder并且实现了我们这个IMath(由于IMath继承了IInterface,而IInterface只有一个接口方法asBinder),所以这个Stub类就是一个Binder,并且必须实现我们自己定义的IMath中的接口方法,并提供asBinder方法给到系统调用。

所以这个Stub类才是我们要着重进行研究的,那么这个Stub类的具体如下所示:

    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.yn.bindertest.aidl.IMath {
        private static final java.lang.String DESCRIPTOR = "com.yn.bindertest.aidl.IMath";

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

        /**
         * Cast an IBinder object into an com.yn.bindertest.aidl.IMath interface,
         * generating a proxy if needed.
         */
        public static com.yn.bindertest.aidl.IMath asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.yn.bindertest.aidl.IMath))) {
                return ((com.yn.bindertest.aidl.IMath) iin);
            }
            return new com.yn.bindertest.aidl.IMath.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_add: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();
                    int _arg1;
                    _arg1 = data.readInt();
                    int _result = this.add(_arg0, _arg1);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
                case TRANSACTION_result: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();
                    this.result(_arg0);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.yn.bindertest.aidl.IMath {
            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 add(int a, int b) 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.writeInt(a);
                    _data.writeInt(b);
                    mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void result(int justTestWithoutReturnValue) 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.writeInt(justTestWithoutReturnValue);
                    mRemote.transact(Stub.TRANSACTION_result, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

        static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_result = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }

看一下asInterface()内容:

asInterface

可以看出,通过DESCRIPTOR(即包名+类名:"com.yn.bindertest.aidl.IMath")先进行本地查找,如果找到,那么返回本地binder类对象(即onBind返回的那个binder对象),如果找不到,那么返回一个binder代理类,这个代理类具体内容如下:

       private static class Proxy implements com.yn.bindertest.aidl.IMath {
            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 add(int a, int b) 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.writeInt(a);
                    _data.writeInt(b);
                    mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void result(int justTestWithoutReturnValue) 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.writeInt(justTestWithoutReturnValue);
                    mRemote.transact(Stub.TRANSACTION_result, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

可以看到,这个Proxy也实现了IMath接口,并且持有一个远程服务的IBinder对象(其实这个IBinder是一个BinderProxy对象,是bindservice时,向ServiceManager获取得到的)。所以,当处于不同的进程时,客户端得到的是一个Proxy代理类,然后由于该代理类实现了aidl定义的接口,并且持有一个远程IBinder接口,所以在调用aidl接口方法时,可以看到,它通过将函数参数放入到一个Parcel(_data)中,提供一个Parcel(_reply)存储返回结果(如果有返回的话),然后通过持有的远程IBinder对象的transact将函数标识,参数Parcel和返回结果Parcel发送给远程服务,客户端调用transact时,客户端进程被挂起,然后通过binder驱动调用到远程服务的onTransact函数,通过传递过来的函数标识,就可以知道要执行哪个函数,然后取出对应的函数参数,运行完成后,将返回结果存储到_reply中,通过binder驱动返回给客户端,客户端只要读取_reply就可以获取到结果。这样,就完成了一次IPC通讯。

综上所述,通过aidl实现IPC的原理如下:
1.IDE根据我们定义的aidl生成一个同名接口,并继承IInterface,目的是为了客户端可以通过asBinder获取到binder对象(同个进程则获取的是同一个binder,不同进程获取到的是一个BinderProxy代理对象)。
2.当Server(Service)启动时,它会通过binder驱动向ServiceManager进行注册(binder标识+binder引用);当Client要获取远程服务(bindservice)时,它首先要通过binder驱动向ServiceManger获取特定远程服务的binder引用,获取成功后,就可以对远程服务进行IPC通讯(也是通过binder驱动)。

总结

应用间通过Binder实现IPC的整体过程如下图所示:

binder综合流程

1.当Server启动时,它会通过进程内的0号binder引用,将服务名和binder传递给内核0号binder引用,再依次传给ServiceManager内核binder实体,最后传入ServiceManager的进程binder中。经过这个过程后,ServiceManager内部就记录了Server相应的信息,该过程即是Server向ServiceManager的注册过程。

2.当Client要进行远程服务通讯时,它首先会经由进程内0号binder引用通知到内核0号binder引用,然后经由ServiceManager内核binder实体,最后将所需的服务信息传入到ServiceManager进程的binder实体中,那么ServiceManager通过查询相关服务信息,如果存在对应服务,就会通过binder驱动传递一个拥有访问远程服务能力的BinderProxy给到Client(即bindservice时,onServiceConnection中的onConnected中的IBinder参数)。这个过程就是Client的获取远程服务过程。

3.Client获取远程服务成功后,就可以通过ServiceManager传递过来的BinderProxy对象创建出一个远程服务Binder的代理类对象(asInterface),通过这个代理类对象,Client就可以执行远程服务相关方法了,从而实现跨进程IPC通讯。

4.当Client调用远程方法时,本地远程binder的代理类对象会调用其持有的BinderProxy对象的transact方法,这个方法会将要调用的函数标识,参数和返回值(如果有的话)放入到Client的共享内存中,然后经由内核binder驱动运输到相应远程服务的共享内存中,然后远程服务的进程binder实体的onTransact函数会被调用,这个函数的功能就是从共享内存中获取传递过来的相关信息,通过函数标识就知道要运行哪个函数,然后再将相关的参数提取出来(如果有的话),执行完毕后,就将返回结果存入之前传递过来的容器(Parcel)内,最后经由内核binder驱动传递回给到Client中。这样,一次完整的binder IPC通讯就完成了。

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

推荐阅读更多精彩内容