[深入理解Android卷二 全文-第二章]深入理解Java Binder和MessageQueue

阿拉神農發表於2015-08-03

 

由於《深入理解Android 卷一》和《深入理解Android卷二》不再出版,而知識的傳播不應該因為紙質媒介的問題而中斷,所以我將在CSDN部落格中全文轉發這兩本書的全部內容

 第2章  深入理解Java Binder和MessageQueue

本章主要內容:

·  介紹Binder系統的Java層框架

·  介紹MessageQueue

本章所涉及的原始碼檔名及位置:

·  IBinder.java

frameworks/base/core/java/android/os/IBinder.java

·  Binder.java

frameworks/base/core/java/android/os/Binder.java

·  BinderInternal.java

frameworks/base/core/java/com/android/intenal/os/BinderInternal.java

·  android_util_Binder.cpp

frameworks/base/core/jni/android_util_Binder.cpp

·  SystemServer.java

frameworks/base/services/java/com/android/servers/SystemServer.java

·  ActivityManagerService.java

frameworks/base/services/java/com/android/servers/ActivityManagerService.java

·  ServiceManager.java

frameworks/base/core/java/android/os/ServiceManager.java

·  ServcieManagerNative.java

frameworks/base/core/java/android/os/ ServcieManagerNative.java

·  MessageQueue.java

frameworks/base/core/java/android/os/MessageQueue.java

·  android_os_MessageQueue.cpp

frameworks/base/core/jni/android_os_MessageQueue.cpp

·  Looper.cpp

frameworks/base/native/android/Looper.cpp

·  Looper.h

frameworks/base/include/utils/Looper.h

·  android_app_NativeActivity.cpp

frameworks/base/core/jni/android_app_NativeActivity.cpp

2.1  概述

以本章做為本書Android分析之旅的開篇,將重點關注兩個基礎知識點,它們是:

·  Binder系統在Java世界是如何佈局和工作的

·  MessageQueue的新職責

先來分析Java層中的Binder。

建議讀者先閱讀《深入理解Android:卷I》(以下簡稱“卷I”)的第6章“深入理解Binder”。網上有樣章可下載。

2.2  Java層中的Binder分析

2.2.1  Binder架構總覽

如果讀者讀過卷I第6章“深入理解Binder”,相信就不會對Binder架構中代表Client的Bp端及代表Server的Bn端感到陌生。Java層中Binder實際上也是一個C/S架構,而且其在類的命名上儘量保持與Native層一致,因此可認為,Java層的Binder架構是Native層Binder架構的一個映象。Java層的Binder架構中的成員如圖2-1所示。


圖2-1  Java層中的Binder家族

由圖2-1可知:

·  系統定義了一個IBinder介面類以及DeathRecepient介面。

·  Binder類和BinderProxy類分別實現了IBinder介面。其中Binder類作為服務端的Bn的代表,而BinderProxy作為客戶端的Bp的代表。

·  系統中還定義一個BinderInternal類。該類是一個僅供Binder框架使用的類。它內部有一個GcWatcher類,該類專門用於處理和Binder相關的垃圾回收。

·  Java層同樣提供一個用於承載通訊資料的Parcel類。

注意,IBinder介面類中定義了一個叫FLAG_ONEWAY的整型,該變數的意義非常重要。當客戶端利用Binder機制發起一個跨程式的函式呼叫時,呼叫方(即客戶端)一般會阻塞,直到服務端返回結果。這種方式和普通的函式呼叫是一樣的。但是在呼叫Binder函式時,在指明瞭FLAG_ONEWAY標誌後,呼叫方只要把請求傳送到Binder驅動即可返回,而不用等待服務端的結果,這就是一種所謂的非阻塞方式。在Native層中,涉及的Binder呼叫基本都是阻塞的,但是在Java層的framework中,使用FLAG_ONEWAY進行Binder呼叫的情況非常多,以後經常會碰到。

思考 使用FLAG_ONEWAY進行函式呼叫的程式在設計上有什麼特點?這裡簡單分析一下:對於使用FLAG_ONEWAY的函式來說,客戶端僅向服務端發出了請求,但是並不能確定服務端是否處理了該請求。所以,客戶端一般會向服務端註冊一個回撥(同樣是跨程式的Binder呼叫),一旦服務端處理了該請求,就會呼叫此回撥來通知客戶端處理結果。當然,這種回撥函式也大多采用FLAG_ONEWAY的方式。

2.2.2  初始化Java層Binder框架

雖然Java層Binder系統是Native層Binder系統的一個Mirror,但這個Mirror終歸還需藉助Native層Binder系統來開展工作,即Mirror和Native層Binder有著千絲萬縷的關係,一定要在Java層Binder正式工作之前建立這種關係。下面分析Java層Binder框架是如何初始化的。

在Android系統中,在Java初創時期,系統會提前註冊一些JNI函式,其中有一個函式專門負責搭建Java Binder和Native Binder互動關係,該函式是register_android_os_Binder,程式碼如下:

[-->android_util_Binder.cpp]

int register_android_os_Binder(JNIEnv* env)

{

    //初始化Java Binder類和Native層的關係

    if(int_register_android_os_Binder(env) < 0)

       return -1;

    //初始化Java BinderInternal類和Native層的關係

    if(int_register_android_os_BinderInternal(env) < 0)

       return -1;

   //初始化Java BinderProxy類和Native層的關係

    if(int_register_android_os_BinderProxy(env) < 0)

       return -1;

   //初始化Java Parcel類和Native層的關係

    if(int_register_android_os_Parcel(env) < 0)

       return -1;

    return0;

}

據上面的程式碼可知,register_android_os_Binder函式完成了Java Binder架構中最重要的4個類的初始化工作。我們重點關注前3個。

1.  Binder類的初始化

int_register_android_os_Binder函式完成了Binder類的初始化工作,程式碼如下:

[-->android_util_Binder.cpp]

static int int_register_android_os_Binder(JNIEnv*env)

{

  jclassclazz;

  //kBinderPathName為Java層中Binder類的全路徑名,“android/os/Binder“

  clazz =env->FindClass(kBinderPathName);

  /*

  gBinderOffSets是一個靜態類物件,它專門儲存Binder類的一些在JNI層中使用的資訊,

  如成員函式execTranscat的methodID,Binder類中成員mObject的fildID

  */

   gBinderOffsets.mClass = (jclass) env->NewGlobalRef(clazz);

   gBinderOffsets.mExecTransact

                     = env->GetMethodID(clazz,"execTransact", "(IIII)Z");

   gBinderOffsets.mObject

                     = env->GetFieldID(clazz,"mObject", "I");

   //註冊Binder類中native函式的實現

    returnAndroidRuntime::registerNativeMethods(

                            env, kBinderPathName,

                            gBinderMethods,NELEM(gBinderMethods));

}

從上面程式碼可知,gBinderOffsets物件儲存了和Binder類相關的某些在JNI層中使用的資訊。

建議 如果讀者對JNI不是很清楚,可參閱卷I第2章“深入理解JNI”。

2.  BinderInternal類的初始化

下一個初始化的類是BinderInternal,其程式碼在int_register_android_os_BinderInternal函式中。

[-->android_util_Binder.cpp]

static intint_register_android_os_BinderInternal(JNIEnv* env)

{

   jclass clazz;

   //根據BinderInternal的全路徑名找到代表該類的jclass物件。全路徑名為

   // “com/android/internal/os/BinderInternal”

   clazz =env->FindClass(kBinderInternalPathName);

   //gBinderInternalOffsets也是一個靜態物件,用來儲存BinderInternal類的一些資訊

   gBinderInternalOffsets.mClass = (jclass)env->NewGlobalRef(clazz);

   //獲取forceBinderGc的methodID

  gBinderInternalOffsets.mForceGc

                 = env->GetStaticMethodID(clazz,"forceBinderGc", "()V");

     //註冊BinderInternal類中native函式的實現

    return AndroidRuntime::registerNativeMethods(

                        env,kBinderInternalPathName,

                         gBinderInternalMethods, NELEM(gBinderInternalMethods));

}

