引言:由于未來(lái)需要深入android底層進(jìn)行系統(tǒng)級(jí)別的開發(fā)鸥昏,所以最近在看老羅的《Android系統(tǒng)源代碼情景分析》,里面提到了一個(gè)很重要的概念叫智能指針母截,這個(gè)東西對(duì)于理解android應(yīng)用系統(tǒng)框架很有幫助腕唧,在FrameWork層大量的C++代碼中會(huì)經(jīng)常使用到這個(gè)概念翎朱。
那么什么是智能指針呢?(這里提下忌堂,這個(gè)指針不是指*盒至,指的是一個(gè)對(duì)象,但是它引用了一個(gè)實(shí)際使用的對(duì)象)
書的原話是:智能指針式一種能夠自動(dòng)維護(hù)對(duì)象引用計(jì)數(shù)的技術(shù)士修。
具體一點(diǎn)解釋枷遂,大家都知道C++需要使用大量的指針,指針最容易出現(xiàn)錯(cuò)誤的地方就是忘記釋放其指向的對(duì)象所占的內(nèi)存導(dǎo)致內(nèi)存泄漏李命。那么為了避免這種情況登淘,Android系統(tǒng)提供了C++智能指針通過(guò)引用計(jì)數(shù)技術(shù)來(lái)維護(hù)對(duì)象的生命周期。(下面稍微解釋一下引用計(jì)數(shù)技術(shù)封字,我相信很多人都了解過(guò))
引用計(jì)數(shù)法:這種算法的思路是如果某一個(gè)對(duì)象被別的對(duì)象黔州,那么就把他們引用計(jì)數(shù)器加上1耍鬓,這樣當(dāng)進(jìn)行垃圾回收時(shí)如果判斷該引用的數(shù)量為0,此時(shí)就代表沒有進(jìn)行任何對(duì)象對(duì)其進(jìn)行引用流妻,此時(shí)就進(jìn)行回收牲蜀。
問(wèn)題:但是這種技術(shù)會(huì)出現(xiàn)一個(gè)問(wèn)題就是兩個(gè)對(duì)象相互引用的時(shí)候會(huì)出現(xiàn)“死鎖”的情況。比如A引用B绅这,B引用A涣达。當(dāng)對(duì)象A不再使用需要釋放它所占的內(nèi)存時(shí),由于A仍然被B引用所以無(wú)法釋放证薇,只能等待B釋放這個(gè)引用度苔,同樣對(duì)B來(lái)說(shuō)一樣的問(wèn)題。所以會(huì)造成相互等待浑度,這個(gè)和Java中的鎖同步問(wèn)題一個(gè)道理寇窑,A對(duì)象wait()了自己等待B對(duì)象喚醒,B對(duì)象也wait()了自己等待A對(duì)象喚醒自己箩张。就如兩個(gè)睡美人都在等待對(duì)方叫醒自己一樣造成死鎖狀態(tài)甩骏。
在java中為了解決這個(gè)問(wèn)題引入了引用鏈方法,這里僅僅提一下這個(gè)概念--“JVM采用GC Roots可達(dá)性來(lái)決定是否會(huì)被GC回收"先慷,可以參考《深入JVM虛擬機(jī)》一書饮笛。
那么Android的智能指針是怎么解決這個(gè)問(wèn)題的呢?
這里先介紹一種較為復(fù)雜的引用計(jì)數(shù)方法论熙,這種方法將對(duì)象的引用計(jì)數(shù)分為強(qiáng)引用和若引用計(jì)數(shù)兩種福青,但是對(duì)象的生命周期只受強(qiáng)引用計(jì)數(shù)控制。這種解決方案以”父子“關(guān)系將對(duì)象很有意思的關(guān)聯(lián)了起來(lái)赴肚,即"父”對(duì)象通過(guò)強(qiáng)引用計(jì)數(shù)引用”子"對(duì)象素跺,“子”對(duì)象通過(guò)弱引用計(jì)數(shù)引用“父”對(duì)象,但是很明顯按照傳統(tǒng)美德只有父親管著兒子誉券,所以當(dāng)“子”對(duì)象想要釋放自己時(shí)由于它還收到“父”對(duì)象的管制無(wú)法釋放自己指厌;但是“父”對(duì)象想要釋放自己時(shí)可以輕易釋放自己,此時(shí)由于“父”不存在了踊跟,“子”對(duì)象不受強(qiáng)引用計(jì)數(shù)的管制了就可以釋放自己了踩验。
好的介紹完了這些背景可以公布答案了,答案就是:Android提供了三種類型的指針商玫,分別為輕量級(jí)指針(Light Pointer)箕憾、強(qiáng)指針(Strong Pointer)、弱指針(Weak Pointer)拳昌。
輕量級(jí)指針
這里不多提輕量級(jí)指針袭异,因?yàn)檫@種指針式通過(guò)簡(jiǎn)單引用計(jì)數(shù)技術(shù)來(lái)維護(hù)對(duì)象生命周期的。(個(gè)人覺得還是會(huì)有相互引用的風(fēng)險(xiǎn)產(chǎn)生炬藤,所以并沒有懂使用這個(gè)指針的意義在哪兒御铃?也許是相比強(qiáng)指針和弱指針其效率更高吧)碴里。關(guān)于它只需知道3點(diǎn):
第一點(diǎn)使用它需要繼承LightRefBase(模板類)
public LightClass: public LightRefBase<LightClass>
第二點(diǎn)LightRefBase類只有一個(gè)成員變量mCount用來(lái)描述一個(gè)對(duì)象的引用計(jì)數(shù)值。
第三點(diǎn)需要知道輕量級(jí)指針的實(shí)現(xiàn)類和強(qiáng)指針的實(shí)現(xiàn)類是同一個(gè)類sp上真。
強(qiáng)指針
與輕量級(jí)指針不同咬腋,強(qiáng)指針不是直接使用一個(gè)整數(shù)來(lái)維護(hù)對(duì)象的引用計(jì)數(shù)的,而是使用一個(gè)weakref_impl對(duì)象睡互,這個(gè)對(duì)象是繼承RefBase類(一個(gè)類要使用強(qiáng)指針和弱指針必須繼承RefBase)中的內(nèi)部類weakref_type類根竿,其中weakref_type僅僅只定義了引用計(jì)數(shù)維護(hù)接口,具體實(shí)現(xiàn)是weakref_type就珠。(具體關(guān)系如下圖寇壳,圖是手碼的,一個(gè)是繼承關(guān)系嗓违,一個(gè)是引用關(guān)系)
這里說(shuō)一下成員變量mFlags的作用九巡,mFlags這個(gè)標(biāo)志位有三種取值:
0:表示對(duì)象的生命周期只受強(qiáng)引用計(jì)數(shù)影響;默認(rèn)就是這個(gè)蹂季。
1(OBJECT_LIFETIME_WEAK):表示對(duì)象的生命周期同時(shí)受強(qiáng)引用計(jì)數(shù)和弱引用計(jì)數(shù)影響
OBJECT_LIFETIME_FOREVER:表示對(duì)象的生命周期完全不受強(qiáng)引用計(jì)數(shù)和弱引用計(jì)數(shù)的影響。//這個(gè)地方我想說(shuō)一下疏日,我實(shí)踐的時(shí)候發(fā)現(xiàn)并沒有這個(gè)標(biāo)志位偿洁,可能是后來(lái)的android版本在基類里面取消了這個(gè)標(biāo)識(shí)位,具體我還沒有仔細(xì)查最新的源碼沟优,會(huì)繼續(xù)補(bǔ)充涕滋。
*原書里面解釋強(qiáng)指針或弱指針均涉及源代碼分析,這里我嘗試用自己的語(yǔ)言總結(jié)一下重要的部分
RefBase的incStrong函數(shù)干了哪些事情呢挠阁?(主要有三步宾肺,第三步是第一次強(qiáng)引用的一些邏輯處理,這里不分析)
1.增加弱引用計(jì)數(shù)(這個(gè)看起來(lái)好像與函數(shù)的名字有點(diǎn)相互違背侵俗,這個(gè)后面會(huì)解釋)
具體過(guò)程:通過(guò)mRefs的incWeak方法來(lái)增加對(duì)象的弱引用計(jì)數(shù)(可以配合類圖理解)锨用,mRefs是Weakref_impI類型的,Weakref_impl又繼承了inWeak方法隘谣,實(shí)際上調(diào)用的是weakref_type的方法
2.增加強(qiáng)引用計(jì)數(shù)增拥。
通過(guò)android_atomic_inc函數(shù)增加強(qiáng)引用計(jì)數(shù)值(返回增加前的值,這里注意是之前)
可以看出強(qiáng)指針類增加對(duì)象的強(qiáng)引用計(jì)數(shù)的同時(shí)也會(huì)增加弱引用計(jì)數(shù)寻歧,即一個(gè)對(duì)象的弱引用計(jì)數(shù)一定是大于或者等于它的強(qiáng)引用計(jì)數(shù)的掌栅。(sp的構(gòu)造函數(shù)就干了這么些事情)
那么sp的析構(gòu)函數(shù)干了什么事情呢?(對(duì)應(yīng)函數(shù)decStrong)
1.減少對(duì)象的強(qiáng)引用計(jì)數(shù)码泛,當(dāng)強(qiáng)引用計(jì)數(shù)為0時(shí)(實(shí)際上不是0猾封,這里用0好解釋),即不再被強(qiáng)指針引用時(shí)噪珊。此時(shí)需要判斷標(biāo)識(shí)位mFlags(上面提過(guò))是否為1晌缘,如果不為1逾苫,就會(huì)釋放對(duì)象所占的內(nèi)存,同時(shí)也會(huì)導(dǎo)致RefBase類的析構(gòu)函數(shù)調(diào)用枚钓。
2.減少對(duì)象的弱引用計(jì)數(shù)铅搓,一旦發(fā)現(xiàn)弱引用計(jì)數(shù)為0時(shí),把引用計(jì)數(shù)對(duì)象mRefs(weakref_impl類型)也釋放掉(前面提過(guò)搀捷,建議回頭看看方便理解)星掰。前面說(shuō)過(guò),一個(gè)對(duì)象的弱引用計(jì)數(shù)一定大于或者等于強(qiáng)引用計(jì)數(shù)的嫩舟,當(dāng)強(qiáng)引用計(jì)數(shù)為0時(shí)氢烘,會(huì)釋放掉RefBase對(duì)象,但當(dāng)此時(shí)弱引用計(jì)數(shù)大于0時(shí)家厌,不能將mRefs也釋放掉播玖,因?yàn)檫€有其他的弱指針通過(guò)weakref_impl對(duì)象來(lái)引用實(shí)際的對(duì)象。
*如果還是不懂饭于,建議配合原書中的源代碼看蜀踏。
弱指針
弱指針同樣從RefBase類繼承下來(lái),因?yàn)镽efBase提供了弱引用計(jì)數(shù)器掰吕。弱指針類的實(shí)現(xiàn)類為wp果覆。弱指針使用的是類型為weakref_type*的成員變量m_refs維護(hù)對(duì)象的弱引用計(jì)數(shù)。
弱指針和強(qiáng)指針有一個(gè)很大的區(qū)別殖熟,就是弱指針不可以直接操作它所引用的對(duì)象局待,因?yàn)樗玫膶?duì)象可能是不受弱引用計(jì)數(shù)控制的,即它所引用的對(duì)象可能是一個(gè)無(wú)效的對(duì)象菱属。因此钳榨,如果需要操作一個(gè)弱指針?biāo)玫膶?duì)象,那么就需要將這個(gè)弱指針升級(jí)為強(qiáng)指針纽门,這是通過(guò)它的成員函數(shù)promote來(lái)實(shí)現(xiàn)的薛耻。如果升級(jí)成功,就說(shuō)明該弱指針?biāo)玫膶?duì)象還沒有被銷毀膜毁,可以正常使用昭卓。
下面著重介紹wp的promote函數(shù)。先來(lái)看兩段源代碼代碼(純手碼截圖瘟滨,下次用markdown編輯器寫候醒,這么寫太sb了)
參數(shù)p指向?qū)ο蟮牡刂罚鴧?shù)refs指向該對(duì)象內(nèi)部的一個(gè)弱引用計(jì)數(shù)器對(duì)象杂瘸。只有在對(duì)象地址不為null的情況下倒淫,才會(huì)調(diào)用它內(nèi)部的弱引用計(jì)數(shù)器對(duì)象的成員函數(shù)attempIncStrong來(lái)試圖增加該對(duì)象的強(qiáng)引用計(jì)數(shù)。如果能夠成功增加對(duì)象的強(qiáng)引用計(jì)數(shù)败玉,那么就可以成功地把一個(gè)弱指針升級(jí)為一個(gè)強(qiáng)指針敌土。
attempIncStrong看著是不是很熟悉镜硕,可以從之前的圖中找到。
這個(gè)成員函數(shù)試圖增加目標(biāo)對(duì)象的強(qiáng)引用計(jì)數(shù)返干,但是有可能會(huì)增加失敗兴枯,因?yàn)槟繕?biāo)對(duì)象可能已經(jīng)被釋放了,或者該目標(biāo)對(duì)象不允許使用強(qiáng)指針引用它矩欠。
(attempIncStrong中有個(gè)有意思的邏輯)
之前提過(guò)增加對(duì)象強(qiáng)引用計(jì)數(shù)時(shí)财剖,同時(shí)也會(huì)增加該對(duì)象的弱引用計(jì)數(shù)。
分割線(邏輯來(lái)了)
1.先調(diào)用成員函數(shù)incWeak來(lái)增加對(duì)象的弱引用計(jì)數(shù) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 2.如果后面增加對(duì)象的強(qiáng)引用計(jì)數(shù)失敗癌淮,則調(diào)用decWeak來(lái)減少對(duì)象的弱引用計(jì)數(shù)躺坟。
一個(gè)弱指針?biāo)玫膶?duì)象可能處于兩種狀態(tài)。(下面均摘自原文)
第一種:該對(duì)象同時(shí)也被其他強(qiáng)指針對(duì)象所引用乳蓄,此時(shí)可以安全地將這個(gè)弱指針升級(jí)為強(qiáng)指針咪橙。
第二種:該對(duì)象沒有被任何強(qiáng)指針引用。這里情況就比較復(fù)雜了虚倒。需要根據(jù)對(duì)象生命周期來(lái)判斷
1.如果對(duì)象生命周期只受強(qiáng)引用計(jì)數(shù)影響美侦,那么就可以成功將該弱指針升級(jí)為強(qiáng)指針。因?yàn)樗軓?qiáng)引用計(jì)數(shù)影響裹刮,而此時(shí)該對(duì)象又沒有被強(qiáng)指針引用過(guò)音榜,那么它必然不會(huì)被釋放。
2.如果只受弱引用計(jì)數(shù)影響捧弃,首先我們可以確定對(duì)象現(xiàn)在一定是存在的,因?yàn)楝F(xiàn)在有一個(gè)弱指針引用它擦囊。但是违霞,這種情況需要進(jìn)一步調(diào)用對(duì)象的成員函數(shù)onIncStrongAttempted來(lái)確認(rèn)對(duì)象是否允許強(qiáng)指針引用它。如果返回為true說(shuō)明允許則成功將該弱指針升級(jí)為強(qiáng)指針瞬场。如果返回為false买鸽,則說(shuō)明升級(jí)失敗。
大概就總結(jié)這么多贯被。下面是打賞時(shí)間眼五,碼字不易,給個(gè)贊也行彤灶。