安卓进阶跨进程通信Android知识

Binder 浅出深入 -- 你真的会用 binder?

2017-03-11  本文已影响193人  crianzy

做过 Android 跨进程通信的同学应该都知道 Binder 是个什么东西。有了他我们可以很方便的调用另外一个进程中的方法。

如果看过 Android Framework 层源码的话,更会发现说,大部分都是在和 binder 打交道。

所以 Binder 这套跨进程通信机制(IPC)对 Android 开发通信来说是非常重要的。

但是想要深入的了解总是感觉有点力不从心, 下面来一起浅出深入的了解 Binder。

如何使用 AIDL

都知道在 Android 开发中如果跨进程件通信需要使用 AIDL的方式.
主要有以下几个步骤

  1. 定义 aidl 接口文件
  2. 等待 IDE 生成相关的 java 文件
  3. 在 Service 中的 onBind()方法中 返回对应的 Binder 对象
  4. 在 Activity 中调用 bindService()方法。 并在回调中获取 Binder 对象
  5. 吧 binder 对象转换成我们定义的接口类型, 在调用我们定义接口方法

关于更加详细的使用步骤请看我之前写的一篇博客.AIDL使用学习

你真的会用 binder?

跨进程通信,使用方式都知道. 但是大多的时候是知其然, 不知其所以然. 我们先不讨论深入的细节原理.
我先来思考一下几个问题.

  1. 当 A 进程在主线程中调用 B 进程中的某个方法时, B 进程中执行该方法的是哪个线程?

  2. 当 A 进程通过 bindService 分别连上 B 进程 和 C 进程. 那么 B C之间不通过 bindService 是否能够通信?

  3. 当 A 进程通过 bindService 连上 B 进程后, 如何同 B 进程调用 A 进程中的方法?

先来回答第一个问题:
这个问题只要自己尝试一下打个 log 就能够知道, B 进程中执行该方法的不是主线程, 而是一个和 binder 相关的线程, 打出来的 log 如下:

ServiceB: basicTypes: B Thread[Binder_1,5,main]

很明显这个线程,不是我们创建的, 是 Binder 这套机制给我创建.

如果属性 Android 源码的话会发现, 基本上每次 AMS 通过 Binder 调用 ActivtyThread 中的方法, 都要在重新通过 Hander 机制在处理一下. 这是因为跨进程调用的那个方法不是主线程. 如果要在该方法中执行主线程的一些方法, 需要通过 Hnader 机制才行.

那为什么会出现这个一个 binder线程呢? 可以好好想想.

再来回答第二个问题:
我们一般跨进程通信第一时间想到的肯定是说 AIDL bindService 来执行. 那 B和 C 之前不 Binder Service怎么跨进程通信呢?

我们下来看看 bindService 中我们拿到的是什么.

bindService(intent, new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mBBinder = service;
        IAtoBAidlInterface stub = IAtoBAidlInterface.Stub.asInterface(mBBinder);
        try {
            stub.basicTypes(1, 2l, false, 1.0f, 2.0, "test");
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.d(TAG, "B onServiceDisconnected() called with: name = [" + name + "]");

    }
}, Context.BIND_AUTO_CREATE);

从面的代码我们可以看到, 我们在 onServiceConnected可以获取到参数 service, 他是一个IBinder对象, 然后我们在通过IAtoBAidlInterface.Stub.asInterface(mBBinder)方法转换成我们需要的接口类型, 然后就可以顺利的执行方法了, 而且执行的 B 进程中的方法.

这里的重点是, 我们拿到一个 IBinder 对象, 然后把它转成了我们要的接口类型. 然后就可以直接调用另外一个进程的方法(感觉上是这样的)

在看看问题和条件.
条件: 当 A 进程通过 bindService 分别联上 B 进程 和 C 进程
问题: B 和 C 之前不 Binder Service怎么跨进程通信呢

从条件中我们可以得到的信息是: A 进程中持有两个我们上面说的IBinder对象, 一个是 B 进程的, 一个是 C 进程的. 这里我们名这两个对象为binderBbinderC

那我们大胆的猜想一下. 把binderC 传递给 B 进程, 那 B 进程是不是直接可以根据这个binderC对象直接调用 C 进程中的方法呢? 可以尝试一下.

我们在 A 调用 B 进程的 AIDL 文件上中加入一个 addBinder() 方法,