int_register_android_os_BinderInternal的工作內容和int_register_android_os_Binder的工作內容類似:

·  獲取一些有用的methodID和fieldID。這表明JNI層一定會向上呼叫Java層的函式。

·  註冊相關類中native函式的實現。

3.  BinderProxy類的初始化

int_register_android_os_BinderProxy完成了BinderProxy類的初始化工作,程式碼稍顯複雜,如下所示:

[-->android_util_Binder.cpp]

static intint_register_android_os_BinderProxy(JNIEnv* env)

{

    jclassclazz;

  

   clazz =env->FindClass("java/lang/ref/WeakReference");

   //gWeakReferenceOffsets用來和WeakReference類打交道

   gWeakReferenceOffsets.mClass = (jclass)env->NewGlobalRef(clazz);

   //獲取WeakReference類get函式的MethodID

   gWeakReferenceOffsets.mGet= env->GetMethodID(clazz, "get",

                                    "()Ljava/lang/Object;");

    clazz = env->FindClass("java/lang/Error");

    //gErrorOffsets用來和Error類打交道

   gErrorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);

 

    clazz =env->FindClass(kBinderProxyPathName);

    //gBinderProxyOffsets用來和BinderProxy類打交道

   gBinderProxyOffsets.mClass = (jclass) env->NewGlobalRef(clazz);

   gBinderProxyOffsets.mConstructor= env->GetMethodID(clazz,"<init>", "()V");

    ...... //獲取BinderProxy的一些資訊

    clazz =env->FindClass("java/lang/Class");

    //gClassOffsets用來和Class類打交道

    gClassOffsets.mGetName=env->GetMethodID(clazz,

                              "getName","()Ljava/lang/String;");

    //註冊BinderProxy native函式的實現

    returnAndroidRuntime::registerNativeMethods(env,

          kBinderProxyPathName,gBinderProxyMethods,

                                NELEM(gBinderProxyMethods));

}

據上面程式碼可知,int_register_android_os_BinderProxy函式除了初始化BinderProxy類外,還獲取了WeakReference類和Error類的一些資訊。看來BinderProxy物件的生命週期會委託WeakReference來管理,難怪JNI層會獲取該類get函式的MethodID。

至此,Java Binder幾個重要成員的初始化已完成,同時在程式碼中定義了幾個全域性靜態物件,分別是gBinderOffsets、gBinderInternalOffsets和gBinderProxyOffsets。

這幾個物件的命名中都有一個Offsets,我覺得這非常彆扭,不知道讀者是否有同感。

框架的初始化其實就是提前獲取一些JNI層的使用資訊,如類成員函式的MethodID,類成員變數的fieldID等。這項工作是必需的,因為它能節省每次使用時獲取這些資訊的時間。當Binder呼叫頻繁時,這些時間累積起來還是不容小覷的。

下面我們通過一個例子來分析Java Binder的工作流程。

2.2.3  窺一斑,可見全豹乎

這個例子源自ActivityManagerService,我們試圖通過它揭示Java層Binder的工作原理。先來描述一下該例子的分析步驟:

·  首先分析AMS如何將自己註冊到ServiceManager。

·  然後分析AMS如何響應客戶端的Binder呼叫請求。

本例的起點是setSystemProcess,其程式碼如下所示:

[-->ActivityManagerService.java]

public static void setSystemProcess() {

 try {

        ActivityManagerService m = mSelf;

        //將ActivityManagerService服務註冊到ServiceManager中

       ServiceManager.addService("activity", m);......

      }

   ......

   return;

}

上面所示程式碼行的目的是將ActivityManagerService服務加到ServiceManager中。ActivityManagerService(以後簡稱AMS)是Android核心服務中的核心,我們以後會經常和它打交道。

大家知道,整個Android系統中有一個Native的ServiceManager(以後簡稱SM)程式,它統籌管理Android系統上的所有Service。成為一個Service的首要條件是先在SM中註冊。下面來看Java層的Service是如何向SM註冊的。

1.  向ServiceManager註冊服務

(1) 建立ServiceManagerProxy

向SM註冊服務的函式叫addService,其程式碼如下:

[-->ServiceManager.java]

public static void addService(String name, IBinderservice) {

  try {

          //getIServiceManager返回什麼

         getIServiceManager().addService(name,service);

     }

     ......

 }

//分析getIServiceManager函式

private static IServiceManagergetIServiceManager() {

    ......

    //呼叫asInterface,傳遞的引數型別為IBinder       

    sServiceManager= ServiceManagerNative.asInterface(

                        BinderInternal.getContextObject());

    returnsServiceManager;

}

asInterface的引數為BinderInternal.getContextObject的返回值。這是一個native的函式,其實現的程式碼為:

[-->android_util_Binder.cpp]

static jobjectandroid_os_BinderInternal_getContextObject(

JNIEnv* env, jobject clazz)

{

   /*

    下面這句程式碼,我們在卷I第6章詳細分析過,它將返回一個BpProxy物件,其中

    NULL(即0,用於標識目的端)指定Proxy通訊的目的端是ServiceManager

   */

    sp<IBinder>b = ProcessState::self()->getContextObject(NULL);

    //由Native物件建立一個Java物件,下面分析該函式

    returnjavaObjectForIBinder(env, b);

}

[-->android_util_Binder.cpp]

jobject javaObjectForIBinder(JNIEnv* env, constsp<IBinder>& val)

{

   //mProxyLock是一個全域性的靜態CMutex物件

    AutoMutex_l(mProxyLock);

 

  /*

    val物件實際型別是BpBinder,讀者可自行分析BpBinder.cpp中的findObject函式。

    事實上,在Native層的BpBinder中有一個ObjectManager,它用來管理在Native BpBinder

    上建立的Java BpBinder物件。下面這個findObject用來判斷gBinderProxyOffsets

    是否已經儲存在ObjectManager中。如果是,那就需要刪除這個舊的object

  */

jobject object =(jobject)val->findObject(&gBinderProxyOffsets);

    if(object != NULL) {

       jobject res = env->CallObjectMethod(object, gWeakReferenceOffsets.mGet);

       android_atomic_dec(&gNumProxyRefs);

       val->detachObject(&gBinderProxyOffsets);

       env->DeleteGlobalRef(object);

    }

   

     //建立一個新的BinderProxy物件,並註冊到Native BpBinder物件的ObjectManager中

        object= env->NewObject(gBinderProxyOffsets.mClass,

                            gBinderProxyOffsets.mConstructor);

    if(object != NULL) {

       env->SetIntField(object, gBinderProxyOffsets.mObject,(int)val.get());

       val->incStrong(object);

       jobject refObject = env->NewGlobalRef(

               env->GetObjectField(object, gBinderProxyOffsets.mSelf));

        /*

        將這個新建立的BinderProxy物件註冊(attach)到BpBinder的ObjectManager中,

       同時註冊一個回收函式proxy_cleanup。當BinderProxy物件撤銷(detach)的時候,

        該函式會 被呼叫,以釋放一些資源。讀者可自行研究proxy_cleanup函式。

      */

       val->attachObject(&gBinderProxyOffsets, refObject,

                             jnienv_to_javavm(env),proxy_cleanup);

 

        //DeathRecipientList儲存了一個用於死亡通知的list

        sp<DeathRecipientList>drl = new DeathRecipientList;

       drl->incStrong((void*)javaObjectForIBinder);

        //將死亡通知list和BinderProxy物件聯絡起來

       env->SetIntField(object, gBinderProxyOffsets.mOrgue,

                             reinterpret_cast<jint>(drl.get()));

        //增加該Proxy物件的引用計數

        android_atomic_inc(&gNumProxyRefs);

        //下面這個函式用於垃圾回收。建立的Proxy物件一旦超過200個,該函式

        //將呼叫BinderInter類的ForceGc做一次垃圾回收

       incRefsCreated(env);

    }

 

    returnobject;

}

