Android指針管理:RefBase,SP,WP

Android中通過引用計數來實現智能指針,並且實現有強指針與弱指針。由對象本身來提供引用計數器,但是對象不會去維護引用計數器的值,而是由智能指針來管理。

要達到所有對象都可用引用計數器實現智能指針管理的目標,可以定義一個公共類,提供引用計數的方法,所有對象都去繼承這個公共類,這樣就可以實現所有對象都可以用引用計數來管理的目標,在Android中,這個公共類就是RefBase,同時還有一個簡單版本LightRefBase。

RefBase作爲公共基類提供了引用計數的方法,但是並不去維護引用計數的值,而是由兩個智能指針來進行管理:sp(Strong Pointer)和wp(Weak Pointer),代表強引用計數和弱引用計數。 

一、輕量級引用計數的實現:LightRefBase

LightRefBase的實現很簡單,只是內部保存了一個變量用於保存對象被引用的次數,並提供了兩個函數用於增加或減少引用計數。

複製代碼
template <class T>
class LightRefBase
{
public:
    inline LightRefBase() : mCount(0) { }
    inline void incStrong(const void* id) const {
        android_atomic_inc(&mCount);
    }
    inline void decStrong(const void* id) const {
        if (android_atomic_dec(&mCount) == 1) {
            delete static_cast<const T*>(this);
        }
    }
    //! DEBUGGING ONLY: Get current strong ref count.
    inline int32_t getStrongCount() const {
        return mCount;
    }
    typedef LightRefBase<T> basetype;
protected:
    inline ~LightRefBase() { }
private:
    mutable volatile int32_t mCount;
};
複製代碼

二、sp(Strong Pointer)

LightRefBase僅僅提供了引用計數的方法,具體引用數應該怎麼管理,就要通過智能指針類來管理了,每當有一個智能指針指向對象時,對象的引用計數要加1,當一個智能指針取消指向對象時,對象的引用計數要減1,在C++中,當一個對象生成和銷燬時會自動調用(拷貝)構造函數和析構函數,所以,對對象引用數的管理就可以放到智能指針的(拷貝)構造函數和析構函數中。Android提供了一個智能指針可以配合LightRefBase使用:sp,sp的定義如下:

 
複製代碼
template <typename T>
class sp
{
public:
    inline sp() : m_ptr(0) { }
    sp(T* other);
    sp(const sp<T>& other);
                                          
    template<typename U> sp(U* other);
    template<typename U> sp(const sp<U>& other);
                                        
    ~sp();
                                          
    // Assignment
    sp& operator = (T* other);
    sp& operator = (const sp<T>& other);
                                          
    template<typename U> sp& operator = (const sp<U>& other);
    template<typename U> sp& operator = (U* other);
                                          
    //! Special optimization for use by ProcessState (and nobody else).
    void force_set(T* other);
                                          
    // Reset
    void clear();
                                          
    // Accessors
    inline  T&      operator* () const  { return *m_ptr; }
    inline  T*      operator-> () const { return m_ptr;  }
    inline  T*      get() const         { return m_ptr; }
                                          
    // Operators
    COMPARE(==)
    COMPARE(!=)
    COMPARE(>)
    COMPARE(<)
    COMPARE(<=)
    COMPARE(>=)
private:  
    template<typename Y> friend class sp;
    template<typename Y> friend class wp;
    void set_pointer(T* ptr);
    T* m_ptr;
};
複製代碼

代碼比較多,其中Accessors部分代碼重載了*、->操作符使我們使用sp的時候就像使用真實的對象指針一樣,可以直接操作對象的屬性或方法,COMPARE是宏定義,用於重載關係操作符,由於對引用計數的控制主要是由(拷貝)構造函數和析構函數控制,所以忽略其他相關代碼後,sp可以精簡爲如下形式(賦值操作符也省略掉了,構造函數省略相似的兩個):

複製代碼
template <typename T>
class sp
{
public:
    inline sp() : m_ptr(0) { }
    sp(T* other);
    sp(const sp<T>& other);
                                        
    ~sp();
                                        
private:  
    template<typename Y> friend class sp;
    template<typename Y> friend class wp;
    void set_pointer(T* ptr);
    T* m_ptr;
};
複製代碼

默認構造函數使智能指針不指向任何對象,sp(T* other)與sp(const sp<T>& other)的實現如下:

