原文地址:http://blog.csdn.net/xuqiqiang1993/article/details/68923160
在Android系統中,Native層的代碼基本都是C++寫的,C++跟Java不一樣,C++沒有垃圾回收機制,C++代碼中難於管理new出來對象的釋放,稍有不慎就造成內存泄漏。針對此問題,Android中提出了一套類似Java垃圾回收機制的智能指針,採用強指針sp(Strong Pointer)和弱指針wp(Weak Pointer)對目標對象進行應用,實現對象的自動回收。下面我們將從C++的基礎知識入手,對Android的智能指針展開逐步的分析。
1 相關基礎知識
Android的智能指針,巧妙的運用C++的基礎特性,實現對象的自動釋放,我們就先來看看都用了C++的什麼特性。
1.1 作用域
標記變量的有效範圍。從作用域上來看,可以將對象分爲全局對象、局部對象、靜態全局對象和靜態局部對象。
一般來說,局部變量的有效作用域從它的定義點開始,到和定義變量之前最鄰近的開括號配對的第一個閉括號,也就是說,作用域由變量所在的最近一對{}括號確定。
[演示代碼1]
1.2 對象內存空間的分配、釋放
從內存分配空間來看,可將對象分爲棧對象和堆對象。棧對象在作用域結束後會自動釋放,而堆對象需要手動顯示的釋放。
[演示代碼2]
圖1-1是內存空間的分配示意圖。
圖 1‑1 內存空間的分配、釋放
1.3 原子操作函數
定義在system/core/libcutils/Atomic.c,依賴於具體的芯片平臺。原子操作函數特點:線程安全,返回舊值。
int32_t android_atomic_add(int32_t increment, volatile int32_t *ptr) |
加函數,返回舊值,*ptr = *ptr + increment |
int32_t android_atomic_inc(volatile int32_t *addr) |
自增操作,返回舊值,*ptr = *ptr + 1 |
int32_t android_atomic_dec(volatile int32_t *addr) |
自減操作, 返回舊值,*ptr = *ptr - 1 |
int32_t android_atomic_and(int32_t value, volatile int32_t *ptr) |
位與操作,返回舊值,*ptr = *ptr & value |
int32_t android_atomic_or(int32_t value, volatile int32_t *ptr) |
位或操作,返回舊值,*ptr = *ptr | value |
int android_atomic_cmpxchg(int32_t old_value, int32_t new_value, volatile int32_t *ptr) |
如果*addr == oldvalue,就會執行*addr = new_value的操作,然後返回0,否則返回1 |
表 1‑1 Android原子操作函數一覽表
1.4 引用計數的原理
棧對象在生命週期,即作用域結束後自動釋放,所以我們這裏討論的是堆對象的引用,也就是指針對象。
圖1-2是指針引用時,利用引用數管理實際對象釋放的原理圖。
引用計數的原理很簡單,當引用某個對象時,使其引用數+1;引用結束時,使其引用數-1;當引用數爲0時,delete掉實際對象。
根據前面的原理,引出兩個問題,帶着這兩個問題,我們來看看Android是怎麼實現的。
Ø 怎麼管理引用數?
Ø 怎麼判斷引用開始和結束,怎麼增減引用數?
2 Android智能指針原理
Android設計了基類RefBase,用以管理引用數,所有類必須從RefBase派生,RefBase是所有對象的始祖。
設計模板類sp、wp,用以引用實際對象,sp強引用和wp弱引用。sp、wp聲明爲棧對象,作用域結束時,自動釋放,自動調用析構函數。因此,可以在sp、wp的構造函數中,增引用數;在析構函數中,減少引用計數。
專門設計weakref_impl類,該類是RefBase的內部類,用來做真正引用數管理,創建實際對象時,同時創建一個mRefs對象。不管是強引用和弱應用,都由mRefs來管理。
圖2-1 展示Android智能指針的關係類圖。
圖 2‑1 Android智能指針關係圖
看了智能指針的實現原理,我們來看看具體的實現是什麼樣的。
3 智能指針的實現
根據前面的原理,Android設計了強引用sp和弱引用wp,故實際對象的釋放,可分爲強引用控制和弱引用控制。所謂強引用控制,指的是強引用數mStrong爲0時,釋放實際對象;弱引用控制,則指的是弱引用數mWeak爲0時,才釋放實際對象。
下面將結合一些實例,分析具體的實現。我們先來看一段代碼實例。
[代碼實例1]
3.1 RefBase構造和mRefs
在實例代碼中,我們先定義了一個類Sheep,從RefBase派生,創建了一個實際對象,pSheep 指向實際對象。
在構造Sheep的實際對象時,將調RefBase的構造函數。RefBase的構造函數如下,在構造函數中創建mRefs。
weakref_impl從weakref_type派生,mRefs纔是真正的“管家”。
[system/core/libutils/RefBase.cpp]
請注意這裏的mFlags,默認值爲0,可通過修改這個標誌來設置是強引用控制,還是弱引用控制,代碼如下:
[system/core/include/utils/RefBase.h]
mFlags默認爲0,即OBJECT_LIFETIME_STRONG,強引用控制。設置爲OBJECT_LIFETIME_WEAK時,爲弱引用控制。可以通過extendObjectLifetime函數修改,代碼如下:
[system/core/libutils/RefBase.cpp]
3.2 sp構造
接下來,我們創建了一個sp對象spSheep,這是一個棧對象,在其作用域結束後將自動釋放,調用sp的析構函數。
[system/core/include/utils/StrongPointer.h]
other指向真正的Sheep對象,在sp的構造函數中,將other賦值給了sp的m_ptr,m-ptr就指向了真正的Sheep對象。
因而,other->incStrong(this),實際就是Sheep的父類RefBase的incStrong函數,代碼如下:
[system/core/libutils/RefBase.cpp]
在incStrong函數中調用refs 的incWeak函數,incWeak的代碼如下:
[system/core/libutils/RefBase.cpp]
OK,sp構造完成,增加一次強引用。sp構造完成後,mRefs的強引用數變爲1,弱引用數也變爲1;第一次強引用時,回調onFirstRef()。
3.3 wp構造
接下來,我們創建了一個wp對象wpSheep,這是一個棧對象,在其作用域結束後將自動釋放,調用wp的析構函數。
[system/core/include/utils/RefBase.h]
other指向真正的Sheep對象,在wp的構造函數中,將other賦值給了wp的m_ptr,m-ptr就指向了真正的Sheep對象。
因而,other-> createWeak (this),實際就是Sheep的父類RefBase的createWeak函數,代碼如下:
[system/core/libutils/RefBase.cpp]
reateWeak時,調用incWeak,最終的影響是弱引用數+1。現在,我們的實例中,強引用數爲1,弱引用數爲2。
返回值爲mRefs,也就是m_refs和mRefs指向同一個weakref_impl對象,而mRefs的mBase指向真正的對象Sheep。因此此處的spSheep和wpSheep都是管理同一個真正的對象。
3.4 wp析構
繼續看我們的實例代碼,現在wpSheep的作用域結束,將調wp的析構函數,wp析構函數的代碼如下:
[system/core/include/utils/RefBase.h]
在wp的析構函數中調用m_refs的decWeak函數。m_refs和mRef指向同一個weakref_impl對象,decWeak代碼如下:
[system/core/libutils/RefBase.cpp]
wp析構,情況比較複雜,總的說來做了以下幾件事:
Ø 弱引用數減1。
Ø 最後一次弱引用時,強引用控制,釋放mRefs,若沒有強引用,釋放實際對象
Ø 最後一次弱引用時,弱引用控制,釋放實際對象
就我們的實例來看,此時,強引用數爲1,弱引用數爲1,並沒有任何釋放。
3.5 sp析構
在我們的實例代碼中,wp析構完後,sp的作用域也就結束了。此時,會調用sp的析構函數,代碼如下:
[system/core/include/utils/StrongPointer.h]
在析構函數中調用m_ptr的decStrong函數,m_ptr指向實際對象。此處爲Sheep的父類RefBase的decStrong函數,代碼如下:
[system/core/libutils/RefBase.cpp]
refs的decWeak函數,前面wp析構的時候分析過,這裏不再重複。sp析構完成,主要完成以下工作:
Ø 強引用數減1,弱引用數據減1。
Ø 最後一次強引用時,若是強引用控制,釋放實際對象,釋放mRefs,調用onLastStrongRef函數。
在我們的代碼中,此時強引用數爲0,弱引用數爲0,實際對象的析構函數將被調用,mRefs將被釋放。下面我看看實際對象的析構函數。
3.6 RefBase析構
實際對象的析構,先析構RefBase,RefBase的析構函數如下:
[system/core/libutils/RefBase.cpp]
OK,RefBase析構分析完了,在RefBase的析構函數中主要的工作就是釋放mRefs指向的weakref_impl的對象。
到此,我們的實例代碼分析完成,我們首先構造一個Sheep對象,pSheep指向實際對象。再分別構造一個強引用sp和一個弱引用wp,用以引用實際對象,實際對象的釋放就由sp和wp控制,我們並沒有顯示的釋放構造的pSheep指向的實際對象。
我們來看看實例代碼1中,對象的構造和析構Log:
[示例代碼1的Log]
3.7 實際對象的狀態
通過前面的分析,我們可以繪製出實際對象的狀態圖,如下如所示:
圖 3‑1 實際對象的狀態圖
4 智能指針的使用
前面我們通過示例代碼1,知道了智能指針是怎麼管理實際對象的,怎麼控制實際對象的釋放的。但是我們只是分析了其中的構造函數和析構函數,下面我們將對智能指針做全面的瞭解。
4.1 RefBase的特性
我們先看看RefBase的類圖,如圖4-1所示。
圖 4‑1 RefBase類圖
Ø 所有類須從RefBase派生,只有一個無參構造函數,RefBase析構函數需申明爲virtual。
Ø 在構造函數中創建mRefs對象,爲weakref_impl類型。
Ø 可以在派生類中通過函數extendObjectLifetime指定是強引用控制,還是弱引用控制,默認爲強引用控制。
Ø 在析構函數中,判斷是否釋放mRefs。
Ø 私有的構造函數和賦值運算重載,不允許子類使用。
Ø 獲取實際對象的強引用數getStrongCount