interface IMyAidlInterface {
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);

    void addBinder(android.os.IBinder binder);
}

然后在 B 进程中实现该方法:

IAtoBAidlInterface.Stub mBinder = new IAtoBAidlInterface.Stub() {
    @Override
    public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
        Log.e(TAG, "basicTypes: B " + Thread.currentThread());
        Log.d(TAG, "basicTypes() called with: anInt = [" + anInt + "], aLong = [" + aLong + "], aBoolean = [" + aBoolean + "], aFloat = [" + aFloat + "], aDouble = [" + aDouble + "], aString = [" + aString + "]");
    }

    @Override
    public void addBinder(IBinder binder) throws RemoteException {
        Log.e(TAG, "addBinder() called with: binder = [" + binder + "]");
        // 把 binder 对象转换成 对应的接口类型
        IAtoCAidlInterface iAtoCAidlInterface = IAtoCAidlInterface.Stub.asInterface(binder);
        iAtoCAidlInterface.basicTypes(2, 3l, false, 1f, 2.3, "B call c");

    }
};

然后我们在在 A 进程中调用 addBinder 方法试试看. 查看 log 日志发现, 果然 B 进程成功调用了 C 进程中的方法.


原来不通过 bindService 也可以两个进程之间通信.可以想一想那我们为什么一般还是用 bindService 呢?

再来看第三个问题
通过上面的实验, 我们发现我们拿到了 B 进程对应的binderB对象, 那么理论上我们弄一个 A 进程的 binder 对象, 然后通过 binderB 传给 B 进程.

那么怎么弄一个 A进程的 binder 呢? 可以看看 Service 中是怎么弄出一个 Binder 对象来的, 他是使用我们之前定义adil 文件中自动生成的 Stub 类来 new 出一个 Binder 对象的. 我们也可以这么干.

我们先定义一个A进程中的 Binder 对象:

// 这里为了方便起见使用的是 IAtoCAidlInterface 的接口
IAtoCAidlInterface.Stub mLocalBinder = new IAtoCAidlInterface.Stub() {
    @Override
    public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
        Log.e(TAG, "ActivityA basicTypes: C " + Thread.currentThread());
        Log.d(TAG, "ActivityA basicTypes() called with: anInt = [" + anInt + "], aLong = [" + aLong + "], aBoolean = [" + aBoolean + "], aFloat = [" + aFloat + "], aDouble = [" + aDouble + "], aString = [" + aString + "]");
    }

};

然后我们把该对象通过binderB对象传递到 B 进程中, 然后在 B 进程中调用basicTypes 方法, 查看日志发现, 果然B 进程通过我们传过去的 binder 调用 A 进程中的方法.

看到这里你会发现, 跨进程通信其实很简单, 只要我们有一个 Binder 对象, 然后传递给另一个进程. 这样相互之间就能够通信了.
其实如果看过 Android Framework 层源码的人会发现里面包含了大量的这种 Binder 传来传去的通信方式.

我们通过 bindService方式去连接 B 进程,其实也是为了 B 进程中的 binder 能够传递到 A 进程中, 不然凭空的两个进程之间也不能传递数据啊.

注意

我上面说的所有的 binder 对象从这个进程传递到另一个进程只是为了方便理解.
其实 A 进程中的 binderA 传到到 B 进程中就不是原来的 binder 对象了. 这是毋庸置疑的, 因为两个进程之间内存是分开的, 对象自然也是分开的. 但是我们依然可以用这个假的 binder 对象去调用远程进程的方法. 这就是 Binder 的高明之处.

结论

  1. 如果 A进程想要和 B 进程通信, 那么只要 A 进程拿到 B 进程的 Binder(无论真假) 对象即可
  2. 为了 A 进程能够方便的拿到 B进程的 Binder 对象, 我们需要通过 bindService 方式来获取 Binder 对象
  3. 通过跨进程调用而执行到的方法, 不是在主线程中执行的, 而是在一个和 binder 相关的线程中执行的

这篇文章只是简单讲述了 binder 的常规用法和非常规用法. 我想你应该理解 binder 的一些特性了, 希望你能通过这些特性更加的了解 Binder.

也只有了解了这些特性,才能更加方便的浅出深入的去理解 binder

上一篇下一篇

猜你喜欢

热点阅读