BinderInternal.getContextObject的程式碼有點多,簡單整理一下,可知該函式完成了以下兩個工作:

·  建立了一個Java層的BinderProxy物件。

·  通過JNI,該BinderProxy物件和一個Native的BpProxy物件掛鉤,而該BpProxy物件的通訊目標就是ServiceManager。

大家還記得在Native層Binder中那個著名的interface_cast巨集嗎?在Java層中,雖然沒有這樣的巨集,但是定義了一個類似的函式asInterface。下面來分析ServiceManagerNative類的asInterface函式,其程式碼如下:

[-->ServiceManagerNative.java]

static public IServiceManager asInterface(IBinderobj)

 {

       ...... //以obj為引數,建立一個ServiceManagerProxy物件

       return new ServiceManagerProxy(obj);

 }

上面程式碼和Native層interface_cast非常類似,都是以一個BpProxy物件為引數構造一個和業務相關的Proxy物件,例如這裡的ServiceManagerProxy物件。ServiceManagerProxy物件的各個業務函式會將相應請求打包後交給BpProxy物件,最終由BpProxy物件傳送給Binder驅動以完成一次通訊。

提示 實際上BpProxy也不會和Binder驅動互動,真正和Binder驅動互動的是IPCThreadState。

(2) addService函式分析

現在來分析ServiceManagerProxy的addService函式,其程式碼如下:

[-->ServcieManagerNative.java]

public void addService(String name, IBinderservice)

                           throws RemoteException {

       Parcel data = Parcel.obtain();

       Parcel reply = Parcel.obtain();

       data.writeInterfaceToken(IServiceManager.descriptor);

       data.writeString(name);

        //注意下面這個writeStrongBinder函式,後面我們會詳細分析它

       data.writeStrongBinder(service);

       //mRemote實際上就是BinderProxy物件,呼叫它的transact,將封裝好的請求資料

       //傳送出去

       mRemote.transact(ADD_SERVICE_TRANSACTION, data, reply, 0);

       reply.recycle();

       data.recycle();

}

BinderProxy的transact,是一個native函式,其實現函式的程式碼如下所示:

[-->android_util_Binder.cpp]

static jbooleanandroid_os_BinderProxy_transact(JNIEnv* env, jobject obj,

                                           jintcode, jobject dataObj,

                                           jobject replyObj, jint flags)

{

        ......

    //從Java的Parcel物件中得到Native的Parcel物件

    Parcel*data = parcelForJavaObject(env, dataObj);

    if (data== NULL) {

       return JNI_FALSE;

    }

    //得到一個用於接收回復的Parcel物件

    Parcel*reply = parcelForJavaObject(env, replyObj);

    if(reply == NULL && replyObj != NULL) {

       return JNI_FALSE;

    }

    //從Java的BinderProxy物件中得到之前已經建立好的那個Native的BpBinder物件

    IBinder*target = (IBinder*)

       env->GetIntField(obj, gBinderProxyOffsets.mObject);

    ......

    //通過Native的BpBinder物件,將請求傳送給ServiceManager

    status_terr = target->transact(code, *data, reply, flags);

    ......

    signalExceptionForError(env, obj, err);

    returnJNI_FALSE;

}

看了上面的程式碼會發現,Java層的Binder最終還是要藉助Native的Binder進行通訊的。

關於Binder這套架構,筆者有一個體會願和讀者一起討論分析。

從架構的角度看,在Java中搭建了一整套框架,如IBinder介面,Binder類和BinderProxy類。但是從通訊角度看,不論架構的編寫採用的是Native語言還是Java語言,只要把請求傳遞到Binder驅動就可以了,所以通訊的目的是向binder傳送請求和接收回復。在這個目的之上,考慮到軟體的靈活性和可擴充套件性,於是編寫了一個架構。反過來說,也可以不使用架構(即沒有使用任何介面、派生之類的東西)而直接和binder互動,例如ServiceManager作為Binder的一個核心程式,就是直接讀取/dev/binder裝置,獲取並處理請求。從這一點上看,Binder的目的雖是簡單的(即開啟binder裝置,然後讀請求和寫回復),但是架構是複雜的(編寫各種介面類和封裝類等)。我們在研究原始碼時,一定要先搞清楚目的。實現只不過是達到該目的的一種手段和方式。脫離目的的實現,如緣木求魚,很容易偏離事物本質。

在對addService進行分析時,我們曾提示writeStrongBinder是一個特別的函式。那麼它特別在哪裡呢?

(3) 三人行之Binder、JavaBBinderHolder和JavaBBinder

ActivityManagerService從ActivityManagerNative類派生,並實現了一些介面,其中和Binder的相關的只有這個ActivityManagerNative類,其原型如下:

[-->ActivityManagerNative.java]

public abstract class ActivityManagerNative

                          extends Binder

                          implementsIActivityManager

ActivityManagerNative從Binder派生,並實現了IActivityManager介面。下面來看ActivityManagerNative的建構函式:

[-->ActivityManagerNative.java]

public ActivityManagerNative() {

       attachInterface(this, descriptor);//該函式很簡單,讀者可自行分析

    }

//這是ActivityManagerNative父類的建構函式,即Binder的建構函式

public Binder() {

       init();

}

Binder建構函式中會呼叫native的init函式,其實現的程式碼如下:

[-->android_util_Binder.cpp]

static void android_os_Binder_init(JNIEnv* env,jobject obj)

{

    //建立一個JavaBBinderHolder物件

   JavaBBinderHolder* jbh = new JavaBBinderHolder();

     bh->incStrong((void*)android_os_Binder_init);

   //將這個JavaBBinderHolder物件儲存到Java Binder物件的mObject成員中

   env->SetIntField(obj, gBinderOffsets.mObject, (int)jbh);

}

從上面程式碼可知,Java的Binder物件將和一個Native的JavaBBinderHolder物件相關聯。那麼,JavaBBinderHolder是何方神聖呢?其定義如下:

[-->android_util_Binder.cpp]

class JavaBBinderHolder : public RefBase

{

public:

   sp<JavaBBinder> get(JNIEnv* env, jobject obj)

    {

       AutoMutex _l(mLock);

       sp<JavaBBinder> b = mBinder.promote();

        if(b == NULL) {

          //建立一個JavaBBinder,obj實際上是Java層中的Binder物件

           b = new JavaBBinder(env, obj);

           mBinder = b;

       }

       return b;

    }

    ......

private:

   Mutex           mLock;

   wp<JavaBBinder> mBinder;

};

從派生關係上可以發現,JavaBBinderHolder僅從RefBase派生,所以它不屬於Binder家族。Java層的Binder物件為什麼會和Native層的一個與Binder家族無關的物件繫結呢?仔細觀察JavaBBinderHolder的定義可知:JavaBBinderHolder類的get函式中建立了一個JavaBBinder物件,這個物件就是從BnBinder派生的。

那麼,這個get函式是在哪裡呼叫的?答案在下面這句程式碼中:

//其中,data是Parcel物件,service此時還是ActivityManagerService

data.writeStrongBinder(service);

writeStrongBinder會做一個替換工作,下面是它的native程式碼實現:

[-->android_util_Binder.cpp]

static void android_os_Parcel_writeStrongBinder(JNIEnv*env,

                                               jobjectclazz, jobject object)

{

   //parcel是一個Native的物件,writeStrongBinder的真正引數是

 //ibinderForJavaObject的返回值

  conststatus_t err = parcel->writeStrongBinder(

                                    ibinderForJavaObject(env,object));

}

