[深刻理解Android卷一全文-第五章]深刻理解常見類

因爲《深刻理解Android 卷一》和《深刻理解Android卷二》再也不出版,而知識的傳播不該該由於紙質媒介的問題而中斷,因此我將在OSC博客中全文轉發這兩本書的所有內容。


第5章 深刻理解常見類

本章主要內容 java

·  分析RefBase、sp,wp和LightRefBase類。 android

·  分析Native的Thread類和經常使用同步類。 程序員

·  分析Java層的Handler、Looper,以及HandlerThread類。 編程

本章涉及的源代碼文件名稱及位置 多線程

下面是咱們本章分析的源碼文件名和它的位置。 框架

·  RefBase.h 函數

framework/base/include/utils/RefBase.h oop

·  RefBase.cpp 學習

framework/base/libs/utils/RefBase.cpp this

·  Thread.cpp

framework/base/libs/utils/Thread.cpp

·  Thread.h

framework/base/include/utils/Thread.h

·  Atomic.h

system/core/include/cutils/Atomic.h

·  AndroidRuntime.cpp

framework/base/core/jni/AndroidRuntime.cpp

·  Looper.java

framework/base/core/Java/Android/os/Looper.java

·  Handler.java

framework/base/core/Java/Android/os/Handler.java

·  HandlerThread.java

framework/base/core/Java/Android/os/HandlerThread.java

5.1  綜述

初次接觸Android源碼,最常見到的必定是sp和wp。若是你只是沉迷於Java世界,那麼Looper和Handler也是避不開的。本章的目的,就是把常常遇見的這些內容中的「攔路虎」一網打盡,將它們完全搞懂。至於弄明白它們有什麼好處,就是仁者見仁,智者見智了。我我的以爲,可能Looper和Handler會相對更實用一些。

5.2  以「三板斧」揭祕RefBase、sp和wp

RefBase是Android中全部對象的始祖,相似MFC中的CObject及Java中的Object對象。在Android中,RefBase結合sp和wp,實現了一套經過引用計數的方法來控制對象生命週期的機制。就如咱們想像的那樣,這三者的關係很是曖昧。初次接觸Android源碼的人每每會被那個隨處可見的sp和wp搞暈了頭。

什麼是sp和wp呢?其實,sp並非我開始所想的smart pointer(C++語言中有這個東西),它真實的意思應該是strong pointer,而wp是weak pointer的意思。我認爲,Android推出這一套機制多是模仿Java,由於Java世界中有所謂weak reference之類的東西。sp和wp的目的,就是爲了幫助健忘的程序員回收new出來的內存。

我仍是喜歡赤裸裸地管理內存的分配和釋放。不過,目前sp和wp的使用已經深刻到Android系統的各個角落,想把它去掉真是不太可能了。

這三者的關係比較複雜,都說程咬金的「三板斧」很厲害,那麼咱們就借用這三板斧,揭密其相互間的曖昧關係。

5.2.1  第一板斧——初識影子對象

咱們的「三板斧」,其實就是三個例子。相信這三板斧劈下去,你會很容易理解它們。

[-->例子1]

//類A從RefBase派生,RefBase是萬物的始祖

class A:public RefBase

{

 //A沒有任何本身的功能

}

int main()

{

  A* pA =new A;

  {

   //注意咱們的sp,wp對象是在{}中建立的,下面的代碼先建立sp,而後建立wp

   sp<A>spA(A);

   wp<A>wpA(spA);

    //大括號結束前,先析構wp,再析構sp

   }

}

例子夠簡單吧?但也需一步一步分析這斧子是怎麼劈下去的。

1. RefBase和它的影子

類A從RefBase中派生。使用的是RefBase構造函數。代碼以下所示:

[-->RefBase.cpp]

RefBase::RefBase()

    :mRefs(new weakref_impl(this))//注意這句話

{

  //mRefs是RefBase的成員變量,類型是weakref_impl,咱們暫且叫它影子對象

  //因此A有一個影子對象

}

mRefs是引用計數管理的關鍵類,須要進去觀察。它是從RefBase的內部類weakref_type中派生出來的。

先看看它的聲明:

class RefBase::weakref_impl : public RefBase::weakref_type

//從RefBase的內部類weakref_type派生

因爲Android頻繁使用C++內部類的方法,因此初次閱讀Android代碼時可能會有點不太習慣,C++的內部類和Java內部類類似,但不一樣的是,它須要一個顯示的成員指向外部類對象,而Java內部類對象就有一個隱式的成員指向外部類對象。

說明:內部類在C++中的學名叫nested class(內嵌類)。

[-->RefBase.cpp::weakref_imple構造]

weakref_impl(RefBase* base)

        :mStrong(INITIAL_STRONG_VALUE) //強引用計數,初始值爲0x1000000

        ,mWeak(0) //弱引用計數,初始值爲0

        ,mBase(base)//該影子對象所指向的實際對象

        ,mFlags(0)

        ,mStrongRefs(NULL)

        ,mWeakRefs(NULL)

        ,mTrackEnabled(!!DEBUG_REFS_ENABLED_BY_DEFAULT)

        ,mRetain(false)

    {

     }

如你所見,new了一個A對象後,其實還new了一個weakref_impl對象,這裏稱它爲影子對象,另外咱們稱A爲實際對象。

這裏有一個問題:影子對象有什麼用?

能夠仔細想一下,是否是發現影子對象成員中有兩個引用計數?一個強引用,一個弱引用。若是知道引用計數和對象生死有些許關聯的話,就容易想到影子對象的做用了。

按上面的分析,在構造一個實際對象的同時,還會悄悄地構造一個影子對象,在嵌入式設備的內存不是很緊俏的今天,這個影子對象的內存佔用已不成問題了。

2. sp上場

程序繼續運行,如今到了

sp<A> spA(A);

請看sp的構造函數,它的代碼以下所示:(注意,sp是一個模板類,對此不熟悉的讀者能夠去翻翻書,或者乾脆把全部出現的T都換成A。)

[-->RefBase.h::sp(T*other)]

template<typename T>

sp<T>::sp(T* other) //這裏的other就是剛纔建立的pA

    :m_ptr(other)// sp保存了pA的指針

{

    if(other) other->incStrong(this);//調用pA的incStrong

}

OK,戰場轉到RefBase的incStrong中。它的代碼以下所示:

[-->RefBase.cpp]

void RefBase::incStrong(const void* id) const

{

 //mRefs就是剛纔RefBase構造函數中new出來的影子對象

 weakref_impl*const refs = mRefs;

 

//操做影子對象,先增長弱引用計數

 refs->addWeakRef(id);

 refs->incWeak(id);

 ......

先來看看影子對象的這兩個weak函數都幹了些什麼。  

(1)眼見而心不煩

先來看第一個函數addWeakRef,代碼以下所示:

[-->RefBase.cpp]

void addWeakRef(const void* /*id*/) { }

呵呵,addWeakRef啥都沒作,由於這是release版走的分支。調試版的代碼咱們就不討論了,它是給創造RefBase、 sp,以及wp的人調試用的。

調試版分支的代碼不少,看來創造它們的人,也爲不理解它們之間的曖昧關係痛苦不已。

總之,一共有這麼幾個不用考慮的函數,咱們都已列出來了。之後再遇見它們,乾脆就直接跳過的是:

void addStrongRef(const void* /*id*/) { }

void removeStrongRef(const void* /*id*/) { }

void addWeakRef(const void* /*id*/) { }

void removeWeakRef(const void* /*id*/) { }

void printRefs() const { }

void trackMe(bool, bool) { }

繼續咱們的征程。再看incWeak函數,代碼以下所示:

[-->RefBase.cpp]

void RefBase::weakref_type::incWeak(const void*id)

{

   weakref_impl* const impl = static_cast<weakref_impl*>(this);

   impl->addWeakRef(id);  //上面說了,非調試版什麼都不幹

   const int32_tc = android_atomic_inc(&impl->mWeak);

  //原子操做,影子對象的弱引用計數加1

  //千萬記住影子對象的強弱引用計數的值,這是完全理解sp和wp的關鍵

}

好,咱們再回到incStrong,繼續看代碼:

[-->RefBase.cpp]

   ......

  //剛纔增長了弱引用計數

  //再增長強引用計數

  refs->addStrongRef(id);//非調試版這裏什麼都不幹

  //下面函數爲原子加1操做,並返回舊值。因此c=0x1000000,而mStrong變爲0x1000001

   const int32_t c =android_atomic_inc(&refs->mStrong);

   if (c!= INITIAL_STRONG_VALUE)  {

      //若是c不是初始值,則代表這個對象已經被強引用過一次了

       return;

    }

  //下面這個是原子加操做,至關於執行refs->mStrong +(-0x1000000),最終mStrong=1

  android_atomic_add(-INITIAL_STRONG_VALUE,&refs->mStrong);

 /*

   若是是第一次引用,則調用onFirstRef,這個函數很重要,派生類能夠重載這個函數,完成一些

   初始化工做。

 */

 const_cast<RefBase*>(this)->onFirstRef();

}

 

