Binder 浅出深入 -- 你真的会用 binder?
做过 Android 跨进程通信的同学应该都知道 Binder 是个什么东西。有了他我们可以很方便的调用另外一个进程中的方法。
如果看过 Android Framework 层源码的话,更会发现说,大部分都是在和 binder 打交道。
所以 Binder 这套跨进程通信机制(IPC)对 Android 开发通信来说是非常重要的。
但是想要深入的了解总是感觉有点力不从心, 下面来一起浅出深入的了解 Binder。
如何使用 AIDL
都知道在 Android 开发中如果跨进程件通信需要使用 AIDL的方式.
主要有以下几个步骤
- 定义 aidl 接口文件
- 等待 IDE 生成相关的 java 文件
- 在 Service 中的 onBind()方法中 返回对应的 Binder 对象
- 在 Activity 中调用 bindService()方法。 并在回调中获取 Binder 对象
- 吧 binder 对象转换成我们定义的接口类型, 在调用我们定义接口方法
关于更加详细的使用步骤请看我之前写的一篇博客.AIDL使用学习
你真的会用 binder?
跨进程通信,使用方式都知道. 但是大多的时候是知其然, 不知其所以然. 我们先不讨论深入的细节原理.
我先来思考一下几个问题.
-
当 A 进程在主线程中调用 B 进程中的某个方法时, B 进程中执行该方法的是哪个线程?
-
当 A 进程通过 bindService 分别连上 B 进程 和 C 进程. 那么 B C之间不通过 bindService 是否能够通信?
-
当 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 进程的. 这里我们名这两个对象为binderB
和 binderC
那我们大胆的猜想一下. 把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 的高明之处.
结论
- 如果 A进程想要和 B 进程通信, 那么只要 A 进程拿到 B 进程的 Binder(无论真假) 对象即可
- 为了 A 进程能够方便的拿到 B进程的 Binder 对象, 我们需要通过 bindService 方式来获取 Binder 对象
- 通过跨进程调用而执行到的方法, 不是在主线程中执行的, 而是在一个和 binder 相关的线程中执行的
这篇文章只是简单讲述了 binder 的常规用法和非常规用法. 我想你应该理解 binder 的一些特性了, 希望你能通过这些特性更加的了解 Binder.
也只有了解了这些特性,才能更加方便的浅出深入的去理解 binder