Android的垃圾回收與內(nèi)存泄露

標(biāo)簽(空格分隔): Android


我們知道App都有一個(gè)UI線程,也叫主線程,那是Android框架幫我們創(chuàng)建的,這里注意的是不是每個(gè)activity對(duì)應(yīng)一個(gè)UI主線程舷胜,而是一個(gè)App展融。
為Message都在一個(gè)隊(duì)列中躬贡,擁有它下一個(gè)對(duì)象的引用非常重要旁瘫,這里的寫法其實(shí)跟我們上學(xué)時(shí)學(xué)習(xí)的隊(duì)列類似,每一個(gè)實(shí)體類都擁有下一個(gè)的引用寝杖,這樣就構(gòu)成了一個(gè)隊(duì)列

內(nèi)存泄漏的基本知識(shí)請(qǐng)見博客一
如何高效使用handler避免內(nèi)存泄漏請(qǐng)見博客二

Looper造成內(nèi)存泄漏的總結(jié)為:
1、因?yàn)長(zhǎng)ooper里面的MesageQueue持有外部傳進(jìn)來的runnable引用互纯,runnable又持有handler的引用瑟幕,handler持有activity的引用//這種情況在用handler開啟一個(gè)耗時(shí)的后臺(tái)操作時(shí),這時(shí)開啟的線程也會(huì)持有handler的引用,在關(guān)閉`Activity`的時(shí)候停掉你的后臺(tái)線程只盹。線程停掉了辣往,就相當(dāng)于切斷了`Handler`和外部連接的線,`Activity`自然會(huì)在合適的時(shí)候被回收殖卑。
2站削、在使用handler.handleMessage()這個(gè)方法時(shí),實(shí)際上是一個(gè)回調(diào)的過程
懦鼠,在message的內(nèi)部handleMessage()將肯定是持有Handler的引用钻哩,而handler又持有activity的引用

