從事 Android native framework 層的開發一定會接觸到兩個 C++ template class: sp 及 wp,分別是 strong pointer 及 weak pointer 的簡稱,是兩個看似簡單但卻有著魔鬼細節的 utility class。
一個比較典型使用 weak pointer 的狀況就是 Listener pattern,例如:
class TimedTextPlayer {
wp<MediaPlayerBase> mListener;
}
void TimedTextPlayer::postTextEvent(const sp<ParcelEvent>& parcel, int64_t timeUs) {
sp<MediaPlayerBase> listener = mListener.promote();
if (listener != NULL) {
// ...
listener->getCurrentPosition(&positionMs);
// ...
}
}
這個例子如果使用 sp 來記錄 listener 很容易因為忘了移除 listener 而造成 memory leak。用了 wp 也就沒有這個困擾,不過額外的開銷就是 wp 必須先呼叫 promote() 拿到一個 sp 才能使用。
如果要了解 sp 及 wp 的原理,必定要先了解 RefBase,因為這兩個 class 都必須搭配繼承自 RefBase 的 class 才能運作,以下是他們之間的大致關係:
其中的 promote() 實現如下:
template<typename T> sp<T> wp<T>::promote() const {
sp<T> result;
if (m_ptr && m_refs->attemptIncStrong(&result)) {
result.set_pointer(m_ptr);
}
return result;
}
bool RefBase::weakref_type::attemptIncStrong(const void* id)
{
incWeak(id);
weakref_impl* const impl = static_cast<weakref_impl*>(this);
int32_t curCount = impl->mStrong;
ALOG_ASSERT(curCount >= 0, "attemptIncStrong called on %p after underflow",
this);
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) {
// Attempting to acquire first strong reference... this is allowed
// if the object does NOT have a longer lifetime (meaning the
// implementation doesn't need to see this), or if the implementation
// allows it to happen.
allow = (impl->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK
|| impl->mBase->onIncStrongAttempted(FIRST_INC_STRONG, id);
} else {
// Attempting to revive the object... this is allowed
// if the object DOES have a longer lifetime (so we can safely
// call the object with only a weak ref) and the implementation
// allows it to happen.
allow = (impl->mFlags&OBJECT_LIFETIME_WEAK) == OBJECT_LIFETIME_WEAK
&& impl->mBase->onIncStrongAttempted(FIRST_INC_STRONG, id);
}
if (!allow) {
decWeak(id);
return false;
}
curCount = android_atomic_inc(&impl->mStrong);
// If the strong reference count has already been incremented by
// someone else, the implementor of onIncStrongAttempted() is holding
// an unneeded reference. So call onLastStrongRef() here to remove it.
// (No, this is not pretty.) Note that we MUST NOT do this if we
// are in fact acquiring the first reference.
if (curCount > 0 && curCount < INITIAL_STRONG_VALUE) {
impl->mBase->onLastStrongRef(id);
}
}
impl->addStrongRef(id);
#if PRINT_REFS
ALOGD("attemptIncStrong of %p from %p: cnt=%d\n", this, id, curCount);
#endif
if (curCount == INITIAL_STRONG_VALUE) {
android_atomic_add(-INITIAL_STRONG_VALUE, &impl->mStrong);
impl->mBase->onFirstRef();
}
return true;
}
有幾個重點:
- Reference count 其實是存在 weakref_impl 裡面的,跟被參考物件 T 的生命週期是獨立的。
- RefBase 有宣告virtual function,比較 heavy weight 一點,在一些比較簡單的狀況可以考慮使用
LightRefBase
。 - 目前的實現是 thread-safe 的,所以對於 strong/weak ref count 的操作都用使用 atomic operation 例如
android_atomic_inc
或android_atomic_dec
。 - 從目前實現看來,原本的設計是希望在第一個 strong ref 被加上前,也就是 constructor 裡面不應該做太 heavy weight 的事。
RefBase::onFirstRef()
是當第一次被 strong ref 時會被呼叫,通常是 override 它來做資源配置或是準備工作,算是一種 lazy initialization。RefBase::OnLastStrongRef()
是當最後一個 strong ref 被釋放時會被呼叫,跟OnFirstRef
相反是用來做資源釋放或是收尾工作。- 預設是最後一個 strong ref 時,實際的物件就會被摧毀;但是也可以透過
extendObjectLifeTime(OBJECT_LIFETIME_WEAK)
讓物件只有要 weak ref 還在時便存活著,目前的唯一的應用實例是 Binder proxy (繼承自BpRefBase
)。
有了這些認知,來看 BpRefBase
的實現便會比較容易了解其意義:
BpRefBase::BpRefBase(const sp<IBinder>& o)
: mRemote(o.get()), mRefs(NULL), mState(0)
{
extendObjectLifetime(OBJECT_LIFETIME_WEAK);
if (mRemote) {
mRemote->incStrong(this); // Removed on first IncStrong().
mRefs = mRemote->createWeak(this); // Held for our entire lifetime.
}
}
BpRefBase::~BpRefBase()
{
if (mRemote) {
if (!(mState&kRemoteAcquired)) {
mRemote->decStrong(this);
}
mRefs->decWeak(this);
}
}
void BpRefBase::onFirstRef()
{
android_atomic_or(kRemoteAcquired, &mState);
}
void BpRefBase::onLastStrongRef(const void* id)
{
if (mRemote) {
mRemote->decStrong(this);
}
}
bool BpRefBase::onIncStrongAttempted(uint32_t flags, const void* id)
{
return mRemote ? mRefs->attemptIncStrong(this) : false;
}