[-->android_util_Binder.cpp]

sp<IBinder> ibinderForJavaObject(JNIEnv*env, jobject obj)

{

   //如果Java的obj是Binder類,則首先獲得JavaBBinderHolder物件,然後呼叫

  //它的get函式。而這個get將返回一個JavaBBinder

  if(env->IsInstanceOf(obj, gBinderOffsets.mClass)) {

     JavaBBinderHolder*jbh = (JavaBBinderHolder*)env->GetIntField(obj,

                                      gBinderOffsets.mObject);

       return jbh != NULL ? jbh->get(env, obj) : NULL;

    }

    //如果obj是BinderProxy類,則返回Native的BpBinder物件

    if(env->IsInstanceOf(obj, gBinderProxyOffsets.mClass)) {

       return (IBinder*)

           env->GetIntField(obj, gBinderProxyOffsets.mObject);

    }

   returnNULL;

}

根據上面的介紹會發現,addService實際新增到Parcel的並不是AMS本身,而是一個叫JavaBBinder的物件。正是將它最終傳遞到Binder驅動。

讀者此時容易想到,Java層中所有的Binder對應的都是這個JavaBBinder。當然,不同的Binder物件對應不同的JavaBBinder物件。

圖2-2展示了Java Binder、JavaBBinderHolder和JavaBBinder的關係。


圖2-2 JavaBinder、JavaBBinderHolder和JavaBBinder三者的關係

從圖2-2可知:

·  Java層的Binder通過mObject指向一個Native層的JavaBBInderHolder物件。

·  Native層的JavaBBinderHolder物件通過mBinder成員變數指向一個Native的JavaBBinder物件。

·  Native的JavaBBinder物件又通過mObject變數指向一個Java層的Binder物件。

為什麼不直接讓Java層的Binder物件指向Native層的JavaBBinder物件呢?由於缺乏設計文件,這裡不便妄加揣測,但從JavaBBinderHolder的實現上來分析,估計和垃圾回收(記憶體管理)有關,因為JavaBBinderHolder中的mBinder物件的型別被定義成弱引用wp了。

建議 對此,如果讀者有更好的解釋,不妨與大家分享一下。

2.  ActivityManagerService響應請求

初見JavaBBinde時,多少有些吃驚。回想一下Native層的Binder架構:雖然在程式碼中呼叫的是Binder類提供的介面,但其物件卻是一個實際的服務端物件,例如MediaPlayerService物件,AudioFlinger物件。

而Java層的Binder架構中,JavaBBinder卻是一個和業務完全無關的物件。那麼,這個物件如何實現不同業務呢?

為回答此問題,我們必須看它的onTransact函式。當收到請求時,系統會呼叫這個函式。

關於這個問題,建議讀者閱讀卷I第六章《深入理解Binder》。

[-->android_util_Binder.cpp]

virtual status_t onTransact(

       uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags =0)

{

       JNIEnv* env = javavm_to_jnienv(mVM);

       IPCThreadState* thread_state = IPCThreadState::self();

       .......

       //呼叫Java層Binder物件的execTranscat函式

       jboolean res = env->CallBooleanMethod(mObject,

                    gBinderOffsets.mExecTransact,code,

                   (int32_t)&data,(int32_t)reply, flags);

        ......

       return res != JNI_FALSE ? NO_ERROR : UNKNOWN_TRANSACTION;

}

就本例而言,上面程式碼中的mObject就是ActivityManagerService,現在呼叫它的execTransact函式,該函式在Binder類中實現,具體程式碼如下:

 [-->Binder.java]

private boolean execTransact(int code, intdataObj, int replyObj,int flags) {

       Parcel data = Parcel.obtain(dataObj);

       Parcel reply = Parcel.obtain(replyObj);

       boolean res;

        try{

           //呼叫onTransact函式,派生類可以重新實現這個函式,以完成業務功能

           res = onTransact(code, data, reply, flags);

        }......

       reply.recycle();

       data.recycle();

       return res;

    }

}

ActivityManagerNative類實現了onTransact函式,程式碼如下:

[-->ActivityManagerNative.java]

