從事 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_incandroid_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;
}