說明:android_atomic_xxx是Android平臺提供的原子操做函數,原子操做函數是多線程編程中的常見函數,讀者能夠學習原子操做函數知識,本章後面將對其作介紹。

(2)sp構造的影響

sp構造完後,它給這個世界帶來了什麼?

·  那就是RefBase中影子對象的強引用計數變爲1,弱引用計數也變爲1。

更準確的說法是,sp的出生致使影子對象的強引用計數加1,弱引用計數加1。

(3)wp構造的影響

繼續看wp,例子中的調用方式以下:

wp<A> wpA(spA)

wp有好幾個構造函數,原理都同樣。來看這個最多見的:

[-->RefBase.h::wp(constsp<T>& other)]

template<typename T>

wp<T>::wp(const sp<T>& other)

    :m_ptr(other.m_ptr) //wp的成員變量m_ptr指向實際對象

{

    if(m_ptr) {

       //調用pA的createWeak,而且保存返回值到成員變量m_refs中

       m_refs = m_ptr->createWeak(this);

    }

}

[-->RefBase.cpp]

RefBase::weakref_type* RefBase::createWeak(constvoid* id) const

{

//調用影子對象的incWeak,這個咱們剛纔講過了,將致使影子對象的弱引用計數增長1

 mRefs->incWeak(id);

 returnmRefs;  //返回影子對象自己

}

咱們能夠看到,wp化後,影子對象的弱引用計數將增長1,因此如今弱引用計數爲2,而強引用計數仍爲1。另外,wp中有兩個成員變量,一個保存實際對象,另外一個保存影子對象。sp只有一個成員變量用來保存實際對象,但這個實際對象內部已包含了對應的影子對象。

OK,wp建立完了,如今開始進入wp的析構。

(4)wp析構的影響

wp進入析構函數,這代表它快要離世了。

[-->RefBase.h]

template<typename T>

wp<T>::~wp()

{

    if(m_ptr) m_refs->decWeak(this); //調用影子對象的decWeak,由影子對象的基類實現

}

[-->RefBase.cpp]

void RefBase::weakref_type::decWeak(const void*id)