複製代碼
template<typename T>
sp<T>::sp(T* other)
: m_ptr(other)
{
    if (other) other->incStrong(this);
}
                                       
template<typename T>
sp<T>::sp(const sp<T>& other)
: m_ptr(other.m_ptr)
{
    if (m_ptr) m_ptr->incStrong(this);
}
複製代碼

內部變量m_ptr指向實際對象,並調用實際對象的incStrong函數,T繼承自LightRefBase,所以此處調用的是LightRefBase的incStrong函數,之後實際對象的引用計數加1。

當智能指針銷燬的時候調用智能指針的析構函數:

template<typename T>
sp<T>::~sp()
{
    if (m_ptr) m_ptr->decStrong(this);
}

調用實際對象即LightRefBase的decStrong函數,其實現如下:

inline void decStrong(const void* id) const {
    if (android_atomic_dec(&mCount) == 1) {
        delete static_cast<const T*>(this);
    }
}

android_atomic_dec返回mCount減1之前的值,如果返回1表示這次減過之後引用計數就是0了,就把對象delete掉。

三、RefBase

RefBase提供了更強大的引用計數的管理。

複製代碼
class RefBase
{
public:
    void    incStrong(const void* id) const;
    void    decStrong(const void* id) const;
    void    forceIncStrong(const void* id) const;
    //! DEBUGGING ONLY: Get current strong ref count.
    int32_t getStrongCount() const;
                                 
    class weakref_type
    {
    public:
        RefBase refBase() const;
        void    incWeak(const void* id);
        void    decWeak(const void* id);
        // acquires a strong reference if there is already one.
        bool    attemptIncStrong(const void* id);
        // acquires a weak reference if there is already one.
        // This is not always safe. see ProcessState.cpp and BpBinder.cpp
        // for proper use.
        bool    attemptIncWeak(const void* id);
        //! DEBUGGING ONLY: Get current weak ref count.
        int32_t getWeakCount() const;
        //! DEBUGGING ONLY: Print references held on object.
        void    printRefs() const;
        //! DEBUGGING ONLY: Enable tracking for this object.
        // enable -- enable/disable tracking
        // retain -- when tracking is enable, if true, then we save a stack trace
        //           for each reference and dereference; when retain == false, we
        //           match up references and dereferences and keep only the
        //           outstanding ones.
        void    trackMe(bool enable, bool retain);
    };
    weakref_type*   createWeak(const void* id) const;
    weakref_type*   getWeakRefs() const;
    // DEBUGGING ONLY: Print references held on object.
    inline  void            printRefs() const { getWeakRefs()->printRefs(); }
    // DEBUGGING ONLY: Enable tracking of object.
    inline  void            trackMe(bool enable, bool retain)
    {
        getWeakRefs()->trackMe(enable, retain);
    }
    typedef RefBase basetype;
                                     
protected:
    RefBase();
    virtual     ~RefBase();
                                     
    //! Flags for extendObjectLifetime()
    enum {
        OBJECT_LIFETIME_STRONG  = 0x0000,
        OBJECT_LIFETIME_WEAK    = 0x0001,
        OBJECT_LIFETIME_MASK    = 0x0003
    };
                                     
    void    extendObjectLifetime(int32_t mode);
    //! Flags for onIncStrongAttempted()
    enum {
        FIRST_INC_STRONG = 0x0001
    };
                                     
    virtual void            onFirstRef();
    virtual void            onLastStrongRef(const void* id);
    virtual bool            onIncStrongAttempted(uint32_t flags, const void* id);
    virtual void            onLastWeakRef(const void* id);
                                     
private:
    friend class weakref_type;
    class weakref_impl;
                                     
    RefBase(const RefBase& o);
    RefBase&        operator=(const RefBase& o);
    weakref_impl* const mRefs;
};
複製代碼

不同於LightRefBase的是,RefBase內部並沒有使用一個變量來維護引用計數,而是通過一個weakref_impl *類型的成員來維護引用計數,並且同時提供了強引用計數和弱引用計數。weakref_impl繼承於RefBase::weakref_type,代碼比較多,不過大都是調試代碼,由宏定義分開,Release是不包含調試代碼的,去除這些代碼後其定義爲:

複製代碼
#define INITIAL_STRONG_VALUE (1<<28)
                                
