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 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());//跨程式時會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)。
積跬步,至千里;積小流,成江海
相關文章
- [Hook] 跨程式 Binder設計與實現 - 設計篇Hook
- Android跨程式通訊之非AIDL(二)AndroidAI
- C++ 郵件槽ShellCode跨程式傳輸C++
- C++ 共享記憶體ShellCode跨程式傳輸C++記憶體
- 一行程式碼實現Android的跨程式呼叫與通訊行程Android
- Chromium中跨程式檔案控制程式碼傳遞
- Android跨程式通訊Android
- 完成資料的跨界傳輸與驗證
- RMAN跨小版本跨平臺與位元組序傳輸表空間
- 跨國檔案傳輸軟體有哪些?跨國檔案傳輸遇到的問題
- 一文了解低程式碼與無程式碼:異同點
- 【Android原始碼】Binder機制和AIDL分析Android原始碼AI
- 跨國跨地域遠端檔案傳輸用什麼好呢?
- Android 之 Binder與程式間通訊Android
- 鐳速傳輸是如何管理大檔案跨國傳輸的
- HarmonyOS跨裝置通訊:多端協同的RPC資料傳輸實現RPC
- android 跨程式點選方式總結Android
- Android跨程式元件IPCInvoker用法完全解析Android元件
- IPCInvoker,Android跨程式呼叫如此簡單Android
- oracle跨版本與平臺執行傳輸表空間Oracle
- 跨平臺表空間傳輸的實現
- 【漫畫技術】Android跨程式通訊Android
- 從AIDL看Android跨程式通訊AIAndroid
- Android多程式之Binder的使用Android
- window.name實現的跨域資料傳輸跨域
- 叮咚!您有一份跨網域跨安全域檔案傳輸方案待查收
- 跨國檔案傳輸用什麼軟體?
- 12c跨平臺傳輸表空間
- 檢視可跨系統可傳輸資料
- 10g跨平臺傳輸表空間
- Ajax與Flask傳值的跨域問題Flask跨域
- Binder學習(一)Android中的程式Android
- Android ContentProvider支援跨程式資料共享與"互斥、同步" 雜談AndroidIDE
- Sublime Text——高效的跨平臺程式碼編輯器
- ajax實現的跨域請求程式碼例項跨域
- Google Inbox 是如何跨平臺重用程式碼的?Go
- 程式設計師與非程式設計師的思維差異程式設計師
- 簡單跨程式使用EventBus