public boolean onTransact(int code, Parcel data,Parcel reply, int flags)

           throws RemoteException {

       switch (code) {

        caseSTART_ACTIVITY_TRANSACTION:

        {

           data.enforceInterface(IActivityManager.descriptor);

           IBinder b = data.readStrongBinder();

           ......

           //再由ActivityManagerService實現業務函式startActivity

           intresult = startActivity(app, intent, resolvedType,

                   grantedUriPermissions, grantedMode, resultTo, resultWho,

                   requestCode, onlyIfNeeded, debug, profileFile,

                   profileFd, autoStopProfiler);

           reply.writeNoException();

           reply.writeInt(result);

           return true;

}

由此可以看出,JavaBBinder僅是一個傳聲筒,它本身不實現任何業務函式,其工作是:

·  當它收到請求時,只是簡單地呼叫它所繫結的Java層Binder物件的exeTransact。

·  該Binder物件的exeTransact呼叫其子類實現的onTransact函式。

·  子類的onTransact函式將業務又派發給其子類來完成。請讀者務必注意其中的多層繼承關係。

通過這種方式,來自客戶端的請求就能傳遞到正確的Java Binder物件了。圖2-3展示AMS響應請求的整個流程。


圖2-3  AMS響應請求的流程

圖2-3中,右上角的大方框表示AMS這個物件,其間的虛線箭頭表示呼叫子類過載的函式。

2.2.4  Java層Binder架構總結


圖2-4展示了Java層的Binder架構。


圖 2-4  Java層Binder架構

 根據圖2-4可知:

·  對於代表客戶端的BinderProxy來說,Java層的BinderProxy在Native層對應一個BpBinder物件。凡是從Java層發出的請求,首先從Java層的BinderProxy傳遞到Native層的BpBinder,繼而由BpBinder將請求傳送到Binder驅動。

·  對於代表服務端的Service來說,Java層的Binder在Native層有一個JavaBBinder物件。前面介紹過,所有Java層的Binder在Native層都對應為JavaBBinder,而JavaBBinder僅起到中轉作用,即把來自客戶端的請求從Native層傳遞到Java層。

·  系統中依然只有一個Native的ServiceManager。

至此,Java層的Binder架構已介紹完畢。從前面的分析可以看出,Java層Binder非常依賴Native層的Binder。建議想進一步瞭解Binder的讀者們,要深入瞭解這一問題,有必要閱讀卷I的第6章“深入理解Binder”。

2.3  心繫兩界的MessageQueue

卷I第5章介紹過,MessageQueue類封裝了與訊息佇列有關的操作。在一個以訊息驅動的系統中,最重要的兩部分就是訊息佇列和訊息處理迴圈。在Andrid 2.3以前,只有Java世界的居民有資格向MessageQueue中新增訊息以驅動Java世界的正常運轉,但從Android 2.3開始,MessageQueue的核心部分下移至Native層,讓Native世界的居民也能利用訊息迴圈來處理他們所在世界的事情。因此現在的MessageQueue心繫Native和Java兩個世界。

2.3.1  MessageQueue的建立

現在來分析MessageQueue是如何跨界工作的,其程式碼如下:

[-->MessageQueue.java]

 MessageQueue() {

       nativeInit(); //建構函式呼叫nativeInit,該函式由Native層實現

 }

nativeInit函式的真正實現為android_os_MessageQueue_nativeInit,其程式碼如下:

[-->android_os_MessageQueue.cpp]

static voidandroid_os_MessageQueue_nativeInit(JNIEnv* env, jobject obj) {

   //NativeMessageQueue是MessageQueue在Native層的代表

   NativeMessageQueue*nativeMessageQueue = new NativeMessageQueue();

   ......

   //將這個NativeMessageQueue物件設定到Java層儲存

   android_os_MessageQueue_setNativeMessageQueue(env,obj,

                                                         nativeMessageQueue);

}

 nativeInit函式在Native層建立了一個與MessageQueue對應的NativeMessageQueue物件,其建構函式如下:

[-->android_os_MessageQueue.cpp]

NativeMessageQueue::NativeMessageQueue() {

 /*

   代表訊息迴圈的Looper也在Native層中呈現身影了。根據訊息驅動的知識,一個執行緒會有一個

   Looper來迴圈處理訊息佇列中的訊息。下面一行的呼叫就是取得儲存線上程本地儲存空間

   (Thread Local Storage)中的Looper物件

   */

    mLooper= Looper::getForThread();

   if(mLooper == NULL) {

    /*

     如為第一次進來,則該執行緒沒有設定本地儲存,所以須先建立一個Looper,然後再將其儲存到

     TLS中,這是很常見的一種以執行緒為單位的單例模式

     */

     mLooper = new Looper(false);

     Looper::setForThread(mLooper);

    }

}

Native的Looper是Native世界中參與訊息迴圈的一位重要角色。雖然它的類名和Java層的Looper類一樣,但此二者其實並無任何關係。這一點以後還將詳細分析。

2.3.2  提取訊息

當一切準備就緒後,Java層的訊息迴圈處理,也就是Looper會在一個迴圈中提取並處理訊息。訊息的提取就是呼叫MessageQueue的next函式。當訊息佇列為空時,next就會阻塞。MessageQueue同時支援Java層和Native層的事件,那麼其next函式該怎麼實現呢?具體程式碼如下:

[-->MessagQueue.java]

final Message next() {

        intpendingIdleHandlerCount = -1;

        intnextPollTimeoutMillis = 0;

 

        for(;;) {

           ......

            //mPtr儲存了NativeMessageQueue的指標,呼叫nativePollOnce進行等待

           nativePollOnce(mPtr, nextPollTimeoutMillis);

           synchronized (this) {

               final long now = SystemClock.uptimeMillis();

               //mMessages用來儲存訊息,這裡從其中取一個訊息進行處理

               final Message msg = mMessages;

               if (msg != null) {

                   final long when = msg.when;

                   if (now >= when) {

                        mBlocked = false;

                        mMessages = msg.next;

                        msg.next = null;

                        msg.markInUse();

                        return msg; //返回一個Message給Looper進行派發和處理

                   } else {

                        nextPollTimeoutMillis =(int) Math.min(when - now,

                                                     Integer.MAX_VALUE);

                   }

               } else {

                   nextPollTimeoutMillis = -1;

               }

           ......

           /*

           處理註冊的IdleHandler,當MessageQueue中沒有Message時,

           Looper會呼叫IdleHandler做一些工作,例如做垃圾回收等

           */

           ......

           pendingIdleHandlerCount = 0;

          nextPollTimeoutMillis = 0;

        }

}

看到這裡,可能會有人覺得這個MessageQueue很簡單,不就是從以前在Java層的wait變成現在Native層的wait了嗎?但是事情本質比表象要複雜得多,來思考下面的情況:

·  nativePollOnce返回後,next函式將從mMessages中提取一個訊息。也就是說,要讓nativePollOnce返回,至少要新增一個訊息到訊息佇列,否則nativePollOnce不過是做了一次無用功罷了。

·  如果nativePollOnce將在Native層等待,就表明Native層也可以投遞Message,但是從Message類的實現程式碼上看,該類和Native層沒有建立任何關係。那麼nativePollOnce在等待什麼呢?

對於上面的問題,相信有些讀者心中已有了答案:nativePollOnce不僅在等待Java層來的Message,實際上還在Native還做了大量的工作。

下面我們來分析Java層投遞Message並觸發nativePollOnce工作的正常流程。

1.  在Java層投遞Message

MessageQueue的enqueueMessage函式完成將一個Message投遞到MessageQueue中的工作,其程式碼如下:

[-->MesssageQueue.java]

final boolean enqueueMessage(Message msg, longwhen) {

        ......

       final boolean needWake;

       synchronized (this) {

           if (mQuiting) {

               return false;

           } else if (msg.target == null) {

               mQuiting = true;

           }

           msg.when = when;

           Message p = mMessages;

           if (p == null || when == 0 || when < p.when) {

               /*

                如果p為空,表明訊息佇列中沒有訊息,那麼msg將是第一個訊息,needWake

                需要根據mBlocked的情況考慮是否觸發

               */

               msg.next = p;

               mMessages = msg;

               needWake = mBlocked;

           } else {

               //如果p不為空,表明訊息佇列中還有剩餘訊息,需要將新的msg加到訊息尾

               Message prev = null;

               while (p != null && p.when <= when) {

                   prev = p;

                   p = p.next;

               }

               msg.next = prev.next;

               prev.next = msg;

               //因為訊息佇列之前還剩餘有訊息,所以這裡不用呼叫nativeWakeup

               needWake = false;

           }

        }

        if(needWake) {

           //呼叫nativeWake,以觸發nativePollOnce函式結束等待

           nativeWake(mPtr);

        }

       return true;

    }

上面的程式碼比較簡單,主要功能是:

·  將message按執行時間排序,並加入訊息隊。

·  根據情況呼叫nativeWake函式,以觸發nativePollOnce函式,結束等待。

建議 雖然程式碼簡單,但是對於那些不熟悉多執行緒的讀者,還是要細細品味一下mBlocked值的作用。我們常說細節體現美,程式碼也一樣,這個小小的mBlocked正是如此。

2.  nativeWake函式分析

nativeWake函式的程式碼如下所示:

[-->android_os_MessageQueue.cpp]

static voidandroid_os_MessageQueue_nativeWake(JNIEnv* env, jobject obj,

                                                      jint ptr)

{

    NativeMessageQueue*nativeMessageQueue =  //取出NativeMessageQueue物件

                       reinterpret_cast<NativeMessageQueue*>(ptr);

    returnnativeMessageQueue->wake(); //呼叫它的wake函式

}
void NativeMessageQueue::wake() {

   mLooper->wake();//層層呼叫,現在轉到mLooper的wake函式

}

 

Native Looper的wake函式程式碼如下:

[-->Looper.cpp]

void Looper::wake() {

    ssize_tnWrite;

       do {

           //向管道的寫端寫入一個字元

       nWrite = write(mWakeWritePipeFd, "W", 1);

    } while(nWrite == -1 && errno == EINTR);

 

    if(nWrite != 1) {

        if(errno != EAGAIN) {

           LOGW("Could not write wake signal, errno=%d", errno);

        }

    }

}

wake函式則更為簡單,僅僅向管道的寫端寫入一個字元”W”,這樣管道的讀端就會因為有資料可讀而從等待狀態中醒來。

2.3.3  nativePollOnce函式分析

nativePollOnce的實現函式是android_os_MessageQueue_nativePollOnce,程式碼如下:

[-->android_os_MessageQueue.cpp]

static void android_os_MessageQueue_nativePollOnce(JNIEnv*env, jobject obj,

        jintptr, jint timeoutMillis)

     NativeMessageQueue*nativeMessageQueue =

                            reinterpret_cast<NativeMessageQueue*>(ptr);

    //取出NativeMessageQueue物件,並呼叫它的pollOnce

   nativeMessageQueue->pollOnce(timeoutMillis);

}

//分析pollOnce函式

void NativeMessageQueue::pollOnce(inttimeoutMillis) {

   mLooper->pollOnce(timeoutMillis); //重任傳遞到Looper的pollOnce函式

}

Looper的pollOnce函式如下:

[-->Looper.cpp]

inline int pollOnce(int timeoutMillis) {

       return pollOnce(timeoutMillis, NULL, NULL, NULL);

}

上面的函式將呼叫另外一個有4個引數的pollOnce函式,這個函式的原型如下:

int pollOnce(int timeoutMillis, int* outFd, int*outEvents, void** outData)

其中:

·  timeOutMillis引數為超時等待時間。如果為-1,則表示無限等待,直到有事件發生為止。如果值為0,則無需等待立即返回。

·  outFd用來儲存發生事件的那個檔案描述符

·  outEvents用來儲存在該檔案描述符[①]上發生了哪些事件,目前支援可讀、可寫、錯誤和中斷4個事件。這4個事件其實是從epoll事件轉化而來。後面我們會介紹大名鼎鼎的epoll。

·  outData用於儲存上下文資料,這個上下文資料是由使用者在新增監聽控制程式碼時傳遞的,它的作用和pthread_create函式最後一個引數param一樣,用來傳遞使用者自定義的資料。

另外,pollOnce函式的返回值也具有特殊的意義,具體如下:

·  當返回值為ALOOPER_POLL_WAKE時,表示這次返回是由wake函式觸發的,也就是管道寫端的那次寫事件觸發的。

·  返回值為ALOOPER_POLL_TIMEOUT表示等待超時。

·  返回值為ALOOPER_POLL_ERROR,表示等待過程中發生錯誤。

返回值為ALOOPER_POLL_CALLBACK,表示某個被監聽的控制程式碼因某種原因被觸發。這時,outFd引數用於儲存發生事件的檔案控制程式碼,outEvents用於儲存所發生的事件。

上面這些知識是和epoll息息相關的。

提示 檢視Looper的程式碼會發現,Looper採用了編譯選項(即#if和#else)來控制是否使用epoll作為I/O複用的控制中樞。鑑於現在大多數系統都支援epoll,這裡僅討論使用epoll的情況。

1.  epoll基礎知識介紹

epoll機制提供了Linux平臺上最高效的I/O複用機制,因此有必要介紹一下它的基礎知識。

從呼叫方法上看,epoll的用法和select/poll非常類似,其主要作用就是I/O複用,即在一個地方等待多個檔案控制程式碼的I/O事件。

下面通過一個簡單例子來分析epoll的工作流程。

[-->epoll工作流程分析案例]

  /*

  使用epoll前,需要先通過epoll_create函式建立一個epoll控制程式碼。

  下面一行程式碼中的10表示該epoll控制程式碼初次建立時候分配能容納10個fd相關資訊的快取。

  對於2.6.8版本以後的核心,該值沒有實際作用,這裡可以忽略。其實這個值的主要目的是

  確定分配一塊多大的快取。現在的核心都支援動態擴充這塊快取,所以該值就沒有意義了

  */ 

  int epollHandle = epoll_create(10);

  

  /*

     得到epoll控制程式碼後,下一步就是通過epoll_ctl把需要監聽的檔案控制程式碼加入到epoll控制程式碼中。

     除了指定檔案控制程式碼本身的fd值外,同時還需要指定在該fd上等待什麼事件。epoll支援四類事件,

    分別是EPOLLIN(控制程式碼可讀)、EPOLLOUT(控制程式碼可寫),EPOLLERR(控制程式碼錯誤)、EPOLLHUP(控制程式碼斷)。

     epoll定義了一個結構體struct epoll_event來表達監聽控制程式碼的訴求。

     假設現在有一個監聽端的socket控制程式碼listener,要把它加入到epoll控制程式碼中。

   */

   structepoll_event listenEvent; //先定義一個event

   /*

   EPOLLIN表示可讀事件,EPOLLOUT表示可寫事件,另外還有EPOLLERR,EPOLLHUP表示

   系統預設會將EPOLLERR加入到事件集合中

   */

   listenEvent.events= EPOLLIN;//指定該控制程式碼的可讀事件

   //epoll_event中有一個聯合體叫data,用來儲存上下文資料,本例的上下文資料就是控制程式碼自己

   listenEvent.data.fd= listenEvent;

  /*

   EPOLL_CTL_ADD將監聽fd和監聽事件加入到epoll控制程式碼的等待佇列中;

   EPOLL_CTL_DEL將監聽fd從epoll控制程式碼中移除;

   EPOLL_CTL_MOD修改監聽fd的監聽事件,例如本來只等待可讀事件,現在需要同時等待

  可寫事件,那麼修改listenEvent.events 為EPOLLIN|EPOLLOUT後,再傳給epoll控制程式碼

   */

   epoll_ctl(epollHandle,EPOLL_CTL_ADD,listener,&listenEvent);

   /*

   當把所有感興趣的fd都加入到epoll控制程式碼後,就可以開始坐等感興趣的事情發生了。

   為了接收所發生的事情,先定義一個epoll_event陣列

   */

 struct  epoll_eventresultEvents[10];

   inttimeout = -1;

   while(1)

   {

      /*

      呼叫epoll_wait用於等待事件,其中timeout可以指定一個超時時間,

      resultEvents用於接收發生的事件,10為該陣列的大小。

       epoll_wait函式的返回值有如下含義:

       nfds大於0表示所監聽的控制程式碼上有事件發生;

       nfds等於0表示等待超時;

       nfds小於0表示等待過程中發生了錯誤

      */

   int nfds= epoll_wait(epollHandle, resultEvents, 10, timeout);

   if(nfds== -1)

   {

      // epoll_wait發生了錯誤

   }

   elseif(nfds == 0)

   {

      //發生超時,期間沒有發生任何事件

   }

   else

   {

      //resultEvents用於返回那些發生了事件的資訊

      for(int i = 0; i < nfds; i++)

      {

         struct epoll_event & event =resultEvents[i];

         if(event & EPOLLIN)

        {

            /*

             收到可讀事件。到底是哪個檔案控制程式碼發生該事件呢?可通過event.data這個聯合體取得

              之前傳遞給epoll的上下文資料,該上下文資訊可用於判斷到底是誰發生了事件。

            */

        }

         .......//其他處理 

      }

   }

 

}

epoll整體使用流程如上面程式碼所示,基本和select/poll類似,不過作為Linux平臺最高效的I/O複用機制,這裡有些內容供讀者參考,

epoll的效率為什麼會比select高?其中一個原因是呼叫方法。每次呼叫select時,都需要把感興趣的事件複製到核心中,而epoll只在epll_ctl進行加入的時候複製一次。另外,epoll內部用於儲存事件的資料結構使用的是紅黑樹,查詢速度很快。而select採用陣列儲存資訊,不但一次能等待的控制程式碼個數有限,並且查詢起來速度很慢。當然,在只等待少量檔案控制程式碼時,select和epoll效率相差不是很多,但筆者還是推薦使用epoll。

epoll等待的事件有兩種觸發條件,一個是水平觸發(EPOLLLEVEL),另外一個是邊緣觸發(EPOLLET,ET為Edge Trigger之意),這兩種觸發條件的區別非常重要。讀者可通過man epoll查閱系統提供的更為詳細的epoll機制。

最後,關於pipe,還想提出一個小問題供讀者思考討論:

為什麼Android中使用pipe作為執行緒間通訊的方式?對於pipe的寫端寫入的資料,讀端都不感興趣,只是為了簡單的喚醒。POSIX不是也有執行緒間同步函式嗎?為什麼要用pipe呢?

關於這個問題的答案,可參見筆者一篇博文“隨筆之如何實現一個執行緒池”。[②]

2.  pollOnce函式分析

下面分析帶4個引數的pollOnce函式,程式碼如下:

[-->Looper.cpp]

int Looper::pollOnce(int timeoutMillis, int*outFd, int* outEvents,

void** outData) {

    intresult = 0;

   for (;;){ //一個無限迴圈

   //mResponses是一個Vector,這裡首先需要處理response

       while (mResponseIndex < mResponses.size()) {

           const Response& response = mResponses.itemAt(mResponseIndex++);

           ALooper_callbackFunc callback = response.request.callback;

           if (!callback) {//首先處理那些沒有callback的Response

               int ident = response.request.ident; //ident是這個Response的id

               int fd = response.request.fd;

               int events = response.events;

               void* data = response.request.data;

               ......

               if (outFd != NULL) *outFd = fd;

               if (outEvents != NULL) *outEvents = events;

               if (outData != NULL) *outData = data;

               //實際上,對於沒有callback的Response,pollOnce只是返回它的

              //ident,並沒有實際做什麼處理。因為沒有callback,所以系統也不知道如何處理

               return ident;

           }

        }

 

        if(result != 0) {

          if (outFd != NULL) *outFd = 0;

           if (outEvents != NULL) *outEvents = NULL;

           if (outData != NULL) *outData = NULL;

           return result;

        }

        //呼叫pollInner函式。注意,它在for迴圈內部

       result = pollInner(timeoutMillis);

    }

}

初看上面的程式碼,可能會讓人有些丈二和尚摸不著頭腦。但是把pollInner函式分析完畢,大家就會明白很多。pollInner函式非常長,把用於除錯和統計的程式碼去掉,結果如下:

[-->Looper.cpp]

int Looper::pollInner(int timeoutMillis) {

   

    if(timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {

       nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);

        ......//根據Native Message的資訊計算此次需要等待的時間

        timeoutMillis= messageTimeoutMillis;

     }

    intresult = ALOOPER_POLL_WAKE;

   mResponses.clear();

   mResponseIndex = 0;

#ifdef LOOPER_USES_EPOLL  //我們只討論使用epoll進行I/O複用的方式

    structepoll_event eventItems[EPOLL_MAX_EVENTS];

    //呼叫epoll_wait,等待感興趣的事件或超時發生

    inteventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS,

                                      timeoutMillis);

#else

     ......//使用別的方式進行I/O複用

#endif

    //從epoll_wait返回,這時候一定發生了什麼事情

   mLock.lock();

    if(eventCount < 0) { //返回值小於零,表示發生錯誤

        if(errno == EINTR) {

           goto Done;

        }

        //設定result為ALLOPER_POLL_ERROR,並跳轉到Done

       result = ALOOPER_POLL_ERROR;

        gotoDone;

    }

 

    //eventCount為零,表示發生超時,因此直接跳轉到Done

    if(eventCount == 0) {

      result = ALOOPER_POLL_TIMEOUT;

        gotoDone;

    }

#ifdef LOOPER_USES_EPOLL

    //根據epoll的用法,此時的eventCount表示發生事件的個數

    for (inti = 0; i < eventCount; i++) {

        intfd = eventItems[i].data.fd;

       uint32_t epollEvents = eventItems[i].events;

        /*

         之前通過pipe函式建立過兩個fd,這裡根據fd知道是管道讀端有可讀事件。

         讀者還記得對nativeWake函式的分析嗎?在那裡我們向管道寫端寫了一個”W”字元,這樣

         就能觸發管道讀端從epoll_wait函式返回了

         */

        if(fd == mWakeReadPipeFd) {

           if (epollEvents & EPOLLIN) {

                //awoken函式直接讀取並清空管道資料,讀者可自行研究該函式

               awoken();

           }

          ......

        }else {

           /*

            mRequests和前面的mResponse相對應,它也是一個KeyedVector,其中儲存了

            fd和對應的Request結構體,該結構體封裝了和監控檔案控制程式碼相關的一些上下文資訊,

            例如回撥函式等。我們在後面的小節會再次介紹該結構體

           */

           ssize_t requestIndex = mRequests.indexOfKey(fd);

           if (requestIndex >= 0) {

               int events = 0;

               //將epoll返回的事件轉換成上層LOOPER使用的事件

               if (epollEvents & EPOLLIN) events |= ALOOPER_EVENT_INPUT;

               if (epollEvents & EPOLLOUT) events |= ALOOPER_EVENT_OUTPUT;

               if (epollEvents & EPOLLERR) events |= ALOOPER_EVENT_ERROR;

               if (epollEvents & EPOLLHUP) events |= ALOOPER_EVENT_HANGUP;

               //每處理一個Request,就相應構造一個Response

               pushResponse(events, mRequests.valueAt(requestIndex));

           }  

            ......

        }

    }

Done: ;

#else

     ......

#endif

    //除了處理Request外,還處理Native的Message

   mNextMessageUptime = LLONG_MAX;

    while(mMessageEnvelopes.size() != 0) {

       nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);

       const MessageEnvelope& messageEnvelope =mMessageEnvelopes.itemAt(0);

        if(messageEnvelope.uptime <= now) {

           {

               sp<MessageHandler> handler = messageEnvelope.handler;

               Message message = messageEnvelope.message;

               mMessageEnvelopes.removeAt(0);

               mSendingMessage = true;

               mLock.unlock();

               //呼叫Native的handler處理Native的Message

               //從這裡也可看出NativeMessage和Java層的Message沒有什麼關係

               handler->handleMessage(message);

           }

           mLock.lock();

           mSendingMessage = false;

            result = ALOOPER_POLL_CALLBACK;

        }else {

            mNextMessageUptime = messageEnvelope.uptime;

           break;

        }

    }

 

    mLock.unlock();

    //處理那些帶回撥函式的Response

   for(size_t i = 0; i < mResponses.size(); i++) {

       const Response& response = mResponses.itemAt(i);

       ALooper_callbackFunc callback = response.request.callback;

        if(callback) {//有了回撥函式,就能知道如何處理所發生的事情了

           int fd = response.request.fd;

           int events = response.events;

           void* data = response.request.data;

           //呼叫回撥函式處理所發生的事件

           int callbackResult = callback(fd, events, data);

           if (callbackResult == 0) {

               //callback函式的返回值很重要,如果為0,表明不需要再次監視該檔案控制程式碼

               removeFd(fd);

           }

           result = ALOOPER_POLL_CALLBACK;

        }

    }

    returnresult;

}

看完程式碼了,是否還有點模糊?那麼,回顧一下pollInner函式的幾個關鍵點:

·  首先需要計算一下真正需要等待的時間。

·  呼叫epoll_wait函式等待。

·  epoll_wait函式返回,這時候可能有三種情況:

m  發生錯誤,則跳轉到Done處。

m  超時,這時候也跳轉到Done處。

m  epoll_wait監測到某些檔案控制程式碼上有事件發生。

·  假設epoll_wait因為檔案控制程式碼有事件而返回,此時需要根據檔案控制程式碼來分別處理:

m  如果是管道讀這一端有事情,則認為是控制命令,可以直接讀取管道中的資料。

m  如果是其他FD發生事件,則根據Request構造Response,並push到Response陣列中。

·  真正開始處理事件是在有Done標誌的位置。

m  首先處理Native的Message。呼叫Native Handler的handleMessage處理該Message。

m  處理Response陣列中那些帶有callback的事件。

上面的處理流程還是比較清晰的,但還是有個一個攔路虎,那就是mRequests,下面就來清剿這個攔路虎。

3.  新增監控請求

新增監控請求其實就是呼叫epoll_ctl增加檔案控制程式碼。下面通過從Native的Activity找到的一個例子來分析mRequests。

[-->android_app_NativeActivity.cpp]

static jint

loadNativeCode_native(JNIEnv* env, jobject clazz,jstring path,

                          jstring funcName,jobject messageQueue,

                          jstring internalDataDir, jstring obbDir,

                          jstring externalDataDir, int sdkVersion,

                          jobject jAssetMgr, jbyteArraysavedState)

{

  ......

  /*

  呼叫Looper的addFd函式。第一個參數列示監聽的fd;第二個引數0表示ident;

  第三個參數列示需要監聽的事件,這裡為只監聽可讀事件;第四個引數為回撥函式,當該fd發生

  指定事件時,looper將回撥該函式;第五個引數code為回撥函式的引數

*/

  code->looper->addFd(code->mainWorkRead,0,

                          ALOOPER_EVENT_INPUT,mainWorkCallback, code);

  ......

}

Looper的addFd程式碼如下所示:

[-->Looper.cpp]

int Looper::addFd(int fd, int ident, int events,

                      ALooper_callbackFunccallback, void* data) {

    if (!callback) {

         //判斷該Looper是否支援不帶回撥函式的檔案控制程式碼新增。一般不支援,因為沒有回撥函式

         //Looper也不知道如何處理該檔案控制程式碼上發生的事情

         if(! mAllowNonCallbacks) {

           return -1;

        }

      ......

    }

 

    #ifdefLOOPER_USES_EPOLL

    intepollEvents = 0;

    //將使用者的事件轉換成epoll使用的值

    if(events & ALOOPER_EVENT_INPUT) epollEvents |= EPOLLIN;

    if(events & ALOOPER_EVENT_OUTPUT) epollEvents |= EPOLLOUT;

 

    {

       AutoMutex _l(mLock);

       Request request; //建立一個Request物件

       request.fd = fd; //儲存fd

       request.ident = ident; //儲存id

       request.callback = callback; //儲存callback

       request.data = data;  //儲存使用者自定義資料

 

       struct epoll_event eventItem;

       memset(& eventItem, 0, sizeof(epoll_event));

       eventItem.events = epollEvents;

       eventItem.data.fd = fd;

        //判斷該Request是否已經存在,mRequests以fd作為key值

       ssize_t requestIndex = mRequests.indexOfKey(fd);

        if(requestIndex < 0) {

           //如果是新的檔案控制程式碼,則需要為epoll增加該fd

           int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);

            ......

           //儲存Request到mRequests鍵值陣列

           mRequests.add(fd, request);

        }else {

           //如果之前加過,那麼就修改該監聽控制程式碼的一些資訊

           int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_MOD, fd, &eventItem);

           ......

           mRequests.replaceValueAt(requestIndex, request);

        }

    }

