Binder牌胶水,如雷贯耳,在Android中无处不在,是每个Android程序猿居家旅行必备。有了它的存在,我们甚至可以不用深入了解App进程和系统进程、用户空间和内核空间、跨进程通讯等概念也可以做好应用层开发。但是呢,阅读本文之前,还是建议先看看[《Binder学习指南》][1]了解下这些概念。
Binder属于“问题领域”的命名?见名知意,作为胶水,“粘合”是他的主要职责。在Androd世界里,他到底粘合了什么?比如我们经常使用的startActivity()
就涉及到进程通讯,Binder粘合了这些进程,弱化了我们对进程的感知,降低了开发难度。
本文来聊聊四瓶Binder胶水,他们功效不同,配方却完全一致。此文的重点有两个,其一,介绍下他们的配方,也就是类图模型,了解他们,对我们阅读Android源码,寻找源码的位置有很大的帮助;其二,既然作为胶水,我们要研究下他粘合了什么东西,粘合了哪些进程。这四瓶胶水的功效和配方分别是:
- AIDL:
IInterface--Stub--Proxy--Stub具体实现
- ContentProvider:
IContentProvider--ContentProviderNative--ContentProviderProxy--ContentProvider.Transport
- 管理四大组件的AMS:
IActivityManager--ActivityManagerNative--ActivityManagerProxy--ActivityManagerService
- 负责ActivityThread和AMS之间的通讯
IApplicationThread--ApplicationThreadNative--ApplicationThreadProxy--ApplicationThread
Binder文章很多,很难写出彩,相比于一言不合就晒C++代码,晒cpp文件的,本文重点还是从Java层出发解释Binder机制。本文所有代码在此:
https://github.com/geniusmart/binder-project
Binder胶水的原始配方
如上图,IInterface/IBinder/Binder/BinderProxy是Binder机制的核心api,理解这些接口/类,是研究Binder的前提。
接口通常代表所具备的能力,比如我们熟悉的Api里,Serializable
和Parcelable
代表其实现类是可序列化的;Iterable
代表可迭代遍历,因此其实现类HashSet
和ArrayList
可使用Iterator
进行遍历。
在这个类图的最顶层,有两个接口,IInterface
和IBinder
。IBinder
代表跨进程传输的能力,而IInterface
则代表远程服务端具备的能力。
Binder
是IBinder
的实现类,因此它具备跨进程传输的能力,它实际上就是远程Server端的Binder对象本身。
Binder
对象是给Server端对象本身,是Server进程用的,与此对应的BinderProxy
则是远程Binder
的代理对象,给Client进程用的(源码位于Binder
类内部)。在跨越进程的时候,Binder驱动会自动完成这两个对象的转换。
以上都是冷冰冰的理论,读到这里我们仍然很困惑,我们需要能亲手掌控两个进程:Client进程和Server进程,并进行通讯,以此来熟悉Binder机制。因此,第一瓶胶水应运而生——AIDL,AIDL能实现这个需求。
以上四个类是
android.os
包给我们提供的Binder机制相关的api,基于这四个类,我们可以扩展出各种各样的Binder模型,实现各种各样的跨进程传输的场景。而本文所描述的四瓶胶水便是基于此套api的四种实现。
第一瓶胶水AIDL
假设你已经熟练掌握了AIDL,首先写个ICompute.aidl
,代码很简单,如下:
// ICompute.aidl
package com.geniusmart.binder.aidl;
interface ICompute {
int add(int a,int b);
}
此时,编译器会帮我们生成一个ICompute.java文件,这个类的可读性很差,为方便大家查阅,我将代码做了格式化,并拷贝一份放在temp包下,点击这里查看。
这个类的细节我不打算多讲,大家可以查看一下文章开篇的那篇文章。对照着生成出来的ICompute.java文件,绘制如下Binder模型图:
1. 熟悉的配方:ICompute-Proxy-Stub-Stub具体实现
通过上图,我们发现AIDL的Binder模型是基于原始配方的扩展。当我们写完ICompute.aidl之后,ICompute
,Stub
,Proxy
已经自动生成出来,他们的作用如下:
-
ICompute
接口继承了IInterface
,并写了add(a,b)
的方法,代表Server端进程具备计算两数相加的能力。此方法将在Stub的具体实现
类中重写。 -
Stub
是一个抽象类,他本质是一个Binder
,他存在的目的之一是约定好asInterface
,asBinder
,onTransact
方法,减少我们开发具体Binder
对象的工作量。此外,Stub
即需要跨进程传输,又需要约定远端服务端具备的能力,因此他需要同时实现IInterface
和IBinder
接口,通过上文的类图可以看得出来。 -
Proxy
是代理对象,它在Client进程中使用,作用是以假乱真。他持有BinderProxy
对象,BinderProxy
能帮我们访问服务端Binder对象(本例中即Stub具体实现类
)的能力。
AIDL让我们可以饭来张口衣来伸手,但是,服务端具备的具体能力,AIDL是没法给的,需要我们自力更生,所以接下来我们来写个Stub的具体实现
,并验证下这个Binder模型的准确性。
2.验证远程对象和代理对象
在Server进程启动一个Service,定义Stub的实现类ComputeBinder
,等待Client进程的连接,代码如下:
public class ComputeService extends Service {
public static final String TAG = "Server进程";
@Override
public IBinder onBind(Intent intent) {
return new ComputeBinder();
}
private static class ComputeBinder extends ICompute.Stub {
@Override
public int add(int a, int b) throws RemoteException {
Log.i(TAG, TAG + this.getClass().getName() + "执行add()");
return a + b;
}
}
}
接下来,我们来写连接远程服务的代码 ,核心代码如下:(完整代码点击查看)
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, TAG + "触发onServiceConnected : " + service.getClass().getName());
mICompute = ICompute.Stub.asInterface(service);
Log.i(TAG, TAG + "触发asInterface : " + mICompute.getClass().getName());
Log.i(TAG, TAG + "触发add() : 远程方法执行结果为" + mICompute.add(3, 5));
}
};```
这里我设计了两种场景,分别让Server进程和Client进程执行连接远程服务,输出结果如下:
- Client进程绑定Server进程的Service获取Binder
(1)Client进程输出日志:
Client进程触发onServiceConnected : android.os.BinderProxy
Client进程触发asInterface : ICompute$Stub$Proxy
Client进程触发add() : result = 8
(2)Server进程输出日志:
Server进程ComputeService$ComputeBinder执行add()
- Server进程绑定自身进程内的Service获取Binder
Server进程输出日志:
Server进程触发onServiceConnected : ComputeService$ComputeBinder
Server进程触发asInterface : ComputeService$ComputeBinder
Server进程ComputeService$ComputeBinder执行add()
Server进程触发add() : 远程方法执行结果为8
结论:
- 当Client进程连接远程时,会经过如下步骤:客户端获得`BinderProxy`对象->转换为`ICompute$Stub$Proxy`->服务端`ComputeService$ComputeBinder`执行相应的方法,并将数据返回给客户端->客户端取得数据。
- 当Server进程连接自身时,始终调用的是Binder本体对象`ComputeService$ComputeBinder`
#### 3.工作原理
说好了不谈细节,但是对于讲清楚Binder机制来说,不谈细节臣妾真的做不到,受限于篇幅,我还是克制住了,取而代之的是一张基于aidl的工作流程图,与其贴代码讲细节,还不如大家对照源码和流程图自己解析aidl的工作原理。如下:
![](http://upload-images.jianshu.io/upload_images/638283-cba5ef060b5ffa63.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
####4. 这瓶胶水粘合了什么?
这个问题对于AIDL来说很简单,他粘合了需要通讯的两个进程,在上文我们称之为Server进程和Client进程,在上文的UML类图中也有所体现。
##第二瓶胶水ContentProvider
为了巩固这个模型,Binder牌胶水隆重推出第二个款式ContentProvider。谈起跨进程,我们自然而然会想起内容提供者。即使你不了解ContentProvider的原理,我们平常在使用api的时候,所做的事情就是让**内容使用者进程**去访问**内容提供者进程**的数据,所以这第二瓶胶水很贴近我们日常的Android开发生活。
首先先给出结论,也就是有关ContentProvider的Binder模型图:
![](http://upload-images.jianshu.io/upload_images/638283-e2196c706abe8b14.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
####1. ContentProvider Binder模型来源
接下来我们来分析下这张图是怎么来的:
作为一名Coder,应该永远保持好奇心,使用`ContentProvider`时,我们经常用这样的api:`getContentResolver().query()/insert()/update()/delete()`,你只要点进去看看源码,都能很轻易发现这个模型图,这个过程大概是这样的:
- (1) 从内容使用者的角度出发,查看`insert()`源码
- (2) 发现关键代码`IContentProvider provider = acquireProvider(url);`
- (3) 活捉一只`IInterface`的子接口`IContentProvider`,对应AIDL的`ICompute`
- (4) 那么问题来了,对应的`Proxy\Stub\Stub具体实现`在哪?一筹莫展中。
- (5) 此路不通,换一条路,即从内容提供者角度出发,查看 `ContentProvider`的源码,找找线索。
- (6) AS切换到Structure视图,观察类的结构,此时应该有点运气成分,我们发现了内部类`Transport`,他的继承关系是:`class Transport extends ContentProviderNative{}`。虽说此步需要点运气,但是`Transport`的中文意思为运输,颇有点跨进程传输的意思,所以也并非毫无根据。
- (7) 看`ContentProviderNative`源码,发现其继承关系`class ContentProviderNative extends Binder implements IContentProvider{}`,这就是我们熟悉的模型啊,对应AIDL中的`Stub`,而`Transport`则对应着`Stub的`具体实现类`ComputeBinder`。
- (8) 在`ContentProviderNative`内部,与他同一级的还有一个类,即`ContentProviderProxy`,至此,Binder模型相关的4个类或接口,我们已经集齐完毕。
- (9) 最后,召唤神龙,深入学习ContentProvider的原理。
#### 2.验证远程对象和代理对象
`ContentProvider`与AIDL不一样,AIDL中Binder对象可以轻易拿到,而我们在使用`ContentProvider`时候,通常都是通过`ContentResolver`来执行CRUD的操作,显然,他并不是一个Binder对象,追溯其源码,我们可以发现这样一行关键的代码:
public final @Nullable Uri insert(@NonNull Uri url, @Nullable ContentValues values) {
IContentProvider provider = acquireProvider(url);
//省略若干代码..
}
看到返回类型`IContentProvider`时就是你大声呼喊“真相只有一个”的时候,阅读源码的乐趣就在于抽丝剥茧,轻解衣裳,窥探最本真和纯洁的胴体。。。
这时候问题又来了,`acquireProvider(url)`是一个`@hide`方法,没法直接调用。解决办法很简单,即使用反射。核心代码如下:
Uri uri = Uri.parse("content://com.geniusmart.binder.AccountProvider/account");
ContentResolver contentResolver = getContentResolver();
//通过反射调用hide方法
Method method = ContentResolver.class.getMethod("acquireProvider", new Class[]{Uri.class});
Object object = method.invoke(contentResolver, uri);
//打印Binder类型
Log.i(TAG, object.getClass().toString());
与AIDL中的验证思路一样,我们来分别验证下客户端和服务端执行此段逻辑之后的结果:
- Client进程使用Server进程的ContentProvider
输出结果为:`class android.content.ContentProviderProxy`
- Server进程使用自己的ContentProvider
输出结果为:`class android.content.ContentProvider$Transport`
结论:Client进程通过`ContentProviderProxy`访问Server进程的`ContentProvider$Transport`,实现进程间通讯。
以上所有代码均在文章开篇的Github项目里。
#### 3. 这瓶胶水粘合了什么?
这个问题也比较简单,ContentProvider这瓶胶水粘合了内容使用者和内容提供者两个进程,写`ContentProvider`的这一方称之为Server进程,用`ContentResolver`的这一方称之为Client进程,两个进程通过Binder进行通讯。
##另外两瓶胶水AMS和ApplicationThread
AIDL和ContentProvider这两个Binder模型,都有清晰的Client进程和Server进程,理解起来相对容易,而另外两个Binder模型所涉及到的进程双方则比较模糊。AMS和ApplicationThread有着千丝万缕的关系,所以我们放在一起讲。首先贴一下这两瓶胶水的配方:
![AMS的Binder模型](http://upload-images.jianshu.io/upload_images/638283-205b10c917ed116c.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![ApplicationThread的Binder模型](http://upload-images.jianshu.io/upload_images/638283-0b389d01130fecd3.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
看到这两个Binder模型图,真是感慨,这简直是一样的配方,熟悉的味道,所以以后如果有看到类似`IXxxx/XxxxProxy/XxxxNative/Xxxx`的命名规则,一定要想起这是Binder的配方。
#### Binder模型来源
这两个Binder模型图从何而来?
在一个夜黑风高的晚上,你我寂寞难耐,准备Fuck Source Code,于是决定挑个并不软的柿子来捏一捏,他就是`startActivity()`,大概会这么一些个前戏:
(1)`context.startActivity()`
(2)`mBase.startActivity()`
(3)`ContextImpl.startActivity()`
(4)`mMainThread.getInstrumentation().execStartActivity()`
(5)`Instrumentation.execStartActivity()`
源码阅到这里已经有一个小高潮了,因为我们发现了如下代码:
//IApplicationThread 是IInterface类型
IApplicationThread whoThread = (IApplicationThread) contextThread;
//ActivityManagerNative是Binder和IInterface类型
ActivityManagerNative.getDefault().startActivityAsUser()
此时Binder机制的雏形我们已经心中有数,之后少侠们可以各自发挥,深入理解这两个Binder机制的作用,获得更多的快感。
所以,理解这个模型的意义在于,当我们看到XxxxProxy执行逻辑时,当前进程所属的角色是Client进程,要查看该逻辑源码的时候,应该找到Server进程的Xxxx类查看。
Activity的启动流程的解析漫长而枯燥,不是此文的重点,大家了解下这两个类的意义即可:
- `ActivityManagerService`
AMS是Android中最核心的服务,主要负责系统中四大组件的启动、切换、调度及应用进程的管理和调度等工作。
- `ApplicationThread`
`ApplicationThread`是`ActivityThread`的内部类,负责`ActivityThread`和`ActivityManagerService`之间的通讯。
#### 这两瓶胶水粘合了哪些进程?
通过Activity启动流程图我们来看下这两个Binder模型是如何发挥作用的,如下图:
![Activity启动流程图](http://upload-images.jianshu.io/upload_images/638283-2168b0e80a01bd8f.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
*注:此张图片来源于这篇文章[《startActivity流程分析(一)》][2],已征得博主同意引用在此处。*
在这张图里,我们意识到除了第一、二瓶胶水描述到的App进程之外,Android世界里还有Launcher进程,系统进程等等,而Binder在其中如鱼得水,散发着万丈光芒。
`AMS`位于系统进程,处于Server进程的位置,Launcher进程和App进程作为Client进程,持有`ActivityManagerProxy`,与AMS进行通讯,召唤四大组件。而`ApplicationThread`位于应用进程,处于Server进程的位置,系统进程则作为Client进程,持有`ApplicationThreadProxy`,使得应用进程中的主线程(`ActivityThread`)和AMS之间可以进行通讯。
至此,对于Binder机制的认知应该要有所升华。
##总结
用一张表格描述一下上文所讲的四个Binder模型:
| 能力 | IInterface| Binder抽象|BinderProxy|Binder|
| -------- | --------| -- | -- | -- |
| AIDL| `ICompute` | `Stub` | `Proxy`|`ComputeBinder`|
| 内容提供者 | `IContentProvider` | `ContentProviderNative` | `ContentProviderProxy`|`ContentProvider.Transport`|
| AMS| `IActivityManager` | `ActivityManagerNative` |`ActivityManagerProxy`|`ActivityManagerService`|
| ActivityThread和AMS之间的通讯 | `IApplicationThread` | `ApplicationThreadNative` |`ApplicationThreadProxy`|`ApplicationThread`|
Binder并不神秘,对于应用层来说,Binder便是上文中我所绘制的各种模型图,我们需要明确两点,**第一,Proxy作为代理对象,以假乱真,Client进程通过持有Proxy对象,进而调用Server进程中实际Binder对象的能力;第二,在使用任何一个Binder对象时,我们要明确,此时进行通讯的是哪两个进程,以及哪个进程充当Client或Server进程。**
最后多说一句,关于Binder写了那么多,大牛们自成体系自然无需阅读此小文,而小小牛们看完之后也许仍然十分懵懂,所以写Binder文章略显尴尬,而在这个过程中收获最大的其实是作者本人。学习Binder,没有捷径,找准切入点(本文的切入点在于应用层的Binder模型以及每种模型涉及通讯的两个进程),然后Read the Fucking Source Code,最后写写文章做总结。
##参考文章
[Binder学习指南][1]
[startActivity流程分析(一)][2]
[Android应用程序组件Content Provider简要介绍和学习计划](http://blog.csdn.net/luoshengyang/article/details/6946067)
[Android进程间通信(IPC)机制Binder简要介绍和学习计划](http://blog.csdn.net/luoshengyang/article/details/6618363)
[1]:http://www.jianshu.com/p/af2993526daf
[2]:http://gityuan.com/2016/03/12/start-activity/