標簽(空格分隔): Android
我們知道App都有一個UI線程,也叫主線程,那是Android框架幫我們創(chuàng)建的,這里注意的是不是每個activity對應一個UI主線程,而是一個App。
為Message都在一個隊列中,擁有它下一個對象的引用非常重要,這里的寫法其實跟我們上學時學習的隊列類似,每一個實體類都擁有下一個的引用,這樣就構(gòu)成了一個隊列
內(nèi)存泄漏的基本知識請見博客一
如何高效使用handler避免內(nèi)存泄漏請見博客二
Looper造成內(nèi)存泄漏的總結(jié)為:
1、因為Looper里面的MesageQueue持有外部傳進來的runnable引用,runnable又持有handler的引用,handler持有activity的引用//這種情況在用handler開啟一個耗時的后臺操作時,這時開啟的線程也會持有handler的引用,在關(guān)閉`Activity`的時候停掉你的后臺線程。線程停掉了,就相當于切斷了`Handler`和外部連接的線,`Activity`自然會在合適的時候被回收。
2、在使用handler.handleMessage()這個方法時,實際上是一個回調(diào)的過程
,在message的內(nèi)部handleMessage()將肯定是持有Handler的引用,而handler又持有activity的引用
會有一條鏈`MessageQueue -> Message -> Handler -> Activity`,由于它的引用導致你的`Activity`被持有引用而無法被回收`
weakHandler博客
WeakHandler的實現(xiàn)原理:
WeakHandler的思想是將Handler和Runnable做一次封裝,我們使用的是封裝后的WeakHandler,但其實真正起到handler作用的是封裝的內(nèi)部,而封裝的內(nèi)部對handler和runnable都是用的弱引用。
自己的理解:###
因為內(nèi)部封裝了的handler與runnable都是弱引用,所以實際上供調(diào)用者使用的是封裝之后的weakHandler與weakRunnable,所雖然在Looper持有的weakhandler與weakRunnable,而weakHandler與weakRunnable又分別持有弱引用的handler與弱引用的runnable,所以當被封裝了的handler與runnabl被回收時,weakHandler內(nèi)部的弱引用handler與弱引用的runnable也會被回收,這時Looper持有的weakHandler內(nèi)部已經(jīng)沒有了handler與runnable,只是一個空客,所以不會內(nèi)存泄露。
以下的內(nèi)容參考博客
內(nèi)存泄漏潛在危害非常大,比如無意泄漏了一個Drawable,它可能只有幾百K的占用,但是由于它一般會引用View,就意味著同時泄漏了View,Context,Activity 以及 Activity中的resource,這個內(nèi)存的泄漏就非??捎^了。
安卓中很容易出現(xiàn)這種連鎖的引用泄露
造成內(nèi)存泄露的情況有下面兩種:##
1、try/catch/finally中網(wǎng)絡(luò)文件等流的沒有手動關(guān)閉###
- HTTP
- File
- ContendProvider
- Bitmap
- Uri
- Socket
2、onDestroy() 或者 onPause()中未及時關(guān)閉對象###
- 線程泄漏:當你執(zhí)行耗時任務,在onDestroy()的時候考慮調(diào)用Thread.close(),如果對線程的控制不夠強的話,可以使用RxJava自動建立線程池進行控制,并在生命周期結(jié)束時取消訂閱;
- Handler泄露:當退出activity時,要注意所在Handler消息隊列中的Message是否全部處理完成,可以考慮removeCallbacksAndMessages(null)手動關(guān)閉
- 廣播泄露:手動注冊廣播時,記住退出的時候要unregisterReceiver() 第三方SDK/開源框架泄露:ShareSDK,
- JPush等第三方SDK需要按照文檔控制生命周期,它們有時候要求你繼承它們丑陋的activity,其實也為了幫你控制生命周期
- 各種callBack/Listener的泄露,要及時設(shè)置為Null,特別是static的callback
- EventBus等觀察者模式的框架需要手動解除注冊
- 某些Service也要及時關(guān)閉,比如圖片上傳,當上傳成功后,要stopself()
- Webview需要手動調(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)部類,它只會出現(xiàn)在類的內(nèi)部,在某個類中寫一個靜態(tài)內(nèi)部類其實同你在IDE里新建一個.java 文件是完全一樣的。

可以看到,在生命周期中,埋下了內(nèi)存泄漏的隱患,如果它的生命周期比activity更長,那么可能會發(fā)生泄露,更可怕的是,有可能會產(chǎn)生難以預防的空指針問題。這個泄露的例子,詳見內(nèi)存管理(2)的文章
(2). static inner method
靜態(tài)內(nèi)部方法,也就是虛函數(shù):可以被直接調(diào)用,而不用去依賴它所在的類,比如你需要隨機數(shù),只用調(diào)用Math.random()即可,而不用實例化Math這個對象。在工具類(Utils)中,建議用static修飾方法。static方法的調(diào)用不會泄露內(nèi)存。
(3). static inner variable
慎重使用靜態(tài)變量,靜態(tài)變量是被分配給當前的Class的,由類的所有實例共享,而不是一個獨立的實例,當ClassLoader停止加載這個Class時,它才會回收。在Android中,需要手動置空才會卸掉ClassLoader,才能出現(xiàn)GC。
當你旋轉(zhuǎn)屏幕后,Drawable就會泄露。
匿名內(nèi)部類實際上就是non-static inner class,所以也會有non-static inner class的缺點##
單例模式(Singleton)是不是內(nèi)存泄漏?##
在單例模式中,只有一個對象被產(chǎn)生,看起來一直占用了內(nèi)存,但是這個不意味就是浪費了內(nèi)存,內(nèi)存本來就是用來裝東西的,只要這個對象一直被高效的利用就不能叫做泄露。但是也不要偷懶,一個勁的全整成了單例,越多的單例會讓內(nèi)存占用過多,放在Application中初始化的內(nèi)容也越多,意味著APP打開白屏的時間會更久,而且軟件維護起來也變得復雜。
好的例子:GlobalContext,SmsReceiver動態(tài)注冊,EventBus
為什么大神喜歡用static final來修飾常數(shù)?##
static由于是所有實例共享的,說到共享一定要加鎖,萬一某個實例更改它后,其它的實例也會受到影響,所以加入final作為永久只讀鎖以防止常數(shù)被修改。
下面的話什么意思,看不懂???
全局變量生命周期是classloader,有坑。你的activity在finish后變量并不會改變。這個在面試中經(jīng)常遇到,問你經(jīng)過多次計算后,static的值是多少。比如在Android中有個坑,最常見的就是把一個sharedpreference賦值給一個static變量,然后又把sharedpreference改變后,再次調(diào)用這個static變量,就發(fā)現(xiàn)變量并沒有改變,這個在debug中很難發(fā)現(xiàn)。
順便說下final吧##
final 變量:是只讀的;
final 方法:是不能繼承或者重寫的。
final 引用:引用不能修改,但是對象本身的屬性可以修改;
final class:不可繼承;
final不會讓代碼速度更快
Bitmap的使用##
使用前注意配置Bitmap的Config,比如長寬,參數(shù)(565, 8888),格式;
使用中注意緩存;
使用后注意recycle以清理native層的內(nèi)存。
2.3以后的bitmap不需要手動recycle了,內(nèi)存已經(jīng)在java層了。同時,Bitmap還有別人做好的輪子,比如PhotoView,Picasso,就可以方便的解決OOM問題。
線程泄露可能是最嚴重的泄露問題##
例如在activity中開了一個線程去上傳圖片,完成之后彈出toast,但是在還沒有上傳完成之前,點擊了推出了activity,注意上傳線程是還在跑,當上傳完成之后,卻發(fā)現(xiàn)window沒了,toast彈不出所以拋出異常
所以應該在activity退出的時候把上傳線程也要停止了。
Context與ApplicationContext##
Context的生命周期是一個Activiy,而ApplicationContext的生命周期是整個程序。我們最要注意的就是Context的內(nèi)存泄露。
在Activiy的UI中要使用Context,而在其他的地方比如數(shù)據(jù)庫、網(wǎng)絡(luò)、系統(tǒng)服務的需要頻繁調(diào)用Context的情況時,要使用ApplicationContext,以防止內(nèi)存泄露。
為什么ApplicationContext不會內(nèi)存泄漏???
其他的小技巧##
1、Listview的item泄露###
這個是入門問題了,加入ViewHolder可以減少findViewById的時間,或者使用RecyclerView,來解決“滑動很卡”的問題。這個實質(zhì)也是一個單例。
2、StringBuilder###
首先說一下String,StringBuilder,StringBuffer的區(qū)別:
- 字符串是不可變的,因為字符串都是常量!如果你試著改變它們的值,另一個對象被創(chuàng)建,而StringBuffer和StringBuilder是可變的,這樣他們就可以改變它們的值
- StringBuffer是線程安全的。當應用程序只需要運行在單個線程則最好使用StringBuilder。StringBuilder比StringBuffer更有效率,因為StringBuffer其實是給StringBuilder加了同步鎖;其實你使用Log.d(TAG,"xx"+"yy")這類寫法后,編譯器生成的代碼已經(jīng)自動幫你變成StringBuilder了
StringBuffer原理分析:###
將字符串拼接時(不管是字面常量也好,或者是變量,方法調(diào)用的結(jié)果也好),即用“+”將多個字符串拼接時,實際上都是變成StringBuilder。如果一個字符串(不管是字面常量也好,或者是變量,方法調(diào)用的結(jié)果也好)
new StringBuilder().append( string_exp ).append( any_exp ).toString()
如果表達式里有多個+號的話,后面相應也會多多幾個StringBuilder.append的調(diào)用,最后才是toString方法。
StringBuilder(String)這個構(gòu)造方法會分配一塊16個字符的內(nèi)存緩沖區(qū)。因此,如果后面拼接的字符不超過16的話,StringBuilder不需要再重新分配內(nèi)存,不過如果超過16個字符的話StringBuilder會擴充自己的緩沖區(qū)。最后調(diào)用toString方法的時候,會拷貝StringBuilder里面的緩沖區(qū),新生成一個String對象返回。
所以在在我們經(jīng)常將一些基本數(shù)據(jù)類型轉(zhuǎn)化成字符串時,例如經(jīng)常是這樣做的:String text=100+"";雖然可以將整數(shù)100轉(zhuǎn)化成“100”字符串,但是一個StringBuilder對象,一個char[16]數(shù)組,一個String對象,一個能把輸入值存進去的char[]數(shù)組。這樣是很浪費內(nèi)存的,所以推薦使用String.valueOf,即String text=String.valueOf(100);這樣至少StringBuilder對象省掉了。
有的時候或許你根本就不需要轉(zhuǎn)化基礎(chǔ)類型。比如,你正在解析一個字符串,它是用單引號分隔開的。最初你可能是這么寫的:
final int nextComma = str.indexOf("'");
或者是這樣
final int nextComma = str.indexOf('\'');
- 同時,使用字符串進行邏輯運算是相當緩慢的,,不建議,因為JVM將字符串轉(zhuǎn)換為字節(jié)碼的StringBuffer。浪費大量的開銷將從字符串轉(zhuǎn)換為StringBuffer然后再返回字符串
綜上所述:盡量使用StringBuilder,而不用String來累加字符串
多用基本類型##
使用int而不用Integer,較少的對象花銷。在Android中使用sparseArrayMap取代HashMap就是把key變成了int,而一定程度上減小了內(nèi)存占用。
Native代碼不受GC控制##
使用弱引用##
使用弱引用可以防止一定程度的無意引用造成的泄露,比如在Handler中使用弱引用作為參數(shù),當銷毀的時候就有可能不會發(fā)生泄露。
但是弱引用隨時可能為null,使用前需要判斷是否為空