[置顶] Android(安卓)Binder跨进程与非跨进程的传输异同源码分析
前两天看到Service的onBind函数返回值的三种情况(扩展Binder对象,Messenger,aidl),一直在想他们内部的实现有什么不一样的,网上很多文章都介绍了Service的绑定过程,但是并没有介绍对于跨进程与非跨进程,对于不同的返回值,其具体有什么区别,以及具体是怎么实现的。这篇文章就根据源码分析Android究竟是在哪部分来控制跨进程与非跨进程Binder的传输的,Binder究竟是怎么传输的。
首先看一下Service的绑定中,Binder跨进程与非跨进程的区别代码中的体现。
Service跨进程绑定与非跨进程绑定的表象
我们都知道如果使用aidl跨进程在onServiceConnection函数中需要这样使用new TestInterface.Stub.Proxy(binder)
(TestInterface是aidl声明的接口,并且在Service的onBind中返回new TestInterface.Stub()
)。如果是普通扩展Binder对象,那么直接强制类型转换就可以了TestBinder(binder)
(TestBinder是Binder子类,并且在Service的onBind中返回new TestBinder()
)。如果想知道onServiceConnection的参数binder具体是怎么样的,其实直接在ServiceConnection的onServiceConnection回调函数中,以及在Service的onBind中分别打印一下Binder就好了,如下代码
//Service onBindpublic Binder onBind(){ Binder binder ; //这里并没有初始化,可以使扩展Binder,也可以是aidl的Stub Log.i("TestLog","onBind: "+binder.hashCode()+","+binder.toString()); return binder;}//ServiceConnectionServiceConnection connection = new ServiceConnection(){ public void onServiceConnection(ComponentName component, Binder binder){ Log.i("TestLog","onServiceConnection: "+binder.hashCode()+","+binder.toString());//跨进程时会binder是一个BinderProxy对象,非跨进程时跟onBind返回的对象一模一样。 }}
通过设置Service的android:process,可以将Service远程进程与相同进程进行实验。
通过log可以发现,如果是跨进程的话,在ServiceConnection的onServiceConnection返回的结果是一个BinderProxy对象,与onBind中打印的结果是不一致的。如果是非跨进程,在onServiceConnection与onBind的打印的内容是一模一样的。使用aidl跨进程与非跨进程,使用扩展Binder非跨进程都是一样的。这里跟onBind是返回扩展Binder,返回扩展aidl自动产生Stub类还是Messenger是没有关系的,只跟是否跨进程有关。
从这里可以看出,如果是跨进程则返回BinderProxy,非跨进程则返回原来在onBind中返回的对象。我困惑的问题是,Android是怎么实现这个根据不同的进程来返回不同的结果呢?因为是Service绑定,所以先看一下Service的绑定过程。
Service绑定过程
绑定过程
首先简单介绍一下Service的绑定过程:
1. Activity通过bindService请求绑定相应的Service(由Intent指定),并设置了ServiceConnection回调接口。
2. 实际上会调用ContextImpl.bindService,然后调用ActivityManagerNative.getDefault().bindService,也就是通过IPC调用ActivityManagerService的bindService函数,之后会调用ActiveServices的bindServiceLocked。
3. 在bindServiceLocked中,先通过retrieveServiceLocked创建ServiceRecord,这个就是对应着我们想要绑定的Service。
4. 然后根据结果创建ConnectionRecord,将ServiceConnection保存在这里面。
5. 判断指定的Service是否启动,如果没有则启动服务,通过ApplicationThreadProxy(app.thread)远程调用ApplicationThread的scheduleCreateService来发送Message给主线程消息循环(ActivityThread,ActivityThread.H),启动服务。如果是远程进程服务,ActivityManagerService会开一个新的进程(startProcessLocked),然后将Service运行在新的Process中。
6. 绑定服务,通过ApplicationThreadProxy(app.thread)远程调用ApplicationThread的scheduleBindService来发送Message给主线程消息循环(ActivityThread,ActivityThread.H),绑定服务(handleBindService)。这里看一下源码:
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) {}
- 在绑定服务中,调用Service的onBind方法得到Binder,并且通过ActivityManagerNative.getDefault().publishService发布服务。
- 在publishServcie中使用ServiceRecord获取ConnectionRecord,最终调用保存在ConnectionRecord里面的ServiceConnection的onServiceConnection函数。其中ServiceRecord,ServiceConnection都是Binder类,可以通过IPC访问。
这里只是简单介绍一下绑定Service的流程,也并没有把具体对象名称写出来。如果想要更详细的了解整个过程可以去看一下老罗的博客,其实最好是照着别人的博客看一下源码。
绑定过程是否处理了不同onBind返回值
我一直觉得是在绑定服务的过程中会针对不同的onBind返回值有不同的处理,但实际上并没有。看第6步中的源码就知道了,只是把onBind的返回值当作IBinder来看。另外上面的绑定过程中,并没有针对不同的IBinder类型进行特殊处理。我想实际上在onBind函数中返回一个BinderProxy也是OK的。
既然绑定过程中没有对不同进程进行处理,那么只能看看更底层的传输过程了,Android中传输都是用Parcel类型。所以只能看看Parcel传输数据的过程中是否有特殊处理。而实际上确实是在Parcel中有体现出来了跨进程与非跨进程的区别。下面看看Parcel读取和写入Binder。
Parcel中写入读取Binder
看一下Parcel关于Binder的接口:
public final void writeStrongBinder(IBinder val) { nativeWriteStrongBinder(mNativePtr, val);}public final IBinder readStrongBinder() { return nativeReadStrongBinder(mNativePtr);}
Parcel写入Binder
Parcel写入Binder是通过writeStrongBinder的,Java层的nativeWriteStrongBinder是一个native函数,对应的JNI实现为:
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:
status_t Parcel::writeStrongBinder(const sp<IBinder>& val){ return flatten_binder(ProcessState::self(), val, this);}
最终调用flatten_binder:
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:
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。
sp<IBinder> Parcel::readStrongBinder() const{ sp<IBinder> val; unflatten_binder(ProcessState::self(), *this, &val); return val;}
调用readStrongBinder的是jni函数的实现:
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。这个函数是关键。看看它的实现:
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;}
//IBinder的checkSubclass。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的值是不一样的:
1. 跨进程的时候flat_binder_object的type为BINDER_TYPE_HANDLE,
2. 非跨进程的时候flat_binder_object的type为BINDER_TYPE_BINDER。
在这里已经可以发现跨进程与非跨进程的时候传输的数据的区别了。客户端的Parcel在读取Binder的时候,根据是flat_binder_object的type的值进行区分对待,返回不同的内容。而写入的时候也是一样的,根据是否是Proxy,来决定写入HANDLE还是BINDER。
最终这些内容都会通过ioctl与Binder驱动进行数据通信。所以最终处理不同进程之间的Binder数据传输处理的也只能是Binder驱动了。
Binder驱动的处理
关于如何判断读取的时候是跟Service同一个进程还是不同的进程,脑子里根据操作系统的知识想想基本就能够想到怎么去实现了。因为每个进程进行系统调用陷入内核的时候,内核的当然是可以知道当前进入内核空间的进程的信息了啦,这样就可以判断当前请求读取信息的是跟Service同一个进程还是不同的进程了。
实际上Binder驱动保存着Service端的Binder地址和handle的信息,将两者相互映射,根据不同的服务端进程和客户端进程来区别处理。这篇文章详细介绍了Binder驱动深入分析Android Binder 驱动。
总结
实际上真正控制跨进程与非跨进程返回Binder类型的,不是绑定服务的过程,而是Binder驱动。如果是与Service端同一个进程则返回Binder(在底层是Binder指针),如果是不同的进程则返回handle。Binder对象传入Binder驱动最底层是转化为flat_binder_object对象传递的。Parcel是根据从驱动中读取的数据作出不同的处理,如果从Binder驱动中读出的flat_binder_object的type为BINDER_TYPE_HANDLE,则创建BpBinder,在JAVA层创建BinderProxy返回,如果读出的flat_binder_object的type为BINDER_TYPE_BINDER则直接使用cookie的指针,将它强制转化为JavaBBinder,在JAVA层为原来Service的Binder对象(相同进程)。
这样每次从Binder驱动中读取IBinder对象,都会这样:不同进程则用handle,同进程则用Service端的Binder地址。每个Binder对象的接口参数里面可能还有Binder变量,但同样按照上面的方式传输。其实如果是handle的话,通信就需要通过Binder驱动去做。
虽然是从Service的绑定过程中想到这个问题,但是Service的绑定过程并没有针对跨进程与非跨进程对Binder传输有过处理,所以Android中任何地方的Binder跨进程与非跨进程的传输都是这样的。从Binder驱动读取Binder对象的时候,Binder驱动判断请求的进程与Service端的进程是否相同,如果相同则Binder驱动传出的数据为Service端Binder的指针(flat_binder_object的type为BINDER),如果不同则Binder驱动传出的数据为Service端Binder的handle(flat_binder_object的type为HANDLE)。
积跬步,至千里;积小流,成江海
更多相关文章
- 一款霸榜 GitHub 的开源 Linux 资源监视器!
- android应用创建子进程的方法探究
- 重温String和StringBuffer
- Android(安卓)Training - 管理应用的内存
- android中进程优先级问题
- [置顶] 如何写Android(安卓)init.rc
- postDelayed方法和removeCallbacks方法的使用(1)
- 浅析Java 对象初始化
- Android(安卓)重学系列 有趣的工具--智能指针与智能锁