#else

    ......

#endif

    return1;

}

4.  處理監控請求

我們發現在pollInner函式中,當某個監控fd上發生事件後,就會把對應的Request取出來呼叫。

pushResponse(events, mRequests.itemAt(i));

此函式如下:

[-->Looper.cpp]

void Looper::pushResponse(int events, constRequest& request) {

    Responseresponse;

   response.events = events;

   response.request = request; //其實很簡單,就是儲存所發生的事情和對應的Request

   mResponses.push(response); //然後儲存到mResponse陣列

}

根據前面的知識可知,並不是單獨處理Request,而是需要先收集Request,等到Native Message訊息處理完之後再做處理。這表明,在處理邏輯上,Native Message的優先順序高於監控FD的優先順序。

下面我們來了解如何新增Native的Message。

5.  Native的sendMessage

Android 2.2中只有Java層才可以通過sendMessage往MessageQueue中新增訊息,從4.0開始,Native層也支援sendMessage了[③]。sendMessage的程式碼如下:

[-->Looper.cpp]

void Looper::sendMessage(constsp<MessageHandler>& handler,

                              constMessage& message) {

    //Native的sendMessage函式必須同時傳遞一個Handler

    nsecs_tnow = systemTime(SYSTEM_TIME_MONOTONIC);

   sendMessageAtTime(now, handler, message); //呼叫sendMessageAtTime

}

