Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> [Android]內存洩露排查手記

[Android]內存洩露排查手記

編輯:關於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())中及時執行關停回收操作即可避免。


 


  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved