Binder牌胶水,如雷贯耳,在Android中无处不在,是每个Android程序猿居家旅行必备。有了它的存在,我们甚至可以不用深入了解App进程和系统进程、用户空间和内核空间、跨进程通讯等概念也可以做好应用层开发。但是呢,阅读本文之前,还是建议先看看《Binder学习指南》了解下这些概念。
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胶水的原始配方
image如上图,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模型图:
image1. 熟悉的配方: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的工作原理。如下:
4. 这瓶胶水粘合了什么?
这个问题对于AIDL来说很简单,他粘合了需要通讯的两个进程,在上文我们称之为Server进程和Client进程,在上文的UML类图中也有所体现。
第二瓶胶水ContentProvider
为了巩固这个模型,Binder牌胶水隆重推出第二个款式ContentProvider。谈起跨进程,我们自然而然会想起内容提供者。即使你不了解ContentProvider的原理,我们平常在使用api的时候,所做的事情就是让内容使用者进程去访问内容提供者进程的数据,所以这第二瓶胶水很贴近我们日常的Android开发生活。
首先先给出结论,也就是有关ContentProvider的Binder模型图:
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](https://link.jianshu.com?t=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模型 ApplicationThread的Binder模型看到这两个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启动流程图注:此张图片来源于这篇文章《startActivity流程分析(一)》,已征得博主同意引用在此处。
在这张图里,我们意识到除了第一、二瓶胶水描述到的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学习指南
startActivity流程分析(一)
Android应用程序组件Content Provider简要介绍和学习计划
Android进程间通信(IPC)机制Binder简要介绍和学习计划
作者:geniusmart
链接:https://www.jianshu.com/p/3d053abba04b
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
网友评论