Smart pointer in Android

Posted by Angerslave on Fri, 06 Mar 2020 08:22:11 +0100

There are three smart pointers available in Android:
① sp + LightRefBase: simple reference count, only strong reference count.
② Strong reference pointer (sp + RefBase): use strong reference count.
③ Weak reference pointer (wp + RefBase): use weak reference count.

1, Lightweight pointer

The simplest way to count references is to use a counter to record the number of times an object has been referenced. Every time a reference points to an object, the counter increases by 1; every time a reference to an object breaks, the counter decreases by 1. When the counter is reduced to 0, the memory space of the object is automatically released.

First of all, each object needs to be associated with a counter. A simple implementation is to define the counter in the base class, so that all classes that inherit the base class have counters.

See how this base class is defined in android:

// frameworks/base/include/utils/RefBase.h
template <class T>
class LightRefBase {
public:
    inline LightRefBase() : mCount(0) { }
    
    // Counter plus 1
    inline void incStrong(const void* id) const {
        android_atomic_inc(&mCount);
    }
    
   // Counter minus 1 
   inline void decStrong(const void* id) const {
        if (android_atomic_dec(&mCount) == 1) {
            // When the counter is reduced to 0, construct itself
            delete static_cast<const T*>(this);
        }
   }
   
protected:
   inline ~LightRefBase() { }
   
private:
    mutable volatile int32_t mCount; // 32-bit counter
}

This class encapsulates counters and provides operation interfaces (incStrong and decStrong) for counters. Among them, Android atom inc() and Android atom dec() are atomic operations, which return the count value before adding 1 or subtracting 1.

Who is going to operate the counter plus one or minus one? sp class does this.

// frameworks/base/include/utils/StrongPointer.h
template <typename T>
class sp
{
public:
    sp(T* other);
    ~sp();
    sp& operator = (T* other);
    sp& operator = (const sp<T>& other);
   inline  T&      operator* () const  { return *m_ptr; }
   inline  T*      operator-> () const { return m_ptr;  }
   ...
private:
   T* m_ptr;
}

As you can see, the sp class contains the m ﹤ PTR pointer member and overloads the * operator and the - > operator, which is a smart pointer. Because it is only used for strong reference counting, the actual meaning of sp here is Strong Pointer.

Let's look at how sp operates the counter to add 1, subtract 1, and release the referenced object.

template<typename T>
sp<T>::sp(T* other) : m_ptr(other)
{
    // In the sp constructor, add 1 to the reference count of the object pointed to
    if (other) other->incStrong(this);
}
template<typename T>
sp<T>::~sp()
{
    // In the sp destructor, the reference count of the object pointed to is subtracted by 1
    // The logic of destruct object is in decStrong()
    if (m_ptr) m_ptr->decStrong(this);
}

One thing to note is that after sp is created, it can point to another object. As shown in the figure below:

So we need to overload the assignment operator

template<typename T>
sp<T>& sp<T>::operator = (T* other)
{
    // Add 1 to the counter of the newly referenced object
    if (other) other->incStrong(this);
    // Since the original object is no longer referenced, the counter of the original object is decremented by 1
    if (m_ptr) m_ptr->decStrong(this);
    m_ptr = other;
    return *this;
}

2, Introduction of weak reference count

Although the above scheme of reference counting is simple to implement, there is a big problem, which is circular reference. The following picture:

Because A and B refer to each other, their counter values are both 1, which cannot be released, but A and B are not referenced by other objects.

In order to solve the circular reference, a weak reference pointer is provided in android.

The basic idea is:
① In circular reference, select one strong and one weak.
② When the strong reference count is reduced to 0, the object is released regardless of whether the weak reference count is 0.
③ You cannot use a weak reference pointer to directly manipulate functions of an object. (to avoid that after the object is released, the weak reference still holds the reference of the object, and then the operation object is wrong.)
④ In order to operate on an object, a weak reference pointer must be promoted to a strong reference pointer. If it is promoted successfully, the object can be accessed. (strong reference pointers overload * and - > operators, while weak reference pointers do not.)

In this scheme, each object needs two counters, one is a strong reference counter and the other is a weak reference counter. Look at the implementation in android:

