Android Binder跨进程与非跨进程的传输异同源码分析

前两天看到Service的onBind函数返回值的三种情况(扩展Binder对象,Messenger,aidl),一直在想他们内部的实现有什么不一样的,网上很多文章都介绍了Service的绑定过程,但是并没有介绍对于跨进程与非跨进程,对于不同的返回值,其具体有什么区别,以及具体是怎么实现的。这篇文章就根据源码分析Android究竟是在哪部分来控制跨进程与非跨进程Binder的传输的,Binder究竟是怎么传输的。

首先看一下Service的绑定中,Binder跨进程与非跨进程的区别代码中的体现。

Service跨进程绑定与非跨进程绑定的表象

想知道具体是怎么样的,其实直接在ServiceConnection的onServiceConnection回调函数中,以及在Service的onBind中分别打印一下Binder就好了,如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
//Service onBind
public Binder onBind(){
Binder binder ; //这里并没有初始化,可以使扩展Binder,也可以是aidl的Stub
Log.i("TestLog","onBind: "+binder.hashCode()+","+binder.toString());
return binder;
}
//ServiceConnection
ServiceConnection connection = new ServiceConnection(){
public void onServiceConnection(ComponentName component, Binder binder){
Log.i("TestLog","onServiceConnection: "+binder.hashCode()+","+binder.toString());
}
}

通过设置Service的android:process,可以将Service远程进程与相同进程进行实验。
通过log可以发现,如果是跨进程的话,在ServiceConnection的onServiceConnection返回的结果是一个BinderProxy对象,与onBind中打印的结果是不一致的。如果是非跨进程,在onServiceConnection与onBind的打印的内容是一模一样的。这里跟onBind是返回扩展Binder还是返回扩展aidl自动产生Stub类是没有关系的,只跟是否跨进程有关

从这里可以看出,如果是跨进程则返回BinderProxy,非跨进程则返回原来在onBind中返回的对象。我困惑的问题是,Android是怎么实现这个根据不同的进程来返回不同的结果呢?下面先看一下Service的绑定过程是否有针对不同进程的区别处理。

Service绑定过程

绑定过程

首先简单介绍一下Service的绑定过程:

  1. Activity通过bindService请求绑定相应的Service(由Intent指定),并设置了ServiceConnection回调接口。
  2. 实际上会调用ContextImpl.bindService,然后调用ActivityManagerNative.getDefault().bindService,也就是通过IPC调用ActivityManagerService的bindService函数。
  3. 在ActivityManagerService的bindService函数中,先通过retrieve创建ServiceRecord,这个就是对应着我们想要绑定的Service
  4. 然后根据结果创建ConnectionRecord,将ServiceConnection保存在这里面。ConnectionRecord保存在一个ArrayList中,可能有多个客户端想要绑定。
  5. 接着在bringUpServiceLocked函数中判断是否需要重新创建一个进程来运行Service,根据android:processd的设置
  6. 启动服务,通过ApplicationThreadProxy(app.thread)远程调用ApplicationThread的scheduleCreateService来发送Message给主线程消息循环(ActivityThread,ActivityThread.H),启动服务。
  7. 绑定服务,通过ApplicationThreadProxy(app.thread)远程调用ApplicationThread的scheduleBindService来发送Message给主线程消息循环(ActivityThread,ActivityThread.H),绑定服务(handleBindService)。这里看一下源码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    try {
    if (!data.rebind) {
    IBinder binder = s.onBind(data.intent);
    ActivityManagerNative.getDefault().publishService(
    data.token, data.intent, binder);
    } else {
    s.onRebind(data.intent);
    ActivityManagerNative.getDefault().serviceDoneExecuting(
    data.token, 0, 0, 0);
    }
    ensureJitEnabled();
    } catch (RemoteException ex) {
    }
  8. 在绑定服务中,调用Service的onBind方法得到Binder,并且通过ActivityManagerNative.getDefault().publishService发布服务。

  9. 在publishServcie中使用ServiceRecord获取ConnectionRecord,最终调用保存在ConnectionRecord里面的ServiceConnection的onServiceConnection函数。其中ServiceRecord,ServiceConnection都是Binder类,可以通过IPC访问。

这里只是简单介绍一下绑定Service的流程,也并没有把具体对象名称写出来。如果想要更详细的了解整个过程可以去看一下老罗的博客,其实最好是照着别人的博客看一下源码。

绑定过程是否处理了不同onBind返回值

我一直觉得是在绑定服务的过程中会针对不同的onBind返回值有不同的处理,但实际上并没有。看第7步中的源码就知道了,上面的过程中,并没有针对不同的onBind返回值进行特殊处理。

既然绑定过程中没有对不同进程进行处理,那么只能看看Parcel了。而实际上确实是在Parcel中有体现出来了跨进程与非跨进程的区别。下面看看Parcel读取和写入Binder。

Parcel中写入读取Binder

Parcel写入Binder

Parcel写入Binder是通过writeStrongBinder的,Java层的writeStrongBinder是一个native函数,对应的JNI实现为:

