Android Binder跨程式與非跨程式的傳輸異同原始碼分析

_houzhi發表於2016-04-30

前兩天看到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) {
}
  1. 在繫結服務中,呼叫Service的onBind方法得到Binder,並且通過ActivityManagerNative.getDefault().publishService釋出服務。
  2. 在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)。


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

相關文章