Binder 牌胶水


Binder牌胶水,如雷贯耳,在Android中无处不在,是每个Android程序猿居家旅行必备。有了它的存在,我们甚至可以不用深入了解App进程和系统进程、用户空间和内核空间、跨进程通讯等概念也可以做好应用层开发。但是呢,阅读本文之前,还是建议先看看[《Binder学习指南》][1]了解下这些概念。

Binder属于“问题领域”的命名?见名知意,作为胶水,“粘合”是他的主要职责。在Androd世界里,他到底粘合了什么?比如我们经常使用的startActivity()就涉及到进程通讯,Binder粘合了这些进程,弱化了我们对进程的感知,降低了开发难度。

本文来聊聊四瓶Binder胶水,他们功效不同,配方却完全一致。此文的重点有两个,其一,介绍下他们的配方,也就是类图模型,了解他们,对我们阅读Android源码,寻找源码的位置有很大的帮助;其二,既然作为胶水,我们要研究下他粘合了什么东西,粘合了哪些进程。这四瓶胶水的功效和配方分别是:

  1. AIDL:
    IInterface--Stub--Proxy--Stub具体实现
  2. ContentProvider:
    IContentProvider--ContentProviderNative--ContentProviderProxy--ContentProvider.Transport
  3. 管理四大组件的AMS:
    IActivityManager--ActivityManagerNative--ActivityManagerProxy--ActivityManagerService
  4. 负责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里,SerializableParcelable代表其实现类是可序列化的;Iterable代表可迭代遍历,因此其实现类HashSetArrayList可使用Iterator进行遍历。

在这个类图的最顶层,有两个接口,IInterfaceIBinderIBinder代表跨进程传输的能力,而IInterface则代表远程服务端具备的能力

BinderIBinder的实现类,因此它具备跨进程传输的能力,它实际上就是远程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模型图:

AIDL的Binder模型

1. 熟悉的配方:ICompute-Proxy-Stub-Stub具体实现

通过上图,我们发现AIDL的Binder模型是基于原始配方的扩展。当我们写完ICompute.aidl之后,IComputeStubProxy已经自动生成出来,他们的作用如下:

  • ICompute接口继承了IInterface,并写了add(a,b)的方法,代表Server端进程具备计算两数相加的能力。此方法将在Stub的具体实现类中重写。
  • Stub是一个抽象类,他本质是一个Binder,他存在的目的之一是约定好asInterfaceasBinderonTransact方法,减少我们开发具体Binder对象的工作量。此外,Stub即需要跨进程传输,又需要约定远端服务端具备的能力,因此他需要同时实现IInterfaceIBinder接口,通过上文的类图可以看得出来。
  • 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/
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 原文:http://weishu.me/2016/01/12/binder-index-for-newer/ 要点...
    指尖流逝的青春阅读 2,586评论 0 13
  • 毫不夸张地说,Binder是Android系统中最重要的特性之一;正如其名“粘合剂”所喻,它是系统间各个组件的桥梁...
    weishu阅读 17,533评论 29 246
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • Android跨进程通信IPC整体内容如下 1、Android跨进程通信IPC之1——Linux基础2、Andro...
    隔壁老李头阅读 11,474评论 11 56
  • Binder浅析 1. 背景知识 Binder在Android系统中是用来进行进程间通信的,所以在介绍Binder...
    蕉下孤客阅读 2,166评论 0 8