// frameworks/base/include/utils/RefBase.h
class RefBase {
public:
    // Strong reference counter plus 1
    void incStrong(const void* id) const;
    // Strong reference counter minus 1
    void decStrong(const void* id) const;
    weakref_type* createWeak(const void* id) const;
    
   class weakref_type {
       // Weak reference counter plus 1
       void incWeak(const void* id);
       // Weak reference counter minus 1
       void decWeak(const void* id);
   }
protected:
    RefBase();
    virtual ~RefBase();
private:
    weakref_impl* const mRefs;
}
// frameworks/base/include/utils/RefBase.cpp
class RefBase::weakref_impl : public RefBase::weakref_type {
public:
    volatile int32_t    mStrong; // Strong reference counter
    volatile int32_t    mWeak; // Weak reference counter
    RefBase* const      mBase; // Referenced objects
    volatile int32_t    mFlags; // Identity of the recycling object

    weakref_impl(RefBase* base)
        // First? Inc? Strong = 0x0001 is used to indicate whether it is the first time
        : mStrong(INITIAL_STRONG_VALUE)
        , mWeak(0)
        , mBase(base)
        , mFlags(0) {}
}

The relationship between these categories is as follows:

The RefBase class and the weakref? Impl class are two-way associations:

  • The RefBase object contains the weakref ABCD impl object, and the counter is in the weakref ABCD impl object.
  • The weakref_impl object contains the RefBase object. In the weak reference count mode, when the weak reference count is reduced to 0, the RefBase object needs to be released.

3, Strong reference pointer

As mentioned earlier in sp class, when sp object is constructed, its incStrong() function pointing to the object will be called, while when sp object is destructed, its decStrong() function pointing to the object will be called. The lightweight pointer uses sp + LightRefBase. The strong reference pointer uses sp + RefBase. In this way, RefBase's incStrong() and decStrong() are called.