1
2
3
4
5
6
7
8
9
10
static void android_os_Parcel_writeStrongBinder(JNIEnv* env, jclass clazz, jlong nativePtr, jobject object)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel != NULL) {
const status_t err = parcel->writeStrongBinder(ibinderForJavaObject(env, object));
if (err != NO_ERROR) {
signalExceptionForError(env, clazz, err);
}
}
}

其中ibinderForJavaObject是将java层的对象转换成native层的Binder对象,实际上对应的是JavaBBinder。

然后native层的parcel会调用它的writeStrognBinder:

1
2
3
4
status_t Parcel::writeStrongBinder(const sp<IBinder>& val)
{
return flatten_binder(ProcessState::self(), val, this);
}

最终调用flatten_binder:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
status_t flatten_binder(const sp<ProcessState>& /*proc*/,
const sp<IBinder>& binder, Parcel* out)
{
flat_binder_object obj;
obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
if (binder != NULL) {
IBinder *local = binder->localBinder(); //JavaBBinder返回的是this,也就是自己
if (!local) {
BpBinder *proxy = binder->remoteBinder();
if (proxy == NULL) {
ALOGE("null proxy");
}
const int32_t handle = proxy ? proxy->handle() : 0;
obj.type = BINDER_TYPE_HANDLE;
obj.binder = 0; /* Don't pass uninitialized stack data to a remote process */
obj.handle = handle;
obj.cookie = 0;
} else {// 写入JavaBBinder将会对应这一段。
obj.type = BINDER_TYPE_BINDER;
obj.binder = reinterpret_cast<uintptr_t>(local->getWeakRefs());
obj.cookie = reinterpret_cast<uintptr_t>(local); //把对应的JavaBBinder的指针转换成整形uintptr_t
}
} else {
obj.type = BINDER_TYPE_BINDER;
obj.binder = 0;
obj.cookie = 0;
}
return finish_flatten_binder(binder, obj, out); //finish_flatten_binder是将obj写入到out里面。
}

这里就根据是本地binder还是远程binder,对Binder的写入采取了两种不同的方式。Binder如果是JavaBBinder,则它的localBinder会返回localBinder,如果是BpBinder则localBinder会为null。我们写入的时候,ibinderForJavaObject就返回的是JavaBBinder。flat_binder_object是Binder写入的时候的对象,它对应着Binder。handle表示Binder对象在Binder驱动中的标志,比如ServiceManager的handle为0。type表示当前传输的Binder是本地的(同进程),还是一个proxy(跨进程)。binder,cookie保存着Binder对象的指针。finish_flatten_binder会将obj写入到out里面,最终写入到Binder驱动中,写入部分也就完了。从上面的流程可以看出,在写入的时候,Parcel会针对不同的Binder(BBinder/JavaBBinder,BpBinder)有不同的处理,而他们确实就是对应着跟Service端Binder是同一个进程的还是不同进程的情况。

Parcel读取Binder

接下来就是读取了,同样的,Java层的Parcel也是通过native函数来读取的。在这里我们从最底层开始分析,首先从unflatten_binder开始:

unflatten_binder:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
status_t unflatten_binder(const sp<ProcessState>& proc,
const Parcel& in, sp<IBinder>* out)
{
const flat_binder_object* flat = in.readObject(false);
if (flat) {
switch (flat->type) {
case BINDER_TYPE_BINDER:
*out = reinterpret_cast<IBinder*>(flat->cookie);
return finish_unflatten_binder(NULL, *flat, in);
case BINDER_TYPE_HANDLE:
*out = proc->getStrongProxyForHandle(flat->handle);
return finish_unflatten_binder(
static_cast<BpBinder*>(out->get()), *flat, in);
}
}
return BAD_TYPE;
}

首先从Binder驱动中读取一个flat_binder_object—flat。flat的处理是关键,它会根据flat->type的值分别处理,如果是BINDER_TYPE_BINDER,则使用cookie中的值强制转换成指针。如果是BINDER_TYPE_HANDLE,则使用Proxy,getStringProxyForHandle会返回BpBinder。而调用unflatten_binder的是native层的Parcel的readStringBinder。

1
2
3
4
5
6
sp<IBinder> Parcel::readStrongBinder() const
{
sp<IBinder> val;
unflatten_binder(ProcessState::self(), *this, &val);
return val;
}

调用readStrongBinder的是jni函数的实现:

1
2
3
4
5
6
7
8
static jobject android_os_Parcel_readStrongBinder(JNIEnv* env, jclass clazz, jlong nativePtr)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel != NULL) {
return javaObjectForIBinder(env, parcel->readStrongBinder());
}
return NULL;
}