class RefBase::weakref_impl : public RefBase::weakref_type 
{ 
public: 
    volatile int32_t    mStrong; 
    volatile int32_t    mWeak; 
    RefBase* const      mBase; 
    volatile int32_t    mFlags; 
                                   
    weakref_impl(RefBase* base) 
        : mStrong(INITIAL_STRONG_VALUE) 
        , mWeak(0) 
        , mBase(base) 
        , mFlags(0) 
    { 
    } 
                                   
    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) { } 
};
複製代碼

weakref_impl中的函數都是作爲調試用,Release版的實現都是空的,成員變量分別表示強引用數、弱引用數、指向實際對象的指針與flag,flag可控制實際對象的生命週期,取值爲0或RefBase中定義的枚舉值。

RefBase提供了incStrong與decStrong函數用於控制強引用計數值,其弱引用計數值是由weakref_impl控制,強引用計數與弱引用數都保存在weakref_impl *類型的成員變量mRefs中。

RefBase同LightRefBase一樣爲對象提供了引用計數的方法,對引用計數的管理同樣要由智能指針控制,由於RefBase同時實現了強引用計數與弱引用計數,所以就有兩種類型的智能指針,sp(Strong Pointer)與wp(Weak Pointer)。

sp前面已經說過,其(拷貝)構造函數調用對象即RefBase的incStrong函數。

複製代碼
void RefBase::incStrong(const void* id) const
{
    weakref_impl* const refs = mRefs;
    refs->incWeak(id);
    refs->addStrongRef(id);
    const int32_t c = android_atomic_inc(&refs->mStrong);
    LOG_ASSERT(c > 0, "incStrong() called on %p after last strong ref", refs);
    if (c != INITIAL_STRONG_VALUE)  {
        return;
    }
    android_atomic_add(-INITIAL_STRONG_VALUE, &refs->mStrong);
    refs->mBase->onFirstRef();
}
複製代碼

addStrong的函數體爲空,incStrong函數內部首先調用成員變量mRefs的incWeak函數將弱引用數加1,然後再將強引用數加1,由於android_atomic_inc返回變量的舊值,所以如果其不等於INITIAL_STRONG_VALUE就直接返回,則則是第一次由強智能指針(sp)引用,將其減去INITIAL_STRONG_VALUE後變成1,然後調用對象的onFirstRef。

成員變量mRefs是在對象的構造函數中初始化的:

RefBase::RefBase()
    : mRefs(new weakref_impl(this))
{
}

weakrel_impl的incWeak繼承自父類weakrel_type的incWeak:

複製代碼
void RefBase::weakref_type::incWeak(const void* id)
{
    weakref_impl* const impl = static_cast<weakref_impl*>
    impl->addWeakRef(id);
    const int32_t c = android_atomic_inc(&impl->mWeak);
    LOG_ASSERT(c >= 0, "incWeak called on %p after last weak ref", this);
}
複製代碼

addWeakRef實現同樣爲空,所以只是將弱引用計數加1。所以當對象被sp引用後,強引用計數與弱引用計數會同時加1。

當sp銷燬時其析構函數調用對象即RefBase的decStrong函數:

複製代碼
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) { 
        const_cast<RefBase*>(this)->onLastStrongRef(id); 
        if ((refs->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) { 
            delete this; 
        }
    } 
    refs->removeWeakRef(id); 
    refs->decWeak(id); 
}
複製代碼

decStrong中將強引用數與弱引用數同時減1,如果這是最後一個強引用的話,會調用對象的onLastStrongRef,並且判斷成員變量mRefs的成員變量mFlags來決定是否在對象的強引用數爲0時釋放對象。

mFlags可以爲0或以下兩個枚舉值:

enum {
    OBJECT_LIFETIME_WEAK    = 0x0001,
    OBJECT_LIFETIME_FOREVER    = 0x0003
};

mFlags的值可以通過extendObjectLifetime函數改變:

void RefBase::extendObjectLifetime(int32_t mode)
{
    android_atomic_or(mode, &mRefs->mFlags);
}

OBJECT_LIFETIME_FOREVER包含OBJECT_LIFETIME_WEAK(位運算中其二進制11包含01),所以當

refs->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK

爲true時表示mFlags爲0,實際對象的生命週期受強引用數控制,所以在強引用數爲0時delete this,否則實際對象的生命週期就由弱引用數控制。

