新聞中心
前言

通過(guò)這幾天對(duì)好幾個(gè)應(yīng)用的內(nèi)存泄露檢測(cè)和改善,效果明顯:
- 完全退出應(yīng)用時(shí),手動(dòng)觸發(fā)GC,從原來(lái)占有內(nèi)存100多M降到低于20M;
- 手動(dòng)觸發(fā)GC后,通過(guò)adb shell dumpsys meminfo packagename -d查看Activity和View的數(shù)量也趨近于0了(沒(méi)有做到歸零是因?yàn)镾DK中存在內(nèi)存泄露,需要中間層去處理);
- 發(fā)現(xiàn)了一個(gè)SDK中的內(nèi)存泄露(Android InputMethodManager 導(dǎo)致的內(nèi)存泄露及解決方案);
- 發(fā)現(xiàn)一個(gè)MTK Webview的內(nèi)存泄露(org.chromium.android_webview.AwPasswordHandler.java中private static AwPasswordHandler sInstance = null導(dǎo)致的內(nèi)存泄露)。
從結(jié)果來(lái)看我分析和改善內(nèi)存泄露的方法是對(duì)的,這個(gè)過(guò)程并不復(fù)雜,所以可以梳理總結(jié)出來(lái)作為分享。
原則
對(duì)于性能問(wèn)題,分析和改善有必要遵循以下原則:
- 一切看數(shù)據(jù)說(shuō)話,不能跟著感覺(jué)走,感覺(jué)哪有問(wèn)題就去改,很有可能會(huì)適得其反;
- 性能優(yōu)化是一個(gè)持續(xù)的過(guò)程,需要不斷地改善,不要想著一氣呵成;
- 對(duì)于性能問(wèn)題,不一定必須要改善,受限于架構(gòu)或者其它原因某些問(wèn)題可能會(huì)很難改善,必須要先保證能用,再才考慮好用。
- 改善后一定要驗(yàn)證,任何一個(gè)地方的改動(dòng)都需要驗(yàn)證,避免因?yàn)楦纳菩阅軉?wèn)題導(dǎo)致其它的問(wèn)題。
步驟
下面是我在針對(duì)內(nèi)存泄露這個(gè)性能問(wèn)題上的解決步驟:
優(yōu)先處理常見(jiàn)的內(nèi)存泄露問(wèn)題
首先解決常見(jiàn)的內(nèi)存泄露問(wèn)題,這個(gè)過(guò)程可以借助Android Studio的Analyze-Inspect Code對(duì)代碼做靜態(tài)分析,常見(jiàn)的內(nèi)存泄露問(wèn)題有:
- 非靜態(tài)內(nèi)部類導(dǎo)致的內(nèi)存泄露,比如Handler,解決方法是將內(nèi)部類寫(xiě)成靜態(tài)內(nèi)部類,在靜態(tài)內(nèi)部類中使用軟引用/弱引用持有外部類的實(shí)例,eg:
- static class ExerciseHandler extends Handler{
- private SoftReference
exerciseActivitySoftReference = null; - public ExerciseHandler(ExerciseActivity exerciseActivity){
- exerciseActivitySoftReference = new SoftReference
(exerciseActivity); - }
- @Override
- public void handleMessage(Message msg) {
- ExerciseActivity exerciseActivity = exerciseActivitySoftReference.get();
- if(null != exerciseActivity){
- super.handleMessage(msg);
- switch (msg.what) {
- case MSG_XX:
- exerciseActivity.***;
- break;
- default:
- break;
- }
- }
- }
- }
- IO操作后,沒(méi)有關(guān)閉文件導(dǎo)致的內(nèi)存泄露,比如Cursor、FileInputStream、FileOutputStream使用完后沒(méi)有關(guān)閉,這種問(wèn)題在Android Studio 2.0中能夠通過(guò)靜態(tài)代碼分析檢查出來(lái),直接改善就可以了;
- 自定義View中使用TypedArray后,沒(méi)有recycle,這種問(wèn)題也可以在Android Studio 2.0中能夠通過(guò)靜態(tài)代碼分析檢查出來(lái),直接改善就可以了;
- 某些地方使用了四大組件的context,在離開(kāi)這些組件后仍然持有其context導(dǎo)致的內(nèi)存泄露,這種問(wèn)題屬于共識(shí),在編寫(xiě)代碼的過(guò)程中就應(yīng)該按照規(guī)則來(lái),使用Application的Context就可以解決這類內(nèi)存泄露的問(wèn)題了,至于什么情況下應(yīng)該使用四大組件的Context,什么時(shí)候應(yīng)該使用Application的context可以參見(jiàn)下表:
application使用場(chǎng)景
備注:大家注意看到有一些NO上添加了一些數(shù)字,其實(shí)這些從能力上來(lái)說(shuō)是YES,但是為什么說(shuō)是NO呢?下面一個(gè)一個(gè)解釋:
1、數(shù)字1:?jiǎn)?dòng)Activity在這些類中是可以的,但是需要?jiǎng)?chuàng)建一個(gè)新的task,一般情況不推薦;
2、數(shù)字2:在這些類中去layout inflate是合法的,但是會(huì)使用系統(tǒng)默認(rèn)的主題樣式,如果你自定義了某些樣式可能不會(huì)被使用;
3、數(shù)字3:在Receiver為null時(shí)允許,在4.2或以上的版本中,用于獲取黏性廣播的當(dāng)前值。(可以無(wú)視);
4、ContentProvider、BroadcastReceiver之所以在上述表格中,是因?yàn)樵谄鋬?nèi)部方法中都有一個(gè)context用于使用。
還有一種不屬于內(nèi)存泄露,但在分析內(nèi)存泄露的問(wèn)題時(shí)應(yīng)該一并解決:同一個(gè)APP,將圖片放在不同的drawable文件夾下,在相同的設(shè)備上占用的內(nèi)存情況不一樣,具體可以參見(jiàn):關(guān)于Android中圖片大小、內(nèi)存占用與drawable文件夾關(guān)系的研究與分析。解決這個(gè)問(wèn)題遵循以下原則就可以了:1、UI只提供一套高分辨率的圖,圖片建議放在drawable-xxhdpi文件夾下(放在xxxhdpi或者更高分辨率的文件夾下沒(méi)有必要,權(quán)衡利弊,照顧主流設(shè)備即可),這樣在低分辨率設(shè)備中圖片的大小只是壓縮,不會(huì)存在內(nèi)存增大的情況;2、涉及到桌面插件或者不需要縮放的圖片,放在drawable-nodpi文件夾下,這個(gè)文件夾下的圖片在任何設(shè)備上都是不會(huì)縮放的。
通過(guò)工具檢查程序運(yùn)行后的內(nèi)存泄露
通過(guò)上面的步驟,應(yīng)用中的大部分內(nèi)存泄露問(wèn)題都能夠得到解決,還有一些內(nèi)存泄露,需要運(yùn)行程序,分析運(yùn)行后的內(nèi)存快照來(lái)解決,比如注冊(cè)之后沒(méi)有反注冊(cè)、類中的靜態(tài)成員變量導(dǎo)致的內(nèi)存泄露、SDK中的內(nèi)存泄露等。解決這類問(wèn)題可以分兩步進(jìn)行:
- 通過(guò)內(nèi)存泄露檢測(cè)工具先定位是哪有問(wèn)題,內(nèi)存泄露的檢測(cè)有兩種比較便捷的方式:1、一種是使用開(kāi)源項(xiàng)目Leakcanary,需要添加到代碼中,運(yùn)行后生成分析結(jié)果;2、另一種方式是使用adb shell dumpsys meminfo packagename -d命令,在進(jìn)入一個(gè)界面之前查看一遍Activity和View的數(shù)量,在退出這個(gè)界面之后再查看一遍Activity和View的數(shù)量,對(duì)比進(jìn)入前和進(jìn)入后Activity和View數(shù)量的變化情況,如果有差異,則說(shuō)明存在內(nèi)存泄露(在使用命令查看Activity和View的數(shù)量之前,記得手動(dòng)觸發(fā)GC)。
備注:在Android Studio中,可以通過(guò)如下方式獲取當(dāng)前選中進(jìn)程的內(nèi)存信息:
- 然后通過(guò)MAT取程序運(yùn)行時(shí)的內(nèi)存快照做詳細(xì)分析,對(duì)于MAT的使用,網(wǎng)上有很多優(yōu)質(zhì)的文章,比如:Android 性能優(yōu)化之使用MAT分析內(nèi)存泄露問(wèn)題,在使用MAT前,有必要知道這幾點(diǎn):1、 不要指望MAT明確告訴你哪里存在內(nèi)存泄露,這需要你根據(jù)上一步驟首先定位到可能存在內(nèi)存泄露的類,然后借助MAT確認(rèn)是否真的存在內(nèi)存泄露,具體哪個(gè)地方存在內(nèi)存泄露;2、借助Retained Size分析某一個(gè)類及與之相關(guān)的實(shí)例所消耗的內(nèi)存,如果這個(gè)類的Retained Size比較大,優(yōu)先分析;3、檢查某個(gè)類是否存在內(nèi)存泄露時(shí),排除其軟/弱/虛引用,右鍵某個(gè)類→Merge Shortest Paths to GC Roots→exclude all phantom/weak/soft etc.references。
驗(yàn)證改善效果
根據(jù)個(gè)人經(jīng)驗(yàn),我一般是這樣驗(yàn)證改善效果的,運(yùn)行程序,各個(gè)功能跑一遍,確保沒(méi)有改出問(wèn)題,完全退出程序,手動(dòng)觸發(fā)GC,然后通過(guò)adb shell dumpsys meminfo packagename -d查看Activivites和Views的數(shù)量是否趨近于0;如果不是0,通過(guò)Leakcanary檢查可能存在內(nèi)存泄露的地方,繼續(xù)通過(guò)MAT分析,周而復(fù)始,改善到自己滿意為止。
推薦閱讀
- Speed up your app
- Android 性能優(yōu)化之使用MAT分析內(nèi)存泄露問(wèn)題
當(dāng)前名稱:Android應(yīng)用內(nèi)存泄露分析、改善經(jīng)驗(yàn)總結(jié)
分享鏈接:http://fisionsoft.com.cn/article/coeieod.html


咨詢
建站咨詢