javaObjectForIBinder与ibinderForJavaObject相对应,把IBinder对象转换成对应的Java层的Object。这个函数是关键。看看它的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
jobject javaObjectForIBinder(JNIEnv* env, const sp<IBinder>& val)
{
if (val == NULL) return NULL;
if (val->checkSubclass(&gBinderOffsets)) { //如果是本地的,那么会直接进入这部分代码,因为这个val是写入的时候同一个对象,gBinderOffsets也是一致。如果val是一种proxy对象,则不然,会继续往下执行找到一个Proxy对象。
// One of our own!
jobject object = static_cast<JavaBBinder*>(val.get())->object();
LOGDEATH("objectForBinder %p: it's our own %p!\n", val.get(), object);
return object;
}
// For the rest of the function we will hold this lock, to serialize
// looking/creation of Java proxies for native Binder proxies.
AutoMutex _l(mProxyLock);
// Someone else's... do we know about it?
jobject object = (jobject)val->findObject(&gBinderProxyOffsets);
if (object != NULL) {
jobject res = jniGetReferent(env, object);
if (res != NULL) {
ALOGV("objectForBinder %p: found existing %p!\n", val.get(), res);
return res;
}
LOGDEATH("Proxy object %p of IBinder %p no longer in working set!!!", object, val.get());
android_atomic_dec(&gNumProxyRefs);
val->detachObject(&gBinderProxyOffsets);
env->DeleteGlobalRef(object);
}
object = env->NewObject(gBinderProxyOffsets.mClass, gBinderProxyOffsets.mConstructor);//gBinderProxyOffsets保存的是Java层BinderProxy的信息,这里也是创建BinderProxy。
if (object != NULL) {
LOGDEATH("objectForBinder %p: created new proxy %p !\n", val.get(), object);
// The proxy holds a reference to the native object.
env->SetLongField(object, gBinderProxyOffsets.mObject, (jlong)val.get());
val->incStrong((void*)javaObjectForIBinder);
// The native object needs to hold a weak reference back to the
// proxy, so we can retrieve the same proxy if it is still active.
jobject refObject = env->NewGlobalRef(
env->GetObjectField(object, gBinderProxyOffsets.mSelf));
val->attachObject(&gBinderProxyOffsets, refObject,
jnienv_to_javavm(env), proxy_cleanup);
// Also remember the death recipients registered on this proxy
sp<DeathRecipientList> drl = new DeathRecipientList;
drl->incStrong((void*)javaObjectForIBinder);
env->SetLongField(object, gBinderProxyOffsets.mOrgue, reinterpret_cast<jlong>(drl.get()));
// Note that a new object reference has been created.
android_atomic_inc(&gNumProxyRefs);
incRefsCreated(env);
}
return object;
}
1
2
3
4
5
//IBinder
checkSubclass(const void* subclassID) const
{
return subclassID == &gBinderOffsets;
}

上面的处理中,如果是跟Service是同一个进程,也就是val是JavaBBinder。那么在checkSubclass中,它所包含的gBinderOffsets指针与参数传入的gBinderOffsets的指针必然是同一个值,则满足if条件。直接将指针强制转换成JavaBBinder,返回对应的jobject。如果是不同的进程,那么val也就会是BpBinder,最终会返回一个BinderProxy,不同的进程这一部分网上很多介绍Binder的文章都介绍了。

Parcel读取写入总结

上面介绍了Parcel整个写入读取的流程,最后代替Binder传输的是flat_binder_object。在native的Parcel中,根据跨进程还是非跨进程,flat_binder_object的值是不一样的,跨进程的时候flat_binder_object的type为BINDER_TYPE_HANDLE,非跨进程的时候flat_binder_object的type为BINDER_TYPE_BINDER。在这里已经可以发现跨进程与非跨进程的时候传输的数据的区别了。客户端的Parcel在读取Binder的时候,根据是flat_binder_object的type的值进行区分对待,返回不同的内容。

Binder驱动的处理

但是我们还没有发现Android是如何区分跨进程与非跨进程的呢?显然它的实现在Binder驱动里面。关于如何判断读取的时候是跟Service同一个进程还是不同的进程,脑子里随便一想基本就能够想到怎么去实现了。因为每个进程进行系统调用陷入内核的时候,内核的当然是可以知道当前进入内核空间的进程的信息了啦,这样就可以判断当前请求读取信息的是跟Service同一个进程还是不同的进程了。

实际上Binder驱动保存着Service端的Binder地址和handle的信息,将两者相互映射,根据不同的服务端进程和客户端进程来区别处理。这篇文章详细介绍了Binder驱动深入分析Android Binder 驱动

总结

实际上真正控制跨进程与非跨进程返回Binder类型的,不是绑定服务的过程,而是Binder驱动。Parcel是根据从驱动中读取的数据作出不同的处理,而Binder驱动真正控制。如果是与Service端同一个进程则返回Binder(在底层是Binder指针),如果是不同的进程则返回handle。真正在传输的过程中,是作为flat_binder_object对象来传递的。

其实也可以在Service的绑定过程中来根据是否是相同进程来进行处理的,但那样就局限于Service的绑定部分了,而Android中用的Binder传输的实在是太多了,所以直接在Binder驱动中进行控制更加方便。


积跬步,至千里;积小流,成江海