會(huì)有一條鏈`MessageQueue -> Message -> Handler -> Activity`,由于它的引用導(dǎo)致你的`Activity`被持有引用而無法被回收`

weakHandler博客
WeakHandler的實(shí)現(xiàn)原理:
WeakHandler的思想是將Handler和Runnable做一次封裝肛冶,我們使用的是封裝后的WeakHandler街氢,但其實(shí)真正起到handler作用的是封裝的內(nèi)部,而封裝的內(nèi)部對(duì)handler和runnable都是用的弱引用睦袖。

自己的理解:###

因?yàn)閮?nèi)部封裝了的handler與runnable都是弱引用珊肃,所以實(shí)際上供調(diào)用者使用的是封裝之后的weakHandler與weakRunnable,所雖然在Looper持有的weakhandler與weakRunnable馅笙,而weakHandler與weakRunnable又分別持有弱引用的handler與弱引用的runnable伦乔,所以當(dāng)被封裝了的handler與runnabl被回收時(shí),weakHandler內(nèi)部的弱引用handler與弱引用的runnable也會(huì)被回收董习,這時(shí)Looper持有的weakHandler內(nèi)部已經(jīng)沒有了handler與runnable烈和,只是一個(gè)空客,所以不會(huì)內(nèi)存泄露皿淋。


以下的內(nèi)容參考博客
內(nèi)存泄漏潛在危害非常大招刹,比如無意泄漏了一個(gè)Drawable,它可能只有幾百K的占用窝趣,但是由于它一般會(huì)引用View疯暑,就意味著同時(shí)泄漏了View,Context哑舒,Activity 以及 Activity中的resource妇拯,這個(gè)內(nèi)存的泄漏就非常可觀了洗鸵。
安卓中很容易出現(xiàn)這種連鎖的引用泄露

造成內(nèi)存泄露的情況有下面兩種:##

1越锈、try/catch/finally中網(wǎng)絡(luò)文件等流的沒有手動(dòng)關(guān)閉###

  • HTTP
  • File
  • ContendProvider
  • Bitmap
  • Uri
  • Socket

2、onDestroy() 或者 onPause()中未及時(shí)關(guān)閉對(duì)象###

  • 線程泄漏:當(dāng)你執(zhí)行耗時(shí)任務(wù)膘滨,在onDestroy()的時(shí)候考慮調(diào)用Thread.close()甘凭,如果對(duì)線程的控制不夠強(qiáng)的話,可以使用RxJava自動(dòng)建立線程池進(jìn)行控制吏祸,并在生命周期結(jié)束時(shí)取消訂閱对蒲;
  • Handler泄露:當(dāng)退出activity時(shí)钩蚊,要注意所在Handler消息隊(duì)列中的Message是否全部處理完成,可以考慮removeCallbacksAndMessages(null)手動(dòng)關(guān)閉
  • 廣播泄露:手動(dòng)注冊(cè)廣播時(shí)蹈矮,記住退出的時(shí)候要unregisterReceiver() 第三方SDK/開源框架泄露:ShareSDK,
  • JPush等第三方SDK需要按照文檔控制生命周期,它們有時(shí)候要求你繼承它們丑陋的activity砰逻,其實(shí)也為了幫你控制生命周期
  • 各種callBack/Listener的泄露,要及時(shí)設(shè)置為Null泛鸟,特別是static的callback
  • EventBus等觀察者模式的框架需要手動(dòng)解除注冊(cè)
  • 某些Service也要及時(shí)關(guān)閉蝠咆,比如圖片上傳,當(dāng)上傳成功后北滥,要stopself()
  • Webview需要手動(dòng)調(diào)用WebView.onPause()以及WebView.destory()

static class/method/variable 的區(qū)別刚操,你真的懂了嗎?##

(1). Static inner class 與 non static inner class 的區(qū)別
static inner class即靜態(tài)內(nèi)部類再芋,它只會(huì)出現(xiàn)在類的內(nèi)部菊霜,在某個(gè)類中寫一個(gè)靜態(tài)內(nèi)部類其實(shí)同你在IDE里新建一個(gè).java 文件是完全一樣的。


此處輸入圖片的描述
此處輸入圖片的描述

可以看到济赎,在生命周期中鉴逞,埋下了內(nèi)存泄漏的隱患,如果它的生命周期比activity更長(zhǎng)司训,那么可能會(huì)發(fā)生泄露构捡,更可怕的是,有可能會(huì)產(chǎn)生難以預(yù)防的空指針問題壳猜。這個(gè)泄露的例子勾徽,詳見內(nèi)存管理(2)的文章

(2). static inner method
靜態(tài)內(nèi)部方法,也就是虛函數(shù):可以被直接調(diào)用统扳,而不用去依賴它所在的類喘帚,比如你需要隨機(jī)數(shù),只用調(diào)用Math.random()即可闪幽,而不用實(shí)例化Math這個(gè)對(duì)象啥辨。在工具類(Utils)中涡匀,建議用static修飾方法盯腌。static方法的調(diào)用不會(huì)泄露內(nèi)存

(3). static inner variable
慎重使用靜態(tài)變量陨瘩,靜態(tài)變量是被分配給當(dāng)前的Class的腕够,由類的所有實(shí)例共享,而不是一個(gè)獨(dú)立的實(shí)例舌劳,當(dāng)ClassLoader停止加載這個(gè)Class時(shí)帚湘,它才會(huì)回收。在Android中甚淡,需要手動(dòng)置空才會(huì)卸掉ClassLoader大诸,才能出現(xiàn)GC。

當(dāng)你旋轉(zhuǎn)屏幕后,Drawable就會(huì)泄露资柔。

匿名內(nèi)部類實(shí)際上就是non-static inner class,所以也會(huì)有non-static inner class的缺點(diǎn)##

單例模式(Singleton)是不是內(nèi)存泄漏焙贷?##

在單例模式中,只有一個(gè)對(duì)象被產(chǎn)生贿堰,看起來一直占用了內(nèi)存辙芍,但是這個(gè)不意味就是浪費(fèi)了內(nèi)存,內(nèi)存本來就是用來裝東西的羹与,只要這個(gè)對(duì)象一直被高效的利用就不能叫做泄露故硅。但是也不要偷懶,一個(gè)勁的全整成了單例纵搁,越多的單例會(huì)讓內(nèi)存占用過多吃衅,放在Application中初始化的內(nèi)容也越多,意味著APP打開白屏的時(shí)間會(huì)更久腾誉,而且軟件維護(hù)起來也變得復(fù)雜捐晶。

好的例子:GlobalContext,SmsReceiver動(dòng)態(tài)注冊(cè)妄辩,EventBus

為什么大神喜歡用static final來修飾常數(shù)惑灵?##

static由于是所有實(shí)例共享的,說到共享一定要加鎖眼耀,萬一某個(gè)實(shí)例更改它后英支,其它的實(shí)例也會(huì)受到影響,所以加入final作為永久只讀鎖以防止常數(shù)被修改哮伟。

下面的話什么意思干花,看不懂?楞黄?池凄?

全局變量生命周期是classloader,有坑鬼廓。你的activity在finish后變量并不會(huì)改變肿仑。這個(gè)在面試中經(jīng)常遇到,問你經(jīng)過多次計(jì)算后碎税,static的值是多少尤慰。比如在Android中有個(gè)坑,最常見的就是把一個(gè)sharedpreference賦值給一個(gè)static變量雷蹂,然后又把sharedpreference改變后伟端,再次調(diào)用這個(gè)static變量,就發(fā)現(xiàn)變量并沒有改變匪煌,這個(gè)在debug中很難發(fā)現(xiàn)责蝠。

順便說下final吧##

final 變量:是只讀的党巾;
final 方法:是不能繼承或者重寫的。
final 引用:引用不能修改霜医,但是對(duì)象本身的屬性可以修改昧港;
final class:不可繼承;
final不會(huì)讓代碼速度更快

Bitmap的使用##

使用前注意配置Bitmap的Config支子,比如長(zhǎng)寬创肥,參數(shù)(565, 8888),格式值朋;
使用中注意緩存叹侄;
使用后注意recycle以清理native層的內(nèi)存。
2.3以后的bitmap不需要手動(dòng)recycle了,內(nèi)存已經(jīng)在java層了。同時(shí)缚窿,Bitmap還有別人做好的輪子,比如PhotoView,Picasso撒强,就可以方便的解決OOM問題。

線程泄露可能是最嚴(yán)重的泄露問題##

例如在activity中開了一個(gè)線程去上傳圖片笙什,完成之后彈出toast飘哨,但是在還沒有上傳完成之前,點(diǎn)擊了推出了activity琐凭,注意上傳線程是還在跑芽隆,當(dāng)上傳完成之后,卻發(fā)現(xiàn)window沒了统屈,toast彈不出所以拋出異常
所以應(yīng)該在activity退出的時(shí)候把上傳線程也要停止了胚吁。

Context與ApplicationContext##

Context的生命周期是一個(gè)Activiy,而ApplicationContext的生命周期是整個(gè)程序愁憔。我們最要注意的就是Context的內(nèi)存泄露腕扶。
在Activiy的UI中要使用Context,而在其他的地方比如數(shù)據(jù)庫(kù)、網(wǎng)絡(luò)吨掌、系統(tǒng)服務(wù)的需要頻繁調(diào)用Context的情況時(shí)半抱,要使用ApplicationContext,以防止內(nèi)存泄露思犁。
為什么ApplicationContext不會(huì)內(nèi)存泄漏代虾?进肯?激蹲?


其他的小技巧##

1、Listview的item泄露###

這個(gè)是入門問題了江掩,加入ViewHolder可以減少findViewById的時(shí)間学辱,或者使用RecyclerView乘瓤,來解決“滑動(dòng)很卡”的問題。這個(gè)實(shí)質(zhì)也是一個(gè)單例策泣。

2衙傀、StringBuilder###

首先說一下String,StringBuilder,StringBuffer的區(qū)別:

  • 字符串是不可變的,因?yàn)樽址际浅A浚∪绻阍囍淖兯鼈兊闹?另一個(gè)對(duì)象被創(chuàng)建,而StringBuffer和StringBuilder是可變的,這樣他們就可以改變它們的值
  • StringBuffer是線程安全的萨咕。當(dāng)應(yīng)用程序只需要運(yùn)行在單個(gè)線程則最好使用StringBuilder统抬。StringBuilder比StringBuffer更有效率,因?yàn)镾tringBuffer其實(shí)是給StringBuilder加了同步鎖危队;其實(shí)你使用Log.d(TAG,"xx"+"yy")這類寫法后聪建,編譯器生成的代碼已經(jīng)自動(dòng)幫你變成StringBuilder了

StringBuffer原理分析:###

將字符串拼接時(shí)(不管是字面常量也好,或者是變量茫陆,方法調(diào)用的結(jié)果也好)金麸,即用“+”將多個(gè)字符串拼接時(shí),實(shí)際上都是變成StringBuilder簿盅。如果一個(gè)字符串(不管是字面常量也好挥下,或者是變量,方法調(diào)用的結(jié)果也好)
new StringBuilder().append( string_exp ).append( any_exp ).toString()
如果表達(dá)式里有多個(gè)+號(hào)的話桨醋,后面相應(yīng)也會(huì)多多幾個(gè)StringBuilder.append的調(diào)用棚瘟,最后才是toString方法。
   StringBuilder(String)這個(gè)構(gòu)造方法會(huì)分配一塊16個(gè)字符的內(nèi)存緩沖區(qū)喜最。因此解取,如果后面拼接的字符不超過16的話,StringBuilder不需要再重新分配內(nèi)存返顺,不過如果超過16個(gè)字符的話StringBuilder會(huì)擴(kuò)充自己的緩沖區(qū)禀苦。最后調(diào)用toString方法的時(shí)候,會(huì)拷貝StringBuilder里面的緩沖區(qū)遂鹊,新生成一個(gè)String對(duì)象返回振乏。
  所以在在我們經(jīng)常將一些基本數(shù)據(jù)類型轉(zhuǎn)化成字符串時(shí),例如經(jīng)常是這樣做的:String text=100+"";雖然可以將整數(shù)100轉(zhuǎn)化成“100”字符串秉扑,但是一個(gè)StringBuilder對(duì)象慧邮,一個(gè)char[16]數(shù)組,一個(gè)String對(duì)象舟陆,一個(gè)能把輸入值存進(jìn)去的char[]數(shù)組误澳。這樣是很浪費(fèi)內(nèi)存的,所以推薦使用String.valueOf秦躯,即String text=String.valueOf(100);這樣至少StringBuilder對(duì)象省掉了忆谓。
  有的時(shí)候或許你根本就不需要轉(zhuǎn)化基礎(chǔ)類型。比如踱承,你正在解析一個(gè)字符串倡缠,它是用單引號(hào)分隔開的哨免。最初你可能是這么寫的:
  final int nextComma = str.indexOf("'");
  或者是這樣
   final int nextComma = str.indexOf('\'');


  • 同時(shí),使用字符串進(jìn)行邏輯運(yùn)算是相當(dāng)緩慢的,,不建議,因?yàn)镴VM將字符串轉(zhuǎn)換為字節(jié)碼的StringBuffer。浪費(fèi)大量的開銷將從字符串轉(zhuǎn)換為StringBuffer然后再返回字符串

綜上所述:盡量使用StringBuilder昙沦,而不用String來累加字符串

多用基本類型##

使用int而不用Integer琢唾,較少的對(duì)象花銷。在Android中使用sparseArrayMap取代HashMap就是把key變成了int盾饮,而一定程度上減小了內(nèi)存占用采桃。

Native代碼不受GC控制##

使用弱引用##

使用弱引用可以防止一定程度的無意引用造成的泄露,比如在Handler中使用弱引用作為參數(shù)丘损,當(dāng)銷毀的時(shí)候就有可能不會(huì)發(fā)生泄露芍碧。
但是弱引用隨時(shí)可能為null,使用前需要判斷是否為空

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末号俐,一起剝皮案震驚了整個(gè)濱河市泌豆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌吏饿,老刑警劉巖踪危,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異猪落,居然都是意外死亡贞远,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門笨忌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蓝仲,“玉大人,你說我怎么就攤上這事官疲「そ幔” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵途凫,是天一觀的道長(zhǎng)垢夹。 經(jīng)常有香客問我,道長(zhǎng)维费,這世上最難降的妖魔是什么果元? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮犀盟,結(jié)果婚禮上而晒,老公的妹妹穿的比我還像新娘。我一直安慰自己阅畴,他們只是感情好倡怎,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般诈胜。 火紅的嫁衣襯著肌膚如雪豹障。 梳的紋絲不亂的頭發(fā)上冯事,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天焦匈,我揣著相機(jī)與錄音,去河邊找鬼昵仅。 笑死缓熟,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的摔笤。 我是一名探鬼主播够滑,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼吕世!你這毒婦竟也來了彰触?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤命辖,失蹤者是張志新(化名)和其女友劉穎况毅,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體尔艇,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡尔许,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了终娃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片味廊。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖棠耕,靈堂內(nèi)的尸體忽然破棺而出余佛,到底是詐尸還是另有隱情,我是刑警寧澤窍荧,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布衙熔,位于F島的核電站,受9級(jí)特大地震影響搅荞,放射性物質(zhì)發(fā)生泄漏红氯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一咕痛、第九天 我趴在偏房一處隱蔽的房頂上張望痢甘。 院中可真熱鬧,春花似錦茉贡、人聲如沸塞栅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽放椰。三九已至作烟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間砾医,已是汗流浹背拿撩。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留如蚜,地道東北人压恒。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像错邦,于是被迫代替她去往敵國(guó)和親探赫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容