void Looper::sendMessageAtTime(nsecs_t uptime,

                                     const sp<MessageHandler>& handler,

                                     const Message& message) {

   size_t i= 0;

    { //acquire lock

       AutoMutex _l(mLock);

 

       size_t messageCount = mMessageEnvelopes.size();

        //按時間排序,將訊息插入到正確的位置上

        while (i < messageCount &&

               uptime >= mMessageEnvelopes.itemAt(i).uptime) {

           i += 1;

        }

       

       MessageEnvelope messageEnvelope(uptime, handler, message);

       mMessageEnvelopes.insertAt(messageEnvelope, i, 1);

       //mSendingMessage和Java層中的那個mBlocked一樣,是一個小小的優化措施

        if(mSendingMessage) {

           return;

        }

    }

    //喚醒epoll_wait,讓它處理訊息

    if (i ==0) {

       wake();

    }

}

 

2.3.4  MessageQueue總結

想不到,一個小小的MessageQueue竟然有如此多的內容。在後面分析Android輸入系統時,我們會再次在Native層和MessageQueue碰面,這裡僅是為後面的相會打下一定的基礎。

現在,我們將站在一個比具體程式碼更高的層次來認識一下MessageQueue和它的夥伴們。

1.  訊息處理的大家族合照

MessageQueue只是訊息處理大家族的一員,該家族的成員合照如圖2-5所示。