// frameworks/base/include/utils/RefBase.cpp
void RefBase::incStrong(const void* id) const
{
    weakref_impl* const refs = mRefs;
    refs->incWeak(id); // Weak reference count plus 1
    
    // Strong reference count plus 1
    const int32_t c = android_atomic_inc(&refs->mStrong);

    if (c != INITIAL_STRONG_VALUE)  { // If not for the first time
        return;
   }

   // Because the initial value of mStrong is initial,
   // Therefore, the original? Strong? Value count value needs to be subtracted to be correct
   android_atomic_add(-INITIAL_STRONG_VALUE, &refs->mStrong);
   refs->mBase->onFirstRef(); // The first increase in count will trigger this call
}
void RefBase::decStrong(const void* id) const
{
    weakref_impl* const refs = mRefs;
    // Strong reference count minus 1
    const int32_t c = android_atomic_dec(&refs->mStrong);

    if (c == 1) { // If the strong reference count is reduced to 0
        refs->mBase->onLastStrongRef(id);
        // The default mode is object? Lifetime? Strong
       if ((refs->mFlags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) {
           // If it is an object "lifetime" mode, the object is destructed
           delete this;
       }
   }
   // Weak reference count minus 1
   refs->decWeak(id);
}

As can be seen from the above code, when the RefBase object is referenced, both the strong reference counter and the weak reference counter are increased by 1; when the sp is destructed, both the strong reference counter and the weak reference counter of the referenced RefBase object are decreased by 1. When the strong reference count is reduced to 0, the RefBase object (object? Lifetime? Strong mode) is destructed.
Using sp + RefBase, the strong reference count is equal to the weak reference count.

The destruction logic of RefBase object is clear, but there is another problem. When was the weakref_impl object created and destructed?

RefBase::RefBase()
    : mRefs(new weakref_impl(this)) // A weakref? Impl is created in RefBase's constructor
{
}
RefBase::~RefBase()
{
    if (mRefs->mStrong == INITIAL_STRONG_VALUE) {
        // If no strong reference has been used to point to it, then directly destruct the weakref_impl
        delete mRefs;
    } else {
        // At present, the strong reference pointer we discussed will not enter the following if statement
        if ((mRefs->mFlags & OBJECT_LIFETIME_MASK) != OBJECT_LIFETIME_STRONG) {
            if (mRefs->mWeak == 0) {
                delete mRefs;
            }
       }
   }
   // for debugging purposes, clear this.
   const_cast<weakref_impl*&>(mRefs) = NULL;
}

We find that when the strong reference count is reduced to 0, the RefBase object is destructed, but its destructor does not see the destructor weakref_impl object. So where is the weakref_impl object deconstructed? The answer is in the RefBase:: weakref_type:: decleak() function.

void RefBase::weakref_type::decWeak(const void* id)
{
    weakref_impl* const impl = static_cast<weakref_impl*>(this);
    // Weak reference count minus 1
    const int32_t c = android_atomic_dec(&impl->mWeak);
    if (c != 1) return;
    
    if ((impl->mFlags&OBJECT_LIFETIME_WEAK) == OBJECT_LIFETIME_STRONG) {
        if (impl->mStrong == INITIAL_STRONG_VALUE) {
           delete impl->mBase;
       } else {
           delete impl; // Deconstruction oneself
       }
   } else {
       ...
   }
}

Here is a summary of the time and location where the weakref ﹐ type object is destructed:

① If the RefBase object has never been referenced by the sp object, the weakref_type object is destructed in the destructor of the RefBase class.
② If the RefBase object has been referenced by sp object, when the weak reference counter of RefBase object is reduced to 0, the weakref  type object destructs itself.

4, Weak reference pointer

The above methods are all the cases with only strong pointer. What if there are only weak or strong pointers?

Let's first look at the definition of weak pointer:

// frameworks/base/include/utils/RefBase.h
template <typename T>
class wp
{
public:
    inline wp() : m_ptr(0) { }
    wp(T* other);
    wp(const wp<T>& other);
    ~wp();
    
   wp& operator = (T* other);
   wp& operator = (const wp<T>& other);
   // Promote to strong pointer
   sp<T> promote() const;
   
private:
   T* m_ptr;
   weakref_type* m_refs;
}

The following figure shows the relationship between wp, RefBase and weakref_type

template<typename T>
wp<T>::wp(T* other) : m_ptr(other)
{
    // Add 1 to the weak reference count of the object referenced by wp
    if (other) m_refs = other->createWeak(this);
}

RefBase::weakref_type* RefBase::createWeak(const void* id) const
{
     // Add 1 to weak reference count
    mRefs->incWeak(id);
    return mRefs; // Return the weakref ﹣ impl object created in the constructor
}

template<typename T>
wp<T>::~wp()
{
    // When decomposing wp, reduce the weak reference count of the referenced object by 1
    if (m_ptr) m_refs->decWeak(this);
}

According to the above code, using wp + RefBase will only increase or decrease the weak reference count of the object.

When is RefBase destructed if there are only weak references? When is the weakref ABCD impl object destructed? Two values of mFlags need to be explained first:

  • Object "lifetime" strong: the object is only controlled by strong references. Once the strong reference count is 0, the object can be recycled.
  • Object "lifetime" weak: the object is controlled by the weak reference. When the strong reference count is 0 and the weak reference count is not 0, the actual object will not be recycled. Only when the strong reference count and the weak reference count are both 0, the actual object will be recycled.
void RefBase::weakref_type::decWeak(const void* id) {
    weakref_impl* const impl = static_cast<weakref_impl*>(this);
    const int32_t c = android_atomic_dec(&impl->mWeak);
    if (c != 1) return;
    
    if ((impl->mFlags&OBJECT_LIFETIME_WEAK) == OBJECT_LIFETIME_STRONG) {
        if (impl->mStrong == INITIAL_STRONG_VALUE) {
             // Indicates that the object has not been referenced by a strong reference, only a weak reference has referenced it
             // In this case, when the weak reference count is reduced to 0, the RefBase object is destructed
            delete impl->mBase;
        } else {
            delete impl;
       }
   } else {
       if ((impl->mFlags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_WEAK) {
           // The object is controlled by a weak reference. When the weak reference count is 0, the RefBase object is destructed
           delete impl->mBase;
       }
   }
}

In the destructor of RefBase object, the weakref uuimpl object is destructed without being referenced by a strong reference pointer.

RefBase::~RefBase()
{
    if (mRefs->mStrong == INITIAL_STRONG_VALUE) {
        // If no strong reference has been used to point to it, then directly destruct the weakref_impl
        delete mRefs;
    } else {
        ...
    }
}

5, Both strong and weak reference pointers are used

For each sp created pointing to RefBase object, the strong reference count and weak reference count of the object will be increased by 1.
Every wp created points to RefBase object, only 1 will be added to the weak reference count of the object.
Therefore, when strong reference pointer and weak reference pointer are used at the same time, the value of weak reference count is higher than that of strong reference count.

The time and location of RefBase object and weakref_impl object are divided into two situations:
1. Object recycling is controlled by strong reference (mFlags is object ﹣ lifetime ﹣ strong)

void RefBase::decStrong(const void* id) const {
    weakref_impl* const refs = mRefs;
    const int32_t c = android_atomic_dec(&refs->mStrong);
    
    if (c == 1) {
        refs->mBase->onLastStrongRef(id);
        if ((refs->mFlags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) {
            // Object recycling is controlled by strong references
            // When the strong reference count is reduced to 0, RefBase objects are destructed regardless of whether the weak reference count is 0 at this time
           delete this;
       }
    }
    refs->decWeak(id); // Weak reference count minus 1
}
void RefBase::weakref_type::decWeak(const void* id) {
    weakref_impl* const impl = static_cast<weakref_impl*>(this);
    const int32_t c = android_atomic_dec(&impl->mWeak);
    // If the strong reference count has been reduced to 0, RefBase is destructed, but the weak reference count is now > 0,
    // At this time, decleak() does not destruct the weakref? Impl object
    // That is to say, the life cycle of a weakref ﹣ impl object can be longer than that of a RefBase object
    if (c != 1) return;
    
    // When the existing wp object is deconstructed, the weak reference count will be reduced to 0 finally
   if ((impl->mFlags&OBJECT_LIFETIME_WEAK) == OBJECT_LIFETIME_STRONG) {
       if (impl->mStrong == INITIAL_STRONG_VALUE) {
           // It's not going to go here. It's only using weak reference pointers
           delete impl->mBase;
       } else {
           // Object recycling is controlled by strong references
           // If the weak reference count is reduced to 0, then the weakref uumpl object is destructed
           delete impl;
       }
   } else {
       ...
   }
}

2. Object recycling is controlled by weak reference (mFlags is object ﹣ lifetime ﹣ weak)

void RefBase::decStrong(const void* id) const {
    weakref_impl* const refs = mRefs;
    // Even if the strong reference count is reduced to 0, RefBase objects will not be destructed
    const int32_t c = android_atomic_dec(&refs->mStrong);
    
    ...
    
    refs->decWeak(id); // Weak reference count minus 1
}
void RefBase::weakref_type::decWeak(const void* id) {
    weakref_impl* const impl = static_cast<weakref_impl*>(this);
    const int32_t c = android_atomic_dec(&impl->mWeak);
    if (c != 1) return;
    
    // When the existing wp object is deconstructed, the weak reference count will be reduced to 0 finally
   if ((impl->mFlags&OBJECT_LIFETIME_WEAK) == OBJECT_LIFETIME_STRONG) {
       ...
   } else {
      // Object recycling is controlled by weak references
      // When the weak reference count is reduced to 0, the RefBase object is de destructed and the destructor of RefBase is called 
      delete impl->mBase;
  }
}
RefBase::~RefBase() {
    if (mRefs->mStrong == INITIAL_STRONG_VALUE) {
        ...
    } else {
        if ((mRefs->mFlags & OBJECT_LIFETIME_MASK) != OBJECT_LIFETIME_STRONG) {
            if (mRefs->mWeak == 0) {
                // Only in RefBase's destructor can we destruct the weakref_impl object
                delete mRefs;
            }
        }
    }
}

Reference resources

  • Android system source code scenario analysis
  • Deep understanding of Android kernel design idea version 2

Topics: Programming Android