天天看點

android 中的的 sp/wp/RefBase

轉自:http://blog.csdn.net/innost/article/details/6752443

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(pA);

   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(pA);

請看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* ) { }

呵呵,addWeakRef啥都沒做,因為這是release版走的分支。調試版的代碼我們就不讨論了,它是給創造RefBase、 sp,以及wp的人調試用的。

說明 調試版分支的代碼很多,看來創造它們的人也在為不了解它們之間的暧昧關系痛苦不已。

總之,一共有這麼幾個不用考慮的函數,下面都已列出來了。以後再碰見它們,幹脆就直接跳過去:

void addStrongRef(const void* ) { }

void removeStrongRef(const void* ) { }

void addWeakRef(const void* ) { }

void removeWeakRef(const void* ) { }

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_t c = 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);

 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(const sp<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(const void* id) const

{

//調用影子對象的incWeak,這個我們剛才講過了,它會導緻影子對象的弱引用計數增加1。

 mRefs->incWeak(id); 

 return mRefs;  //傳回影子對象本身。

}

我們可以看到,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。

  const int32_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;

        }

    }

}

在例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。

    const int32_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。

    const int32_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(pA);

   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

{

    return sp<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)//有點看不清楚。

{

//上面那行代碼夠簡潔,但是不友善閱讀,我們寫成下面這樣:

}

2.成敗在此一舉

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

[-->RefBase.cpp]

bool RefBase::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;

        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();

    }

    return true; //由弱生強成功。

}

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

{

   public A()

   {

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

   }

}

int main()

{

   A *pA = new A();

    wp<A> wpA(pA);//弱引用計數加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);

    const int32_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);

    const int32_t c = android_atomic_dec(&impl->mWeak);

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

    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);

        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:

    inline LightRefBase() : 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);

        }

    }

    inline int32_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平台上,不考慮多線程的話,将原子操作換成普通的非原子操作不是也可以嗎?如果更細心更負責任的話,你可以自己用彙編來實作常用的原子操作,核心代碼中有現成的函數,一看就會明白。)

如果把破解代碼看成是攻城略地的話,我們必須學會靈活多變,而且應力求破解方法日臻極緻!

繼續閱讀