圖2-5  訊息處理的家族合照

結合前述內容可從圖2-5中得到:

·  Java層提供了Looper類和MessageQueue類,其中Looper類提供迴圈處理訊息的機制,MessageQueue類提供一個訊息佇列,以及插入、刪除和提取訊息的函式介面。另外,Handler也是在Java層常用的與訊息處理相關的類。

·  MessageQueue內部通過mPtr變數儲存一個Native層的NativeMessageQueue物件,mMessages儲存來自Java層的Message訊息。

·  NativeMessageQueue儲存一個native的Looper物件,該Looper從ALooper派生,提供pollOnce和addFd等函式。

·  Java層有Message類和Handler類,而Native層對應也有Message類和MessageHandler抽象類。在編碼時,一般使用的是MessageHandler的派生類WeakMessageHandler類。

注意 在include/media/stagfright/foundation目錄下也定義了一個ALooper類,它是供stagefright使用的類似Java訊息迴圈的一套基礎類。這種同名類的產生,估計是兩個事先未做交流的Group的人寫的。

 

2.  MessageQueue處理流程總結

MessageQueue核心邏輯下移到Native層後,極大地擴充了訊息處理的範圍,總結一下有以下幾點:

·  MessageQueue繼續支援來自Java層的Message訊息,也就是早期的Message加Handler的處理方式。

·  MessageQueue在Native層的代表NativeMessageQueue支援來自Native層的Message,是通過Native的Message和MessageHandler來處理的。

·  NativeMessageQueue還處理通過addFd新增的Request。在後面分析輸入系統時,還會大量碰到這種方式。

·  從處理邏輯上看,先是Native的Message,然後是Native的Request,最後才是Java的Message。

對Java程式設計師來說,以前單純的MessageQueue開始變得複雜。有同事經常與筆者討論,cpu並不是很忙,為什麼sendMessage的訊息很久後才執行?是的,對於只瞭解MessageQueue Java層的工作人員,這個問題還是沒辦法回答。因為MessageQueue在Native層的兄弟NativeMessageQueue可能正在處理一個Native的Message,而Java的呼叫堆疊資訊又不能列印Native層的活動,所以這個對Java程式設計師來說看上去很面善的MessageQueue,還是讓筆者為某些只沉迷於Java語言的同仁們感到擔心。

2.4  本章小結

本章先對Java層的Binder架構做了一次較為深入的分析。Java層的Binder架構和Native層Binder架構類似,但是Java的Binder在通訊上還是依賴Native層的Binder。建議想進一步瞭解Native Binder工作原理的讀者,閱讀卷I第6章“深入理解Binder”。另外,本章還對MessageQueue進行了較為深入的分析。Android 2.2中那個功能簡單的MessageQueue現在變得複雜了,原因是該類的核心邏輯下移到Native層,導致現在的MessageQueue除了支援Java層的Message派發外,還新增了支援Native Message派發以及處理來自所監控的檔案控制程式碼的事件。



①注意,以後檔案描述符也會簡寫為檔案控制程式碼。

[②] 我的部落格地址是http://blog.csdn.net/innost

[③] 我們這裡略過了Android2.2到Android 4.0之間幾個版本中的程式碼變化。

相關文章