{

  //把基類指針轉換成子類(影子對象)的類型,這種作法有些違背面向對象編程的思想

  weakref_impl*const impl = static_cast<weakref_impl*>(this);

  impl->removeWeakRef(id);//非調試版不作任何事情

 

  //原子減1,返回舊值,c=2,而弱引用計數從2變爲1

  constint32_t c = android_atomic_dec(&impl->mWeak);

  if (c !=1) return; //c=2,直接返回

  

  //若是c爲1,則弱引用計數爲0,這說明沒用弱引用指向實際對象,須要考慮是否釋放內存

  // OBJECT_LIFETIME_XXX和生命週期有關係,咱們後面再說。

    if((impl->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) {

        if(impl->mStrong == INITIAL_STRONG_VALUE)

           delete impl->mBase;

       else {

           delete impl;

        }

    } else{

        impl->mBase->onLastWeakRef(id);

        if((impl->mFlags&OBJECT_LIFETIME_FOREVER) != OBJECT_LIFETIME_FOREVER) {

           delete impl->mBase;

        }

    }

}

OK,在例1中,wp析構後,弱引用計數減1。但因爲此時強引用計數和弱引用計數仍爲1,因此沒有對象被幹掉,即沒有釋放實際對象和影子對象佔據的內存。

(5)sp析構的影響

下面進入sp的析構。

[-->RefBase.h]

template<typename T>

sp<T>::~sp()

{

    if(m_ptr) m_ptr->decStrong(this); //調用實際對象的decStrong。由RefBase實現

}

[-->RefBase.cpp]

void RefBase::decStrong(const void* id) const

{

   weakref_impl* const refs = mRefs;

    refs->removeStrongRef(id);//調用影子對象的removeStrongRef,啥都不幹

    //注意,此時強弱引用計數都是1,下面函數調用的結果是c=1,強引用計數爲0

    constint32_t c = android_atomic_dec(&refs->mStrong);

    if (c== 1) { //對於咱們的例子, c爲1

        //調用onLastStrongRef,代表強引用計數減爲0,對象有可能被delete

       const_cast<RefBase*>(this)->onLastStrongRef(id);

       //mFlags爲0,因此會經過delete this把本身幹掉

      //注意,此時弱引用計數仍爲1

        if((refs->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) {

           delete this;

        }

   ......

}

先看delete this的處理,它會致使A的析構函數被調用;再看A的析構函數,代碼以下所示:

[-->例子1::~A()]

//A的析構直接致使進入RefBase的析構。

RefBase::~RefBase()

{

   if(mRefs->mWeak == 0) { //弱引用計數不爲0,而是1

      delete mRefs;  

    }

}

RefBase的delete this自殺行爲沒有把影子對象幹掉,但咱們還在decStrong中,可接着從delete this往下看:

[-->RefBase.cpp]

     ....//接前面的delete this

   if ((refs->mFlags&OBJECT_LIFETIME_WEAK)!= OBJECT_LIFETIME_WEAK) {

           delete this;

        }

  //注意,實際數據對象已經被幹掉了,因此mRefs也沒有用了,可是decStrong剛進來

  //的時候就保存mRefs到refs了,因此這裏的refs指向影子對象

   refs->removeWeakRef(id);

   refs->decWeak(id);//調用影子對象decWeak

}

[-->RefBase.cpp]

void RefBase::weakref_type::decWeak(const void*id)

{

  weakref_impl*const impl = static_cast<weakref_impl*>(this);

  impl->removeWeakRef(id);//非調試版不作任何事情

 

    //調用前影子對象的弱引用計數爲1,強引用計數爲0,調用結束後c=1,弱引用計數爲0

    constint32_t c = android_atomic_dec(&impl->mWeak);

    if (c!= 1) return;

   

    //此次弱引用計數終於變爲0,而且mFlags爲0, mStrong也爲0。

    if((impl->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) {

        if(impl->mStrong == INITIAL_STRONG_VALUE)

           delete impl->mBase;

       else {

           delete impl; //impl就是this,把影子對象本身幹掉

        }

    } else{

       impl->mBase->onLastWeakRef(id);

        if((impl->mFlags&OBJECT_LIFETIME_FOREVER) != OBJECT_LIFETIME_FOREVER) {

           delete impl->mBase;

        }

    }

}

好,第一板斧劈下去了!來看看它的結果是什麼。

3. 第一板斧的結果

第一板斧事後,來總結一下剛纔所學的知識:

·  RefBase中有一個隱含的影子對象,該影子對象內部有強弱引用計數。

·  sp化後,強弱引用計數各增長1,sp析構後,強弱引用計數各減1。

·  wp化後,弱引用計數增長1,wp析構後,弱引用計數減1。

徹底完全地消滅RefBase對象,包括讓實際對象和影子對象滅亡,這些都是由強弱引用計數控制的,另外還要考慮flag的取值狀況。當flag爲0時,可得出以下結論:

·  強引用爲0將致使實際對象被delete。

·  弱引用爲0將致使影子對象被delete。

 

5.2.2  第二板斧——由弱生強

再看第二個例子,代碼以下所示:

[-->例子2]

int main()

{

   A *pA =new A();

  wp<A> wpA(A);

  sp<A> spA = wpA.promote();//經過promote函數,獲得一個sp。

}

對A的wp化,再也不作分析了。按照前面所學的知識,wp化後僅會使弱引用計數加1,因此此處wp化的結果是:

·  影子對象的弱引用計數爲1,強引用計數仍然是初始值0x1000000。

wpA的promote函數是從一個弱對象產生一個強對象的重要函數,試看:

1. 由弱生強的方法

代碼以下所示:

[-->RefBase.h]

template<typename T>

sp<T> wp<T>::promote() const

{

    returnsp<T>(m_ptr, m_refs);  //調用sp的構造函數。

}

[-->RefBase.h]

template<typename T>

sp<T>::sp(T* p, weakref_type* refs)

    :m_ptr((p && refs->attemptIncStrong(this)) ? p : 0)//有點看不清楚

{

//上面那行代碼夠簡潔,可是不方便閱讀,咱們寫成下面這樣:

/*

  T* pTemp= NULL;

  //關鍵函數attemptIncStrong

  if(p !=NULL && refs->attemptIncStrong(this) == true)

      pTemp = p;

 

  m_ptr =pTemp;

*/

}

2. 成敗在此一舉

由弱生強的關鍵函數是attemptIncStrong,它的代碼以下所示:

[-->RefBase.cpp]

boolRefBase::weakref_type::attemptIncStrong(const void* id)

{

     incWeak(id);//增長弱引用計數,此時弱引用計數變爲2

    weakref_impl* const impl = static_cast<weakref_impl*>(this);

      int32_t curCount = impl->mStrong; //這個還是初始值

     //下面這個循環,在多線程操做同一個對象時可能會循環屢次。這裏能夠不去管它,

     //它的目的就是使強引用計數增長1

    while(curCount > 0 && curCount != INITIAL_STRONG_VALUE) {

        if(android_atomic_cmpxchg(curCount, curCount+1, &impl->mStrong) == 0) {

           break;

        }

       curCount = impl->mStrong;

    }

   

    if(curCount <= 0 || curCount == INITIAL_STRONG_VALUE) {

         bool allow;

  /*

   下面這個allow的判斷極爲精妙。impl的mBase對象就是實際對象,有可能已經被delete了。

   curCount爲0,表示強引用計數確定經歷了INITIAL_STRONG_VALUE->1->...->0的過程。

   mFlags就是根據標誌來決定是否繼續進行||或&&後的判斷,由於這些判斷都使用了mBase,

   如不作這些判斷,一旦mBase指向已經回收的地址,你就等着segment fault吧!

   其實,我們大可沒必要理會這些東西,由於它不影響咱們的分析和理解。

  */

        if(curCount == INITIAL_STRONG_VALUE) {

             allow =(impl->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK

                 || impl->mBase->onIncStrongAttempted(FIRST_INC_STRONG, id);

        }else {

           allow = (impl->mFlags&OBJECT_LIFETIME_WEAK) ==OBJECT_LIFETIME_WEAK

                 && impl->mBase->onIncStrongAttempted(FIRST_INC_STRONG,id);

        }

        if(!allow) {

        //allow爲false,表示不容許由弱生強,弱引用計數要減去1,這是由於我們進來時加過一次

            decWeak(id);

           return false; //由弱生強失敗

        }

 

     //容許由弱生強,則強引用計數要增長1,而弱引用計數已經增長過了

       curCount = android_atomic_inc(&impl->mStrong);

        if(curCount > 0 && curCount < INITIAL_STRONG_VALUE) {

           impl->mBase->onLastStrongRef(id);

        }

    }

   impl->addWeakRef(id);

   impl->addStrongRef(id);//兩個函數調用沒有做用

     if(curCount == INITIAL_STRONG_VALUE) {

         //強引用計數變爲1

       android_atomic_add(-INITIAL_STRONG_VALUE, &impl->mStrong);

        //調用onFirstRef,通知該對象第一次被強引用

       impl->mBase->onFirstRef();

    }

    returntrue; //由弱生強成功

}

3. 第二板斧的結果

promote完成後,至關於增長了一個強引用。根據上面所學的知識可知:

·  由弱生強成功後,強弱引用計數均增長1。因此如今影子對象的強引用計數爲1,弱引用計數爲2。

5.2.3  第三板斧——破解生死魔咒

1. 延長生命的魔咒

RefBase爲咱們提供了一個這樣的函數:

extendObjectLifetime(int32_t mode)

另外還定義了一個枚舉:

enum {

       OBJECT_LIFETIME_WEAK    =  0x0001,

       OBJECT_LIFETIME_FOREVER = 0x0003

};

注意:FOREVER的值是3,二進制表示是B11,而WEAK的二進制是B01,也就是說FOREVER包括了WEAK的狀況。

上面這兩個枚舉值,是破除強弱引用計數做用的魔咒。先觀察flags爲OBJECT_LIFETIME_WEAK的狀況,見下面的例子。

[-->例子3]

class A:public RefBase

{

   publicA()

   {

      extendObjectLifetime(OBJECT_LIFETIME_WEAK);//在構造函數中調用

   }

}

int main()

{

   A *pA =new A();

   wp<A> wpA(A);//弱引用計數加1

  {

      sp<A>spA(pA) //sp後,結果是強引用計數爲1,弱引用計數爲2

   }

....

}

 

sp的析構將直接調用RefBase的decStrong,它的代碼以下所示:

[-->RefBase.cpp]

void RefBase::decStrong(const void* id) const

{

   weakref_impl* const refs = mRefs;

   refs->removeStrongRef(id);

    constint32_t c = android_atomic_dec(&refs->mStrong);

    if (c== 1) { //上面原子操做後,強引用計數爲0

       const_cast<RefBase*>(this)->onLastStrongRef(id);、

        //注意這句話。若是flags不是WEAK或FOREVER的話,將delete數據對象

       //如今咱們的flags是WEAK,因此不會delete 它

        if((refs->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) {

            delete this;

        }

  }

    refs->removeWeakRef(id);

   refs->decWeak(id);//調用前弱引用計數是2。

}

而後調用影子對象的decWeak。再來看它的處理,代碼以下所示:

[-->RefBase.cpp::weakref_type的decWeak()函數]

void RefBase::weakref_type::decWeak(const void*id)

{

   weakref_impl* const impl = static_cast<weakref_impl*>(this);

   impl->removeWeakRef(id);

    constint32_t c = android_atomic_dec(&impl->mWeak);

    if (c!= 1) return;  //c爲2,弱引用計數爲1,直接返回。

   /*

     假設咱們如今到了例子中的wp析構之處,這時也會調用decWeak,調用上邊的原子減操做後

     c=1,弱引用計數變爲0,此時會繼續往下運行。因爲mFlags爲WEAK ,因此不知足if的條件

   */

    if((impl->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) {

        if(impl->mStrong == INITIAL_STRONG_VALUE)

           delete impl->mBase;

       else {

           delete impl;

        }

    } else{//flag爲WEAK,知足else分支的條件

       impl->mBase->onLastWeakRef(id);

       /*

        因爲flags值知足下面這個條件,因此實際對象會被delete,根據前面的分析, 實際對象的delete會檢查影子對象的弱引用計數,若是它爲0,則會把影子對象也delete掉。

        因爲影子對象的弱引用計數此時已經爲0,因此影子對象也會被delete。

      */

        if((impl->mFlags&OBJECT_LIFETIME_FOREVER) != OBJECT_LIFETIME_FOREVER) {

           delete impl->mBase;

        }

    }

}

2. LIFETIME_WEAK的魔力

看完上面的例子,咱們發現什麼了?

·  在LIFETIME_WEAK的魔法下,強引用計數爲0,而弱引用計數不爲0的時候,實際對象沒有被delete!只有當強引用計數和弱引用計數同時爲0時,實際對象和影子對象纔會被delete。

3. 魔咒大揭祕

至於LIFETIME_FOREVER的破解,就不用再來一斧子了,我直接的答案是:

·  flags爲0,強引用計數控制實際對象的生命週期,弱引用計數控制影子對象的生命週期。強引用計數爲0後,實際對象被delete。因此對於這種狀況,應記住的是,使用wp時要由弱生強,以避免收到segment fault信號。

·  flags爲LIFETIME_WEAK,強引用計數爲0,弱引用計數不爲0時,實際對象不會被delete。當弱引用計數減爲0時,實際對象和影子對象會同時被delete。這是功德圓滿的狀況。

·  flags爲LIFETIME_FOREVER,對象將長生不老,完全擺脫強弱引用計數的控制。因此你要在適當的時候殺死這些老妖精,省得她禍害「人間」。

5.2.4  輕量級的引用計數控制LightRefBase

上面介紹的RefBase,是一個重量級的引用計數控制類。那麼,究竟有沒有一個簡單些的引用計數控制類呢?Android爲咱們提供了一個輕量級的LightRefBase。這個類很是簡單,咱們不妨一塊兒來看看。

[-->RefBase.h]

template <class T>

class LightRefBase

{

public:

    inlineLightRefBase() : mCount(0) { }

inline void incStrong(const void* id) const {

      //LightRefBase只有一個引用計數控制量mCount。incStrong的時候使它增長1

       android_atomic_inc(&mCount);

    }

inline void decStrong(const void* id) const {

       //decStrong的時候減1,當引用計數變爲零的時候,delete掉本身

        if(android_atomic_dec(&mCount) == 1) {

           delete static_cast<const T*>(this);

        }

    }

    inlineint32_t getStrongCount() const {

       return mCount;

    }

   

protected:

    inline~LightRefBase() { }

   

private:

mutable volatile int32_t mCount;//引用計數控制變量

};

LightRefBase類夠簡單吧?不過它是一個模板類,咱們該怎麼用它呢?下面給出一個例子,其中類A是從LightRefBase派生的,寫法以下:

class A:public LightRefBase<A> //注意派生的時候要指明是LightRefBase<A>

{

public:

A(){};

~A(){};

};

另外,咱們從LightRefBase的定義中能夠知道,它支持sp的控制,由於它只有incStrong和decStrong函數。

5.2.5  題外話——三板斧的來歷

從代碼量上看,RefBase、sp和wp的代碼量並很少,但裏邊的關係,尤爲是flags的引入,曾一度讓我眼花繚亂。當時,我確實很但願能本身調試一下這些例子,但在設備上調試native代碼,須要花費很大的精力,即便是經過輸出log的方式也須要不少時間。該怎麼解決這一難題?

既然它的代碼很少並且簡單,那何不把它移植到臺式機的開發環境下,整一個相似的RefBase呢?因爲有了這樣的構想,我便用上了Visual Studio。至於那些原子操做,Windows平臺上有很直接的InterlockedExchangeXXX與之對應,真的是踏破鐵鞋無覓處,得來全不費功夫!(在Linux平臺上,不考慮多線程的話,將原子操做換成普通的非原子操做不是也能夠嗎?若是更細心更負責任的話,你能夠本身用匯編來實現經常使用的原子操做,內核代碼中有現成的函數,一看就會明白。)

若是把破解代碼當作是攻城略地的話,咱們必須學會靈活多變,並且應力求破解方法日臻極致!

 

5.3  Thread類以及經常使用同步類的分析

Thread類是Android爲線程操做而作的一個封裝。代碼在Thread.cpp中,其中還封裝了一些與線程同步相關(既然是封裝,要掌握它,最重要的固然是與Pthread相關的知識)的類。咱們擬先行分析Threa類,進而再介紹與經常使用同步類相關的知識。

5.3.1  一個變量引起的思考

Thread類雖然說挺簡單,但它構造函數中的那個canCallJava卻一度使我感到費解。由於我一直使用的是本身封裝的Pthread類。當發現Thread構造函數中居然存在這樣一個東西時,很擔憂本身封裝的Pthread類會不會有什麼重大問題,由於當時我還歷來沒考慮過Java方面的問題。

// canCallJava表示這個線程是否會使用JNI函數。爲何須要一個這樣的參數呢?

Thread(bool canCallJava = true)。

咱們必須得了解它實際建立的線程函數是什麼。Thread類真實的線程是建立在run函數中的。

1. 一個變量,兩種處理

先來看一段代碼:

[-->Thread.cpp]

status_t Thread::run(const char* name, int32_tpriority, size_t stack)

{

   Mutex::Autolock_l(mLock);

    ....

   //若是mCanCallJava爲真,則調用createThreadEtc函數,線程函數是_threadLoop。

 //_threadLoop是Thread.cpp中定義的一個函數。

   if(mCanCallJava) {

       res = createThreadEtc(_threadLoop,this, name, priority,

                                   stack,&mThread);

    } else{

       res = androidCreateRawThreadEtc(_threadLoop, this, name, priority,

                                   stack,&mThread);

    }

上面的mCanCallJava將線程建立函數的邏輯分爲兩個分支,雖傳入的參數都有_threadLoop,但調用的函數卻不一樣。先直接看mCanCallJava爲true的這個分支,代碼以下所示:

[-->Thread.h::createThreadEtc()函數]

inline bool createThreadEtc(thread_func_tentryFunction,

                            void *userData,

                            const char*threadName = "android:unnamed_thread",

                            int32_tthreadPriority = PRIORITY_DEFAULT,

                            size_tthreadStackSize = 0,

                            thread_id_t*threadId = 0)

{

    returnandroidCreateThreadEtc(entryFunction, userData, threadName,

                   threadPriority, threadStackSize,threadId) ? true : false;

}

它調用的是androidCreateThreadEtc函數,相關代碼以下所示:

// gCreateThreadFn是函數指針,初始化時和mCanCallJava爲false時使用的是同一個

//線程建立函數。那麼有地方會修改它嗎?

static android_create_thread_fn gCreateThreadFn= androidCreateRawThreadEtc;

int androidCreateThreadEtc(android_thread_func_tentryFunction,

                            void*userData,const char* threadName,

                            int32_tthreadPriority,size_t threadStackSize,

                            android_thread_id_t*threadId)

{

    returngCreateThreadFn(entryFunction, userData, threadName,

                               threadPriority,threadStackSize, threadId);

}

若是沒有人修改這個函數指針,那麼mCanCallJava就是虛晃一槍,並沒有什麼做用,很惋惜,代碼中有的地方是會修改這個函數指針的指向的,請看:

2. zygote偷樑換柱

在第四章4.2.1的第2小節AndroidRuntime調用startReg的地方,就有可能修改這個函數指針,其代碼以下所示:

[-->AndroidRuntime.cpp]

/*static*/ int AndroidRuntime::startReg(JNIEnv*env)

{

   //這裏會修改函數指針爲javaCreateThreadEtc

  androidSetCreateThreadFunc((android_create_thread_fn)javaCreateThreadEtc);

  return0;

}

因此,若是mCanCallJava爲true,則將調用javaCreateThreadEtc。那麼,這個函數有什麼特殊之處呢?來看其代碼,以下所示:

[-->AndroidRuntime.cpp]

int AndroidRuntime::javaCreateThreadEtc(

                               android_thread_func_tentryFunction,

                                void* userData,

                                const char*threadName,

                                int32_tthreadPriority,

                                size_t threadStackSize,

                               android_thread_id_t* threadId)

{

    void**args = (void**) malloc(3 * sizeof(void*));  

    intresult;

   args[0] = (void*) entryFunction;

   args[1] = userData;

   args[2] = (void*) strdup(threadName);

    //調用的仍是androidCreateRawThreadEtc,但線程函數卻換成了javaThreadShell。

    result= androidCreateRawThreadEtc(AndroidRuntime::javaThreadShell, args,

                         threadName, threadPriority,threadStackSize, threadId);

    returnresult;

}

[-->AndroidRuntime.cpp]

int AndroidRuntime::javaThreadShell(void* args){

      ......

     intresult;

    //把這個線程attach到JNI環境中,這樣這個線程就能夠調用JNI的函數了

    if(javaAttachThread(name, &env) != JNI_OK)

       return -1;

     //調用實際的線程函數幹活

     result = (*(android_thread_func_t)start)(userData);

   //從JNI環境中detach出來。

   javaDetachThread();

   free(name);

    returnresult;

}

3. 費力而討好

你明白mCanCallJava爲true的目的了嗎?它建立的新線程將:

·  在調用你的線程函數以前會attach到 JNI環境中,這樣,你的線程函數就能夠無憂無慮地使用JNI函數了。

·  線程函數退出後,它會從JNI環境中detach,釋放一些資源。

第二點尤爲重要,由於進程退出前,dalvik虛擬機會檢查是否有attach了,可是最後未detach的線程若是有,則會直接abort(這不是一件好事)。若是你關閉JNI check選項,就不會作這個檢查,但我以爲,這個檢查和資源釋放有關係。建議仍是重視JNIcheck。若是直接使用POSIX的線程建立函數,那麼凡是使用過attach的,最後就都須要detach!

Android爲了dalvik的健康真是費盡心機呀。

4. 線程函數_threadLoop介紹

不論一分爲二是如何處理的,最終的線程函數_threadLoop都會被調用,爲何不直接調用用戶傳入的線程函數呢?莫非_threadLoop會有什麼暗箱操做嗎?下面,咱們來看:

[-->Thread.cpp]

int Thread::_threadLoop(void* user)

{

   Thread* const self = static_cast<Thread*>(user);

   sp<Thread> strong(self->mHoldSelf);

   wp<Thread> weak(strong);

   self->mHoldSelf.clear();

 

#if HAVE_ANDROID_OS

   self->mTid = gettid();

#endif

 

    boolfirst = true;

 

    do {

       bool result;

        if(first) {

           first = false;

          //self表明繼承Thread類的對象,第一次進來將調用readyToRun,看看是否準備好

          self->mStatus = self->readyToRun();

           result = (self->mStatus == NO_ERROR);

 

           if (result && !self->mExitPending) {

                result = self->threadLoop();

           }

        }else {

          /*

調用子類實現的threadLoop函數,注意這段代碼運行在一個do-while循環中。

             這表示即便咱們的threadLoop返回了,線程也不必定會退出。

         */

           result = self->threadLoop();

        }

   /*

線程退出的條件:

    1)result 爲false。這代表,若是子類在threadLoop中返回false,線程就能夠

    退出。這屬於主動退出的狀況,是threadLoop本身不想繼續幹活了,因此返回false。

讀者在本身的代碼中千萬別寫錯threadLoop的返回值。

    2)mExitPending爲true,這個變量可由Thread類的requestExit函數設置,這種

    狀況屬於被動退出,由於由外界強制設置了退出條件。

   */

        if(result == false || self->mExitPending) {

           self->mExitPending = true;

           self->mLock.lock();

           self->mRunning = false;

           self->mThreadExitedCondition.broadcast();

           self->mLock.unlock();

           break;

        }

       strong.clear();

       strong = weak.promote();

    }while(strong != 0);

   

    return0;

}

關於_threadLoop,咱們就介紹到這裏。請讀者務必注意下面一點:

·  threadLoop運行在一個循環中,它的返回值能夠決定是否退出線程。

5.3.2  經常使用同步類

同步,是多線程編程中不可迴避的話題,同時也是一個很是複雜的問題。這裏,只簡單介紹一下Android提供的同步類。這些類,只對系統提供的多線程同步函數(這種函數咱們也稱之爲Raw API)進行了面向對象的封裝,讀者必須先理解Raw API,而後才能真正掌握其具體用法。

瞭解Windows下的多線程編程,有不少參考資料,但我覺得,如今先學習MSDN就能夠了。有關Linux下完整系統闡述多線程編程的書籍目前較少,這裏推薦一本含金量較高的著做《Programmingwith POSIX Thread》(本書只有英文版的,由Addison-Wesley出版)。

Android提供了兩個封裝好的同步類,它們是Mutex和Condition。這是重量級的同步技術,通常內核會有對應的支持。另外,OS還提供了簡單的原子操做,這些也算是同步技術的一種。下面分別來介紹這三種東西。

1. 互斥類——Mutex

Mutex是互斥類,用於多線程訪問同一個資源的時候,保證一次只能有一個線程能訪問該資源。在《Windows核心編程》一書中,對於這種互斥訪問有一個很形象的比喻:想象你在飛機上如廁,這時衛生間的信息牌上顯示「有人」,你必須等裏邊的人出來後纔可進去。這就是互斥的含義。

下面來看Mutex的實現方式,它們都很簡單。

(1)Mutex介紹

其代碼以下所示:

[-->Thread.h::Mutex的聲明和實現]

inline Mutex::Mutex(int type, const char* name){

    if(type == SHARED) {

      //type若是是SHARED,則代表這個Mutex支持跨進程的線程同步

      //之後咱們在Audio系統和Surface系統中會常常見到這種用法

       pthread_mutexattr_t attr;

       pthread_mutexattr_init(&attr);

       pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);

       pthread_mutex_init(&mMutex, &attr);

       pthread_mutexattr_destroy(&attr);

    } else {

       pthread_mutex_init(&mMutex, NULL);

    }

}

inline Mutex::~Mutex() {

   pthread_mutex_destroy(&mMutex);

}

inline status_t Mutex::lock() {

    return-pthread_mutex_lock(&mMutex);

}

inline void Mutex::unlock() {

   pthread_mutex_unlock(&mMutex);

}

inline status_t Mutex::tryLock() {

    return-pthread_mutex_trylock(&mMutex);

}

關於Mutex的使用,除了初始化外,最重要的是lock和unlock函數的使用,它們的用法以下:

·  要想獨佔衛生間,必須先調用Mutex的lock函數。這樣,這個區域就被鎖住了。若是這塊區域以前已被別人鎖住,lock函數則會等待,直到能夠進入這塊區域爲止。系統保證一次只有一個線程能lock成功。

·  當你「方便」完畢,記得調用Mutex的unlock以釋放互斥區域。這樣,其餘人的lock才能夠成功返回。

·  另外,Mutex還提供了一個trylock函數,該函數只是嘗試去鎖住該區域,使用者須要根據trylock的返回值判斷是否成功鎖住了該區域。

注意,以上這些內容都和Raw API有關,不瞭解它的讀者可自行學習與它相關的知識。在Android系統中,多線程也是常見和重要的編程手段,務請你們重視。

Mutex類確實比Raw API方便好用,不過仍是稍顯麻煩。來看下一節。

(2)AutoLock介紹

AutoLock類是定義在Mutex內部的一個類,它實際上是一幫「懶人」搞出來的,爲何這麼說呢?先來看看使用Mutex夠多麻煩:

·  顯示調用Mutex的lock。

·  在某個時候要記住調用該Mutex的unlock。

以上這些操做都必須一一對應,不然會出現「死鎖」!有些代碼中,在判斷分支特別多的狀況下,unlock這句代碼被寫得比比皆是,如稍有不慎,在某處就會忘寫了它。有什麼好辦法能解決這個問題嗎?終於有人想出來一個好辦法,就是充分利用了C++的構造和析構函數,只需一看AutoLock的定義就會明白。代碼以下所示:

[-->Thread.h Mutex::Autolock聲明和實現]

    classAutolock {

   public:

        //構造的時候調用lock

       inline Autolock(Mutex& mutex) : mLock(mutex)  { mLock.lock(); }

       inline Autolock(Mutex* mutex) : mLock(*mutex) { mLock.lock(); }

        //析構的時候調用unlock

       inline ~Autolock() { mLock.unlock(); }

   private:

       Mutex& mLock;

    };

AutoLock的用法很簡單:

·  先定義一個Mutex,如 Mutex xlock;

·  在使用xlock的地方,定義一個AutoLock,如 AutoLock autoLock(xlock)。

因爲C++對象的構造和析構函數都是自動被調用的,因此在AutoLock的生命週期內,xlock的lock和unlock也就自動被調用了,這樣就省去了重複書寫unlock的麻煩,並且lock和unlock的調用確定是一一對應的,這樣就絕對不會出錯。

2. 條件類——Condition

多線程同步中的條件類對應的是下面一種使用場景:

·  線程A作初始化工做,而其餘線程好比線程B、C必須等到初始化工做完後才能工做,即線程B、C在等待一個條件,咱們稱B、C爲等待者。

·  當線程A完成初始化工做時,會觸發這個條件,那麼等待者B、C就會被喚醒。觸發這個條件的A就是觸發者。

上面的使用場景很是形象,並且條件類提供的函數也很是形象,它的代碼以下所示:

[-->Thread.h::Condition的聲明和實現]

class Condition {

public:

    enum {

       PRIVATE = 0,

       SHARED = 1

    };

 

   Condition();

   Condition(int type);//若是type是SHARED,表示支持跨進程的條件同步

   ~Condition();

    //線程B和C等待事件,wait這個名字是否是很形象呢?

   status_t wait(Mutex& mutex);

  //線程B和C的超時等待,B和C能夠指定等待時間,當超過這個時間,條件卻還不知足,則退出等待

   status_t waitRelative(Mutex& mutex, nsecs_t reltime);

    //觸發者A用來通知條件已經知足,可是B和C只有一個會被喚醒

    voidsignal();

    //觸發者A用來通知條件已經知足,全部等待者都會被喚醒

    voidbroadcast();

 

private:

#if defined(HAVE_PTHREADS)

   pthread_cond_t mCond;

#else

   void*   mState;

#endif

}

聲明很簡單,定義也很簡單,代碼以下所示:

inline Condition::Condition() {

   pthread_cond_init(&mCond, NULL);

}

inline Condition::Condition(int type) {

    if(type == SHARED) {//設置跨進程的同步支持

       pthread_condattr_t attr;

        pthread_condattr_init(&attr);

       pthread_condattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);

       pthread_cond_init(&mCond, &attr);

       pthread_condattr_destroy(&attr);

    } else{

       pthread_cond_init(&mCond, NULL);

    }

}

inline Condition::~Condition() {

   pthread_cond_destroy(&mCond);

}

inline status_t Condition::wait(Mutex&mutex) {

    return-pthread_cond_wait(&mCond, &mutex.mMutex);

}

inline status_tCondition::waitRelative(Mutex& mutex, nsecs_t reltime) {

#if defined(HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE)

    structtimespec ts;

   ts.tv_sec  = reltime/1000000000;

   ts.tv_nsec = reltime%1000000000;

    return-pthread_cond_timedwait_relative_np(&mCond, &mutex.mMutex, &ts);

    ...... //有些系統沒有實現POSIX的相關函數,因此不一樣系統須要調用不一樣的函數

#endif

}

inline void Condition::signal() {

   pthread_cond_signal(&mCond);

}

inline void Condition::broadcast() {

   pthread_cond_broadcast(&mCond);

}

能夠看出,Condition的實現全是憑藉調用了Raw API的pthread_cond_xxx函數。這裏要重點說明的是,Condition類必須配合Mutex來使用。什麼意思?

·  上面代碼中,不管是wait、waitRelative、signal仍是broadcast的調用,都放在一個Mutex的lock和unlock範圍中,尤爲是wait和waitRelative函數的調用,這是強制性的。

來看一個實際的例子,加深一下對Condition類和Mutex類使用的印象。這個例子是Thread類的requestExitAndWait,目的是等待工做線程退出,代碼以下所示:

[-->Thread.cpp]

status_t Thread::requestExitAndWait()

{

    ......

   requestExit();//設置退出變量mExitPending爲true

    Mutex::Autolock_l(mLock);//使用Autolock,mLock被鎖住

    while(mRunning == true) {

    /*

     條件變量的等待,這裏爲何要經過while循環來反覆檢測mRunning?

     由於某些時候即便條件類沒有被觸發,wait也會返回。關於這個問題,強烈建議讀者閱讀

     前邊推薦的《Programming with POSIX Thread》一書。

   */

      mThreadExitedCondition.wait(mLock);

    }

 

   mExitPending = false;

   //退出前,局部變量Mutex::Autolock _l的析構會被調用,unlock也就會被自動調用。

    returnmStatus;

}

那麼,什麼地方會觸發這個條件呢?是在工做線程退出前。其代碼以下所示:

[-->Thread.cpp]

int Thread::_threadLoop(void* user)

{

    Thread* const self =static_cast<Thread*>(user);

   sp<Thread> strong(self->mHoldSelf);

   wp<Thread> weak(strong);

   self->mHoldSelf.clear();

 

    do {

          ......  

          result= self->threadLoop();//調用子類的threadLoop函數

           ......

         //若是mExitPending爲true,則退出

        if(result == false || self->mExitPending) {

           self->mExitPending = true;

           //退出前觸發條件變量,喚醒等待者

           self->mLock.lock();//lock鎖住

           //mRunning的修改位於鎖的保護中。若是你閱讀了前面推薦的書,這裏也就不難理解了

            self->mRunning = false;

           self->mThreadExitedCondition.broadcast();

           self->mLock.unlock();//釋放鎖

           break;//退出循環,此後該線程函數會退出

        }

       ......

    }while(strong != 0);

   

    return0;

}

關於Android多線程的同步類,暫時介紹到此吧。固然,這些類背後所隱含的知識及技術是讀者須要倍加劇視的。

但願咱們能養成一種由點及面的學習方法。以咱們的同步類爲例,假設你是第一次接觸多線程編程,也學會了如何使用Mutex和Condition這兩個類,不妨以這兩個類代碼中所傳遞的知識作爲切入點,把和多線程相關的全部知識(這個知識不只僅是函數的使用,還包括多線程的原理,多線程的編程模型,甚至是如今很熱門的並行多核編程)廣泛瞭解一下。只有深入理解並掌握了原理等基礎和框架性的知識,才能以不變應萬變,才能作到遊刃有餘。

3. 原子操做函數介紹

什麼是原子操做?所謂原子操做,就是該操做毫不會在執行完畢前被任何其餘任務或事件打斷,也就說,原子操做是最小的執行單位。

上面這句話放到代碼中是什麼意思?請看一個例子:

[-->例子]

static int g_flag = 0; //全局變量g_flag

static Mutex lock  ;//全局的鎖

//線程1執行thread1

void thread1()

{

  //g_flag遞減,每次操做前鎖住

  lock.lock();

   g_flag--;

 lock.unlock();

}

//線程2中執行thread2函數

void thread2()

{

  lock.lock();

  g_flag++; //線程2對g_flag進行遞增操做,每次操做前要取得鎖

lock.unlock();

}

爲何須要Mutex來幫忙呢?由於g_flags++或者g_flags—操做都不是原子操做。從彙編指令的角度看,C/C++中的一條語句對應了數條彙編指令。以g_flags++操做爲例,它生成的彙編指令可能就是如下三條:

·  從內存中取數據到寄存器。

·  對寄存器中的數據進行遞增操做,結果還在寄存器中。

·  寄存器的結果寫回內存。

這三條彙編指令,若是按正常的順序連續執行,是沒有問題的,但在多線程時就不能保證了。例如,線程1在執行第一條指令後,線程2因爲調度的緣由,搶先在線程1以前連續執行完了三條指令。這樣,線程1繼續執行指令時,它所使用的值就不是線程2更新後的值,而是以前的舊值。再對這個值進行操做便沒有意義了。

在通常狀況下,處理這種問題可使用Mutex來加鎖保護,但Mutex的使用比它所要保護的內容還複雜,例如,鎖的使用將致使從用戶態轉入內核態,有較大的浪費。那麼,有沒有簡便些的辦法讓這些加、減等操做不被中斷呢?

答案是確定的,但這須要CPU的支持。在X86平臺上,一個遞增操做能夠用下面的內嵌彙編語句實現:

#define LOCK "lock;"

INT32 InterlockedIncrement(INT32* lpAddend)

{

  /*

   這是咱們在Linux平臺上實現Windows API時使用的方法。

   其中在SMP系統上,LOCK定義成」lock;」表示鎖總線,這樣同一時刻只能有一個CPU訪問總線。

   非SMP系統,LOCK定義成空。因爲InterlockedIncrement要返回遞增前的舊值,因此咱們

   使用了xaddl指令,它先交換源和目的的操做數,再進行遞增操做。

*/

        INT32i = 1;

        __asm____volatile__(

                 LOCK"xaddl %0, %1"

                 :"+r"(i), "+m" (*lpAddend)

                 :: "memory");

        return*lpAddend;

}

Android提供了相關的原子操做函數。這裏,有必要介紹一下各個函數的做用。

[-->Atomic.h],注意該文件位置在system/core/include/cutils目錄中。

//原子賦值操做,結果是*addr=value

void android_atomic_write(int32_t value,volatile int32_t* addr);

//下面全部函數的返回值都是操做前的舊值

//原子加1和原子減1

int32_t android_atomic_inc(volatile int32_t*addr);

int32_t android_atomic_dec(volatile int32_t*addr);

//原子加法操做,value爲被加數

int32_t android_atomic_add(int32_t value,volatile int32_t* addr);

//原子「與」和「或」操做

int32_t android_atomic_and(int32_t value,volatile int32_t* addr);

int32_t android_atomic_or(int32_t value,volatile int32_t* addr);

/*

條件交換的原子操做。只有在oldValue等於*addr時,纔會把newValue賦值給*addr

這個函數的返回值須特別注意。返回值非零,表示沒有進行賦值操做。返回值爲零,表示

進行了原子操做。

*/

int android_atomic_cmpxchg(int32_t oldvalue,int32_t newvalue,

                                volatile int32_t*addr);

有興趣的話,讀者能夠對上述函數的實現進行深刻研究,其中,

·  X86平臺的實如今system/core/libcutils/Atomic.c中,注意其代碼在#elif defined(__i386__) || defined(__x86_64__)所包括的代碼段內。

·  ARM平臺的實如今system/core/libcutils/atomic-android-arm.S彙編文件中。

原子操做的最大好處在於避免了鎖的使用,這對整個程序運行效率的提升有很大幫助。目前,在多核並行編程中,最高境界就是徹底不使用鎖。固然,它的難度可想而知是巨大的。

5.4  Looper和Handler類分析

就應用程序而言,Android系統中Java的和其餘系統上的相同,是靠消息驅動來工做的,它們大體的工做原理以下:

·  有一個消息隊列,能夠往這個消息隊列中投遞消息。

·  有一個消息循環,不斷從消息隊列中取出消息,而後處理。

咱們用圖5-1來展現這個工做過程:

圖5-1  線程和消息處理原理圖

從圖中能夠看出:

·  事件源把待處理的消息加入到消息隊列,通常是加至隊列尾,一些優先級高的消息也能夠加至隊列頭。事件源提交的消息能夠是按鍵、觸摸屏等物理事件產生的消息,也能夠是來自系統或應用程序自己發出的請求消息。

·  處理線程不斷從消息隊列頭中取出消息並處理,事件源能夠把優先級高的消息放到隊列頭,這樣,優先級高的消息就會首先被處理。

在Android系統中,這些工做主要由Looper和Handler來實現:

·  Looper類,用於封裝消息循環,而且有一個消息隊列。

·  Handler類,有點像輔助類,它封裝了消息投遞,消息處理等接口。

Looper類是其中的關鍵。先來看看它是怎麼作的。

5.4.1 Looper類的分析

咱們以Looper使用的一個常見例子來分析Looper類。

[-->例子1]

//定義一個LooperThread

class LooperThread extends Thread {

    publicHandler mHandler;

public void run() {

     //① 調用prepare

     Looper.prepare();

    ......

     //② 進入消息循環

Looper.loop();

   }

}

//應用程序使用LooperThread

{

  ......

  newLooperThread().start();//啓動新線程,線程函數是run

}

上面的代碼一共有兩個關鍵調用,咱們對其逐一進行分析。

1. 準備好了嗎?

第一個調用函數是Looper的prepare函數。它會作什麼工做呢?其代碼以下所示:

[-->Looper.java]

  publicstatic final void prepare() {

   //一個Looper只能調用一次prepare 

  if(sThreadLocal.get() != null) {

     thrownew RuntimeException("Only one Looper may be created per thread");

  }

   //構造一個Looper對象,設置到調用線程的局部變量中

   sThreadLocal.set(newLooper());

}

//sThreadLocal定義

private static final ThreadLocal sThreadLocal =new ThreadLocal();

ThreadLocal是Java中的線程局部變量類,全名應該是Thread Local Variable。我以爲,它的實現和操做系統提供的線程本地存儲(TLS)有關係。總之,該類有兩個關鍵函數:

·  set:設置調用線程的局部變量。

·  get:獲取調用線程的局部變量。

注意,set/get的結果都和調用這個函數的線程有關。ThreadLocal類可參考JDK API文檔或Android API文檔。

根據上面的分析可知,prepare會在調用線程的局部變量中設置一個Looper對象。這個調用線程就是LooperThread的run線程。先看看Looper對象的構造,其代碼以下所示:

[-->Looper.java]

private Looper(){

 //構造一個消息隊列

 mQueue =new MessageQueue();

 mRun =true;

 //獲得當前線程的Thread對象

 mThread =Thread.currentThread();

}

prepare函數很簡單,它主要乾了一件事:

·  在調用prepare的線程中,設置了一個Looper對象,這個Looper對象就保存在這個調用線程的TLV中。而Looper對象內部封裝了一個消息隊列。

也就是說,prepare函數經過ThreadLocal機制,巧妙地把Looper和調用線程關聯在一塊兒了。要了解這樣作的目的是什麼,須要再看第二個重要函數。

2. Looper循環

代碼以下所示:

[-->Looper.java]

public static final void loop() {

       Looper me = myLooper();//myLooper返回保存在調用線程TLV中的Looper對象

        //取出這個Looper的消息隊列

       MessageQueue queue = me.mQueue;

       while (true) {

            Message msg = queue.next();

        //處理消息,Message對象中有一個target,它是Handler類型

          //若是target爲空,則表示須要退出消息循環

           if (msg != null) {

               if (msg.target == null) {

                     return;

               }

              //調用該消息的Handler,交給它的dispatchMessage函數處理

              msg.target.dispatchMessage(msg);

              msg.recycle();

           }

        }

}

//myLooper函數返回調用線程的線程局部變量,也就是存儲在其中的Looper對象

public static final Looper myLooper() {

       return (Looper)sThreadLocal.get();

}

經過上面的分析會發現,Looper的做用是:

·  Looper封裝了一個消息隊列。

·  Looper的prepare函數把這個Looper和調用prepare的線程(也就是最終的處理線程)綁定在一塊兒了。

·  處理線程調用loop函數,處理來自該消息隊列的消息。

當事件源向這個Looper發送消息的時候,實際上是把消息加到這個Looper的消息隊列裏了。那麼,該消息就將由和Looper綁定的處理線程來處理。那麼,事件源又是怎麼向Looper消息隊列添加消息的呢?來看下一節。

3. Looper、Message和Handler的關係

Looper、Message和Handler之間也存在曖昧關係,不過要比RefBase那三個簡單得多,用兩句話就能夠說清楚:

·  Looper中有一個Message隊列,裏邊存儲的是一個個待處理的Message。

·  Message中有一個Handler,這個Handler是用來處理Message的。

其中,Handler類封裝了不少瑣碎的工做。先來認識一下這個Handler。

5.4.2 Handler分析

1. 初識Handler

Handler中所包括的成員:

[-->Handler.java]

final MessageQueue mQueue;//Handler中也有一個消息隊列

final Looper mLooper;//也有一個Looper

final Callback mCallback;//有一個回調用的類

這幾個成員變量是怎麼使用的呢?這首先得分析Handler的構造函數。Handler一共有四個構造函數,它們主要的區別,是在對上面三個重要成員變量的初始化上。咱們試對其進行逐一分析。

[-->Handler.java]

//構造函數1

public Handler() {

        //得到調用線程的Looper

         mLooper = Looper.myLooper();

        if(mLooper == null) {

           throw new RuntimeException(......);

        }

        //獲得Looper的消息隊列

       mQueue = mLooper.mQueue;

       //無callback設置

       mCallback = null;

    }

  

 //構造函數2

   publicHandler(Callback callback) {

        mLooper = Looper.myLooper();

        if(mLooper == null) {

throw new RuntimeException(......);

        }

        //和構造函數1相似,只不過多了一個設置callback

       mQueue = mLooper.mQueue;

       mCallback = callback;

    }

//構造函數3

   publicHandler(Looper looper) {

       mLooper = looper; //looper由外部傳入,是哪一個線程的Looper不肯定

       mQueue = looper.mQueue;

       mCallback = null;

    }

//構造函數4,和構造函數3相似,只不過多了callback設置

   publicHandler(Looper looper, Callback callback) {

        mLooper= looper;

       mQueue = looper.mQueue;

       mCallback = callback;

}

在上述構造函數中,Handler中的消息隊列變量最終都會指向了Looper的消息隊列,Handler爲什麼要如此作?

2. Handler的真面目

根據前面的分析可知,Handler中的消息隊列實際就是某個Looper的消息隊列,那麼,Handler作如此安排的目的何在?

在回答這個問題以前,我先來問一個問題:

·  怎麼往Looper的消息隊列插入消息?

若是不知道Handler,這裏有一個很原始的方法:

·  調用Looper的myQueue,它將返回消息隊列對象MessageQueue。

·  構造一個Message,填充它的成員,尤爲是target變量。

·  調用MessageQueue的enqueueMessage,將消息插入消息隊列。

這種原始方法的確很麻煩,且極容易出錯。但有了Handler後,咱們的工做就變得異常簡單了。Handler更像一個輔助類,幫助咱們簡化編程的工做。

2.1 Handler和Message

Handle提供了一系列函數,幫助咱們完成建立消息和插入消息隊列的工做。這裏只列舉其中一二。要掌握詳細的API,則須要查看相關文檔。

//查看消息隊列中是否有消息碼是what的消息

final boolean    hasMessages(int what)

//從Handler中建立一個消息碼是what的消息

final Message    obtainMessage(int what)

//從消息隊列中移除消息碼是what的消息

final void       removeMessages(int what)

//發送一個只填充了消息碼的消息

final boolean    sendEmptyMessage(int what)

//發送一個消息,該消息添加到隊列尾

final boolean    sendMessage(Message msg)

//發送一個消息,該消息添加到隊列頭,因此優先級很高

final boolean    sendMessageAtFrontOfQueue(Message msg)

只需對上面這些函數稍做分析,就能明白其餘的函數。現以sendMessage爲例,其代碼以下所示:

[-->Handler.java]

public final boolean sendMessage(Message msg)  

 {  

    return sendMessageDelayed(msg, 0); //調用sendMessageDelayed 

 } 

[-->Handler.java]

// delayMillis是以當前調用時間爲基礎的相對時間

public final boolean sendMessageDelayed(Message msg, long delayMillis)  

{  

   if (delayMillis < 0) {  

      delayMillis = 0;  

  }  

   //調用sendMessageAtTime,把當前時間算上

  return sendMessageAtTime(msg,SystemClock.uptimeMillis() + delayMillis);  

}  

   [-->Handler.java]

//uptimeMillis 是絕對時間,即sendMessageAtTime函數處理的是絕對時間

public boolean sendMessageAtTime(Message msg, long uptimeMillis){  

    boolean sent = false;  

    MessageQueue queue = mQueue;  

    if (queue != null) {  

//把Message的target設置爲本身,而後加入到消息隊列中  

         msg.target = this;  

         sent = queue.enqueueMessage(msg, uptimeMillis);  

    }  

     return sent;  

}  

看到上面這些函數能夠想見,若是沒有Handler的輔助,當咱們本身操做MessageQueue的enqueueMessage時,得花費多大功夫!

Handler把Message的target設爲本身,是由於Handler除了封裝消息添加等功能外還封裝了消息處理的接口。

2.2 Handler的消息處理

剛纔,咱們往Looper的消息隊列中加入了一個消息,按照Looper的處理規則,它在獲取消息後,會調用target的dispatchMessage函數,再把這個消息派發給Handler處理。Handler在這塊是如何處理消息的呢?

[-->Handler.java]

public void dispatchMessage(Message msg) {

        //若是Message自己有callback,則直接交給Message的callback處理

        if(msg.callback != null) {

           handleCallback(msg);

        }else {

          //若是本Handler設置了mCallback,則交給mCallback處理

           if (mCallback != null) {

               if (mCallback.handleMessage(msg)) {

                    return;

               }

           }

           //最後纔是交給子類處理

           handleMessage(msg);

        }

    }

 dispatchMessage定義了一套消息處理的優先級,它們分別是:

·  Message若是自帶了callback處理,則交給callback處理。

·  Handler若是設置了全局的mCallback,則交給mCallback處理。

·  若是上述都沒有,該消息則會被交給Handler子類實現的handleMessage來處理。固然,這須要從Handler派生並重載handleMessage函數。

在一般狀況下,咱們通常都是採用第三種方法,即在子類中經過重載handleMessage來完成處理工做的。

至此,Handler知識基本上講解完了,但是在實際編碼過程當中還有一個重要問題須要警戒。下一節內容就將談及此問題。

5.4.3 Looper和Handler的同步關係

Looper和Handler會有什麼同步關係呢?它們之間確實有同步關係,並且若是不注意此關係,定要鑄成大錯!

同步關係確定和多線程有關,看下面的一個例子:

[-->例子2]

//先定義一個LooperThread類

class LooperThread extends Thread {

    publicLooper myLooper = null;//定義一個public的成員myLooper,初值爲空。

public void run() { //假設run在線程2中執行

         Looper.prepare();

        // myLooper必須在這個線程中賦值

         myLooper = Looper.myLooper();

Looper.loop();

   }

}

//下面這段代碼在線程1中執行,而且會建立線程2

{

  LooperThreadlpThread= new LooperThread;

  lpThread.start();//start後會建立線程2

  Looper looper = lpThread.myLooper;//<======注意

 // thread2Handler和線程2的Looper掛上鉤

  Handler thread2Handler = new Handler(looper); 

 //sendMessage發送的消息將由線程2處理 

  threadHandler.sendMessage(...)

}

上面這段代碼的目的很簡單:

·  線程1中建立線程2,而且線程2經過Looper處理消息。

·  線程1中獲得線程2的Looper,而且根據這個Looper建立一個Handler,這樣發送給該Handler的消息將由線程2處理。

但很惋惜,上面的代碼是有問題的。若是咱們熟悉多線程,就會發現標有「注意」的那行代碼存在着嚴重問題。myLooper的建立是在線程2中,而looper的賦值則在線程1,頗有可能此時線程2的run函數還沒來得及給myLooper賦值,這樣線程1中的looper將取到myLooper的初值,也就是looper等於null。另外,

Handler thread2Handler = new Handler(looper) 不能替換成

Handler thread2Handler = new Handler(Looper.myLooper())

這是由於,myLooper返回的是調用線程的Looper,即Thread1的Looper,而不是咱們想要的Thread2的Looper。

對這個問題,能夠採用同步的方式進行處理。你是否是有點火燒眉毛地想完善這個例子了?其實Android早就替咱們想好了,它提供了一個HandlerThread來解決這個問題。

5.4.4  HandlerThread介紹

HandlerThread完美地解決了myLooper可能爲空的問題。來看看它是怎麼作的。代碼以下所示:

[-->HandlerThread]

public class HandlerThread extends Thread{

//線程1調用getLooper來得到新線程的Looper

 publicLooper getLooper() {

       ......      

       synchronized (this) {

           while (isAlive() && mLooper == null) {

               try {

                    wait();//若是新線程還未建立Looper,則等待

               } catch (InterruptedException e) {

               }

           }

        }

       return mLooper;

    }

   

//線程2運行它的run函數,looper就是在run線程裏建立的。

  publicvoid run() {

       mTid = Process.myTid();

       Looper.prepare();  //建立這個線程上的Looper

       synchronized (this) {

           mLooper = Looper.myLooper();

           notifyAll();//通知取Looper的線程1,此時Looper已經建立好了。

        }

       Process.setThreadPriority(mPriority);

       onLooperPrepared();

       Looper.loop();

       mTid = -1;

    }

}

HandlerThread很簡單,小小的wait/ notifyAll就解決了咱們的難題。爲了不重複發明輪子,咱們仍是多用HandlerThread類吧!

5.5  本章小結

本章主要分析了Android代碼中最多見的幾個類:其中在Native層包括與對象生命週期相關的RefBase、sp、wp、LightRefBase類,以及Android爲多線程編程提供的Thread類和相關的同步類;Java層則包括使用最爲普遍的Handler類和Looper類。另外,還分析了方類HandlerThread,它下降了建立和使用帶有消息隊列的線程的難度。



 本書中文版由機械工業出版社出版,原書做者Jeffrey Richter。

相關文章
相關標籤/搜索