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

/*

如果是第一次引用,则调用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(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_typeRefBase:createWeak(const voidid)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_implconst 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_implconst 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。