編輯:關於Android編程
內存洩露排查手記
Time:2013.09.02
Author:sodino
問題現象:
這裡內存洩露是指已實例化的對象長期被hold住且無法釋放或不能按照對象正常的生命周期進行釋放。
問題期望:
進行多次重復操作後,能夠正常回收該對象(JobAppInterface)。期望在切換帳號後,之前的JobAppInterface能夠及時回收(允許等待一段時間後再回收)。
問題排查:
經過排查,總結為三種情況導致JobAppInterface內存洩露:
1.靜態實例長期占用JobAppInterface。
2.線程沒有被stop導致JobAppInterface無法釋放。
3.Observer/Listener沒有被反注冊導致Activity或JobAppInterface無法被釋放。
一號坑:靜態實例長期占用
嚴重,無法釋放
SettingManager的靜態實例長期占據第一次初始化的JobAppInterface
表現:啟動應用時所創建的JobAppInterface會一直被引用,無法釋放。
原因:
經全文查找,發現其靜態實例只有初始化的入口,條件是當其實例為null的時候就初始化。sInstance執行new操作後,再沒有任何回收操作。這樣,除非手Q完全退出了,不然sInstance會一直存在並且占其初始化時使用的JobAppInterface。
public static SettingManager getInstance(AppInterface app) { if (sInstance == null) { sInstance = new SettingManager(app); } return sInstance; }
解決方案:
在獲取實例時,如果sInstance已經存在,則對比app,當app不一致時則進行回收和新的實例的生成。
public static SettingManager getInstance(AppInterface app) { if (sInstance == null) { sInstance = new SettingManager(app); } else if (sInstance.mApp != app) { //切換了賬號 sInstance = null; sInstance = new SettingManager(app); } return sInstance; }
另,將該實例的回收與JobAppInterface的生命周期保持一致。在JobAppInterface.onDestory()時及時清理掉。
JobAppInterface.java protected void onDestroy() { ... ... SettingManager.clearInstance(this); ... ... } SettingManager.java public static void clearInstance(AppInterface app){ if(sInstance != null && sInstance.mApp == app){ sInstance = null; } }
二號坑:線程長期running導致對象無法被釋放
嚴重,無法釋放
MessageThread線程一直在運行,導致無法釋放JobAppInterface
表現:
這個在瘋狂亂點切換帳號時出現。MAT工具發現無法釋放的JobAppInterface都有被多個MessageThread引用著。嫌疑很大。但當時對這塊邏輯不熟,為了進一步確認,做了如下操作:
1.在生成MessageThread的地方將新new出現的Thread命名為index_System.time,並在其start()之後輸出相應的start日志,日志信息包括Thread的name及其所關聯的JobAppInterface.hashcode。
2.在Thread.run()方法結尾處輸出exit日志,日志信息同樣包括Thread的name及其所關聯的JobAppInterface.hashcode。
經比較,發現快速切換帳號後,每切換一次會新生成5個Thread,但最後都只有5個Thread會執行exit操作,則仍然為(n -1)*5個Thread仍在運行著,導致JobAppInterface無法釋放。
原因:
確定了MessageThread有問題後,可以下定決心分析下原因了。經查,發現在原邏輯中,是有關閉這些Thread的地方,如下:
JobInitHandler.java中: private void onStateRunning() { ... ... ... ... // 消息拉取完成後,做一些事情 if(curStep == (STEP_GET_MSG + 1)){ doSomethingAfterSyncMsg();// ---->這裡去關閉MessageThread.. } switch (curStep) { case STEP_INIT: // action... break; case STEP_START: // action... break; ... ... ... ... }; }
在JobInitHandler.onStateRunning(),當消息拉取完成後,做一些事情這裡會去把MessageThread 停止掉。但問題就出在這裡,關閉的時機出現了問題,在快速切換帳號的操作中,由於沒有足夠的時間讓消息拉取操作完成,也就造成了curStep無法走到值為(STEP_GET_MSG + 1)的情形,導致MessageThread一直在空跑無法,其引用的JobAppInterface無法被釋放。
解決方案:
在JobInitHandler.destory()時將仍在運行的Thread停止掉。
public void destroy() { ... ... ... ... // 停止代理處理線程 app.getHandler().stopProxyThread(); ... ... ... ... }
三號坑:Observer/Listener沒有被反注冊
嚴重,無法釋放
注冊BroadcastReceiver後沒有反注冊
表現:
直接看圖吧,new的一個BroadcastReceiver在構造函數中直接register,但通篇沒有被ungister導致內存洩露。見圖1。
解決方案:
if(tmpHandler != null && tmpHandler instanceof DataLineHandler){ ((DataLineHandler)tmpHandler).close();// 執行反注冊操作 }
問題總結:
就目前來說,經過以上三種類型問題的排查,目前已經達成目的。
這裡小總結一下,洩露的原因分別為靜態實例占用、線程沒被及時停止、注冊的Observer/BroadcastReceiver沒有及時被反注冊。但解決的方法都是同樣的:在JobAppInterface的onDesotry()方法(或相似的如JobInitHandler.destory())中及時執行關停回收操作即可避免。
上一篇博客中我們已經繪制出了一個直角三角形,雖然我們相對於坐標,我們設置的直角三角形的兩腰是相等的,但是實際上展示出來的卻並不是這樣,雖然通過計算,我們可以把三角形的兩腰
最近在項目中需要對外部存儲設備的狀態進行監聽,所以整理了此筆記,以便日後查看。 外部存儲設備的狀態變化時發出的廣播
---- The mark of the immature man is that he wants to die nobly for a causer wh
1、概述控件基於android-Ultra-Pull-to-Refresh做的header定制,繼承PtrFrameLayout,把事件分發給裡面的RadioGroup,