再來看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; 
                          
    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,若減1後不爲0直接返回,否則判斷

(impl->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK

若判斷結果爲true:

    實際對象生命週期被強引用數控制,接下來判斷:

mpl->mStrong == INITIAL_STRONG_VALUE
  1. 如果判斷爲true表示對象只被弱引用引用過,現在弱引用數爲0,直接刪除實際對象。

  2. 如果判斷爲false,表示對象曾經被強引用引用過,但現在強引用爲變爲0了(因爲增加或減小強引用數時一定同時增加或減小弱引用數,所以弱引用數爲0時,強引用數一定爲0),弱引用數爲0了,直接釋放mRefs,而實際對象由於受強引用數控制,已經在RefBase::decStrong中被delete了。

若判斷結果爲false:

    判斷mFlgs是否是OBJECT_LIFETIME_FOREVER,如果是,什麼都不作由用戶自己控制對象的生命週期,否則,實際對象的生命週期受弱引用數控制,現在弱引用數爲0,delete實際對象。

四、wp(Weak Pointer)

定義如下:

複製代碼
template <typename T>
class wp
{
public:
    typedef typename RefBase::weakref_type weakref_type;
                     
    inline wp() : m_ptr(0) { }
                 
    wp(T* other);
    wp(const wp<T>& other);
    wp(const sp<T>& other);
    template<typename U> wp(U* other);
    template<typename U> wp(const sp<U>& other);
    template<typename U> wp(const wp<U>& other);
                 
    ~wp();
                     
    // Assignment
                 
    wp& operator = (T* other);
    wp& operator = (const wp<T>& other);
    wp& operator = (const sp<T>& other);
                     
    template<typename U> wp& operator = (U* other);
    template<typename U> wp& operator = (const wp<U>& other);
    template<typename U> wp& operator = (const sp<U>& other);
                     
    void set_object_and_refs(T* other, weakref_type* refs);
                 
    // promotion to sp
                     
    sp<T> promote() const;
                 
    // Reset
                     
    void clear();
                 
    // Accessors
                     
    inline  weakref_type* get_refs() const { return m_refs; }
                     
    inline  T* unsafe_get() const { return m_ptr; }
                 
    // Operators
                         
    COMPARE(==)
    COMPARE(!=)
    COMPARE(>)
    COMPARE(<)
    COMPARE(<=)
    COMPARE(>=)
                 
private:
    template<typename Y> friend class sp;
    template<typename Y> friend class wp;
                 
    T*              m_ptr;
    weakref_type*   m_refs;
};
複製代碼

同sp一樣,m_ptr指向實際對象,但wp還有一個成員變量m_refs。

複製代碼
template<typename T>
wp<T>::wp(T* other)
    : m_ptr(other)
{
    if (other) m_refs = other->createWeak(this);
}
                  
template<typename T>
wp<T>::wp(const wp<T>& other)
    : m_ptr(other.m_ptr), m_refs(other.m_refs)
{
    if (m_ptr) m_refs->incWeak(this);
}
                 
RefBase::weakref_type* RefBase::createWeak(const void* id) const
{
    mRefs->incWeak(id);
    return mRefs;
}
複製代碼

可以看到,wp的m_refs就是RefBase即實際對象的mRefs。

wp析構的時候減少弱引用計數:

template<typename T>
wp<T>::~wp()
{
    if (m_ptr) m_refs->decWeak(this);
}

由於弱指針沒有重載*與->操作符,所以不能直接操作指向的對象,雖然有unsafe_get函數,但像名字所示的,不建議使用,直接使用實際對象指針的話就沒必要用智能指針了。

因爲弱指針不能直接操作對象,所以要想操作對象的話就要將其轉換爲強指針,即wp::promote方法:

複製代碼
template<typename T>
sp<T> wp<T>::promote() const
{
    return sp<T>(m_ptr, m_refs);
}
                
template<typename T>
sp<T>::sp(T* p, weakref_type* refs)
    : m_ptr((p && refs->attemptIncStrong(this)) ? p : 0)
{
}
複製代碼

是否能從弱指針生成一個強指針關鍵是看refs->attemptIncStrong,看其定義:

複製代碼
bool RefBase::weakref_type::attemptIncStrong(const void* id)
{
    incWeak(id);
                    
    weakref_impl* const impl = static_cast<weakref_impl*>(this);
                    
    int32_t curCount = impl->mStrong;
    LOG_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->addWeakRef(id);
    impl->addStrongRef(id);
                
#if PRINT_REFS
    LOGD("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;
}
複製代碼

首先通過incWeak將弱引用數加1(被強指針sp引用會導致強引用數和弱引用數同時加1),然後:

複製代碼
int32_t curCount = impl->mStrong;
while (curCount > 0 && curCount != INITIAL_STRONG_VALUE) {
    if (android_atomic_cmpxchg(curCount, curCount+1, &impl->mStrong) == 0) {
        break;
    }
    curCount = impl->mStrong;
}
複製代碼

如果之前已經有強引用,直接將強引用數加1,android_atomic_cmpxchg表示如果impl->mStrong的值爲curCount,則把impl->mString的值改爲curCount+1,此處用while循環是防止其他線程已經增加了強引用數。

接下來:

if (curCount <= 0 || curCount == INITIAL_STRONG_VALUE)

表示對象目前沒有強引用,這就要判斷對象是否存在了。

如果curCount == INITIAL_STRONG_VALUE,表示對象沒有被sp引用過。接下來判斷:

allow = (impl->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK
    || impl->mBase->onIncStrongAttempted(FIRST_INC_STRONG, id);
 

表示:如果對象的生命週期只受強引用控制,對象一定存在,要有強引用纔可以管理對象的釋放,所以一定會允許生成強引用;如果對象的生命週期受弱引用控制,調用對象的onIncStrongAttempted試圖增加強引用,由於此時在弱引用中,弱引用一定不爲0,對象也一定存在,調用onIncStrongAttempted的意圖是因爲類的實現者可能不希望用強引用引用對象。在RefBase中onIncStrongAttempted默認返回true:

bool RefBase::onIncStrongAttempted(uint32_t flags, const void* id)
{
    return (flags&FIRST_INC_STRONG) ? true : false;
}

如果curCount <= 0(只會等於0),表示對象強引用數經歷了INITIAL_STRONG_VALUE -->大於0 --> 0,接下來就要判斷:

allow = (impl->mFlags&OBJECT_LIFETIME_WEAK) == OBJECT_LIFETIME_WEAK
    && impl->mBase->onIncStrongAttempted(FIRST_INC_STRONG, id);

如果對象的生命週期受強引用數控制,那麼由於曾被sp引用過,現在強引用數又爲0,對象就已經被delete了,所以就不能生成強引用,否則如果對象的生命週期受弱引用數控制,就通過onIncStrongAttempted看類的實現者是否希望當對象的強引用數變爲0時可以再次被強引用引用。

 
if (!allow) {
    decWeak(id);
    return false;
}

如果allow爲false表示不能從弱引用生成強引用,就要調用decWeak將弱引用減1(因爲在promote入口先將弱引用加了1),然後返回false表示生成強引用失敗。

if (curCount == INITIAL_STRONG_VALUE) {
    android_atomic_add(-INITIAL_STRONG_VALUE, &impl->mStrong);
    impl->mBase->onFirstRef();
}

最後,如果curCount == INITIAL_STRONG_VALUE表示第一次被sp引用,調用對象的onFirstRef函數。

五、總結

RefBase內部有一個指針指向實際對象,有一個weakref_impl類型的指針保存對象的強/弱引用計數、對象生命週期控制。

sp只有一個成員變量,用來保存實際對象,但這個實際對象內部已包含了weakref_impl *對象用於保存實際對象的引用計數。sp 管理一個對象指針時,對象的強、弱引用數同時加1,sp銷燬時,對象的強、弱引用數同時減1。

wp中有兩個成員變量,一個保存實際對象,另一個是weakref_impl *對象。wp管理一個對象指針時,對象的弱引用計數加1,wp銷燬時,對象的弱引用計數減1。

weakref_impl中包含一個flag用於決定對象的生命週期是由強引用數控制還是由弱引用數控制:

  • 當flag爲0時,實際對象的生命週期由強引用數控制,weakref_impl *對象由弱引用數控制。

  • 當flag爲OBJECT_LIFETIME_WEAK時,實際對象的生命週期受弱引用數控制。

  • 當flag爲OBJECT_LIFETIME_FOREVER時,實際對象的生命週期由用戶控制。

        可以用extendObjectLifetime改變flag的值。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章