引言:由于未來需要深入android底層進行系統級別的開發,所以最近在看老羅的《Android系統源代碼情景分析》,里面提到了一個很重要的概念叫智能指針,這個東西對于理解android應用系統框架很有幫助,在FrameWork層大量的C++代碼中會經常使用到這個概念。
那么什么是智能指針呢?(這里提下,這個指針不是指*,指的是一個對象,但是它引用了一個實際使用的對象)
書的原話是:智能指針式一種能夠自動維護對象引用計數的技術。
具體一點解釋,大家都知道C++需要使用大量的指針,指針最容易出現錯誤的地方就是忘記釋放其指向的對象所占的內存導致內存泄漏。那么為了避免這種情況,Android系統提供了C++智能指針通過引用計數技術來維護對象的生命周期。(下面稍微解釋一下引用計數技術,我相信很多人都了解過)
引用計數法:這種算法的思路是如果某一個對象被別的對象,那么就把他們引用計數器加上1,這樣當進行垃圾回收時如果判斷該引用的數量為0,此時就代表沒有進行任何對象對其進行引用,此時就進行回收。
問題:但是這種技術會出現一個問題就是兩個對象相互引用的時候會出現“死鎖”的情況。比如A引用B,B引用A。當對象A不再使用需要釋放它所占的內存時,由于A仍然被B引用所以無法釋放,只能等待B釋放這個引用,同樣對B來說一樣的問題。所以會造成相互等待,這個和Java中的鎖同步問題一個道理,A對象wait()了自己等待B對象喚醒,B對象也wait()了自己等待A對象喚醒自己。就如兩個睡美人都在等待對方叫醒自己一樣造成死鎖狀態。
在java中為了解決這個問題引入了引用鏈方法,這里僅僅提一下這個概念--“JVM采用GC Roots可達性來決定是否會被GC回收",可以參考《深入JVM虛擬機》一書。
那么Android的智能指針是怎么解決這個問題的呢?
這里先介紹一種較為復雜的引用計數方法,這種方法將對象的引用計數分為強引用和若引用計數兩種,但是對象的生命周期只受強引用計數控制。這種解決方案以”父子“關系將對象很有意思的關聯了起來,即"父”對象通過強引用計數引用”子"對象,“子”對象通過弱引用計數引用“父”對象,但是很明顯按照傳統美德只有父親管著兒子,所以當“子”對象想要釋放自己時由于它還收到“父”對象的管制無法釋放自己;但是“父”對象想要釋放自己時可以輕易釋放自己,此時由于“父”不存在了,“子”對象不受強引用計數的管制了就可以釋放自己了。
好的介紹完了這些背景可以公布答案了,答案就是:Android提供了三種類型的指針,分別為輕量級指針(Light Pointer)、強指針(Strong Pointer)、弱指針(Weak Pointer)。
輕量級指針
這里不多提輕量級指針,因為這種指針式通過簡單引用計數技術來維護對象生命周期的。(個人覺得還是會有相互引用的風險產生,所以并沒有懂使用這個指針的意義在哪兒?也許是相比強指針和弱指針其效率更高吧)。關于它只需知道3點:
第一點使用它需要繼承LightRefBase(模板類)
public LightClass: public LightRefBase<LightClass>
第二點LightRefBase類只有一個成員變量mCount用來描述一個對象的引用計數值。
第三點需要知道輕量級指針的實現類和強指針的實現類是同一個類sp。
強指針
與輕量級指針不同,強指針不是直接使用一個整數來維護對象的引用計數的,而是使用一個weakref_impl對象,這個對象是繼承RefBase類(一個類要使用強指針和弱指針必須繼承RefBase)中的內部類weakref_type類,其中weakref_type僅僅只定義了引用計數維護接口,具體實現是weakref_type。(具體關系如下圖,圖是手碼的,一個是繼承關系,一個是引用關系)
這里說一下成員變量mFlags的作用,mFlags這個標志位有三種取值:
0:表示對象的生命周期只受強引用計數影響;默認就是這個。
1(OBJECT_LIFETIME_WEAK):表示對象的生命周期同時受強引用計數和弱引用計數影響
OBJECT_LIFETIME_FOREVER:表示對象的生命周期完全不受強引用計數和弱引用計數的影響。//這個地方我想說一下,我實踐的時候發現并沒有這個標志位,可能是后來的android版本在基類里面取消了這個標識位,具體我還沒有仔細查最新的源碼,會繼續補充。
*原書里面解釋強指針或弱指針均涉及源代碼分析,這里我嘗試用自己的語言總結一下重要的部分
RefBase的incStrong函數干了哪些事情呢?(主要有三步,第三步是第一次強引用的一些邏輯處理,這里不分析)
1.增加弱引用計數(這個看起來好像與函數的名字有點相互違背,這個后面會解釋)
具體過程:通過mRefs的incWeak方法來增加對象的弱引用計數(可以配合類圖理解),mRefs是Weakref_impI類型的,Weakref_impl又繼承了inWeak方法,實際上調用的是weakref_type的方法
2.增加強引用計數。
通過android_atomic_inc函數增加強引用計數值(返回增加前的值,這里注意是之前)
可以看出強指針類增加對象的強引用計數的同時也會增加弱引用計數,即一個對象的弱引用計數一定是大于或者等于它的強引用計數的。(sp的構造函數就干了這么些事情)
那么sp的析構函數干了什么事情呢?(對應函數decStrong)
1.減少對象的強引用計數,當強引用計數為0時(實際上不是0,這里用0好解釋),即不再被強指針引用時。此時需要判斷標識位mFlags(上面提過)是否為1,如果不為1,就會釋放對象所占的內存,同時也會導致RefBase類的析構函數調用。
2.減少對象的弱引用計數,一旦發現弱引用計數為0時,把引用計數對象mRefs(weakref_impl類型)也釋放掉(前面提過,建議回頭看看方便理解)。前面說過,一個對象的弱引用計數一定大于或者等于強引用計數的,當強引用計數為0時,會釋放掉RefBase對象,但當此時弱引用計數大于0時,不能將mRefs也釋放掉,因為還有其他的弱指針通過weakref_impl對象來引用實際的對象。
*如果還是不懂,建議配合原書中的源代碼看。
弱指針
弱指針同樣從RefBase類繼承下來,因為RefBase提供了弱引用計數器。弱指針類的實現類為wp。弱指針使用的是類型為weakref_type*的成員變量m_refs維護對象的弱引用計數。
弱指針和強指針有一個很大的區別,就是弱指針不可以直接操作它所引用的對象,因為它所引用的對象可能是不受弱引用計數控制的,即它所引用的對象可能是一個無效的對象。因此,如果需要操作一個弱指針所引用的對象,那么就需要將這個弱指針升級為強指針,這是通過它的成員函數promote來實現的。如果升級成功,就說明該弱指針所引用的對象還沒有被銷毀,可以正常使用。
下面著重介紹wp的promote函數。先來看兩段源代碼代碼(純手碼截圖,下次用markdown編輯器寫,這么寫太sb了)
參數p指向對象的地址,而參數refs指向該對象內部的一個弱引用計數器對象。只有在對象地址不為null的情況下,才會調用它內部的弱引用計數器對象的成員函數attempIncStrong來試圖增加該對象的強引用計數。如果能夠成功增加對象的強引用計數,那么就可以成功地把一個弱指針升級為一個強指針。
attempIncStrong看著是不是很熟悉,可以從之前的圖中找到。
這個成員函數試圖增加目標對象的強引用計數,但是有可能會增加失敗,因為目標對象可能已經被釋放了,或者該目標對象不允許使用強指針引用它。
(attempIncStrong中有個有意思的邏輯)
之前提過增加對象強引用計數時,同時也會增加該對象的弱引用計數。
分割線(邏輯來了)
1.先調用成員函數incWeak來增加對象的弱引用計數 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 2.如果后面增加對象的強引用計數失敗,則調用decWeak來減少對象的弱引用計數。
一個弱指針所引用的對象可能處于兩種狀態。(下面均摘自原文)
第一種:該對象同時也被其他強指針對象所引用,此時可以安全地將這個弱指針升級為強指針。
第二種:該對象沒有被任何強指針引用。這里情況就比較復雜了。需要根據對象生命周期來判斷
1.如果對象生命周期只受強引用計數影響,那么就可以成功將該弱指針升級為強指針。因為它受強引用計數影響,而此時該對象又沒有被強指針引用過,那么它必然不會被釋放。
2.如果只受弱引用計數影響,首先我們可以確定對象現在一定是存在的,因為現在有一個弱指針引用它。但是,這種情況需要進一步調用對象的成員函數onIncStrongAttempted來確認對象是否允許強指針引用它。如果返回為true說明允許則成功將該弱指針升級為強指針。如果返回為false,則說明升級失敗。
大概就總結這么多。下面是打賞時間,碼字不易,給個贊也行。