標(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 文件是完全一樣的。
![此處輸入圖片的描述](http://o6uwc0k25.bkt.clouddn.com/%E5%8C%BA%E5%88%AB.jpg)
可以看到济赎,在生命周期中鉴逞,埋下了內(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,使用前需要判斷是否為空