1. 程式人生 > >有關Android Handler記憶體洩漏分析及解決辦法

有關Android Handler記憶體洩漏分析及解決辦法

1、Android的開發工具是java,這能幫助我們解決很底層的問題 包括:記憶體管理,平臺依賴。然而,有時候專案依然會報OOM錯誤,so垃圾收集器在哪?

2、我主要研究一種情況:記憶體中較大物件很長一段時間內不能被釋放。這方面並不完全算作記憶體溢位,物件會在某一時間點上被收集,so我們不屌它。雖然有時候他也會導致oom,所以不建議這麼幹滴。(這話咋說的這麼矛盾,作者精分了?)

3、簡單例子:

public class NewActivity extends Activity {
	private Handler mHandler = new Handler();
	private TextView mTextview;

	@Override
	protected void onCreate(android.os.Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.inflate_textview);
		
		mTextview = (TextView) findViewById(R.id.xxwj_newtv4);
		mHandler.postDelayed(new Runnable() {
			@Override
			public void run() {
				mTextview.setText("Done");
			}
		}, 80000);
	};
}

4、這是一個非常基本的Activity,注意到匿名類Runnable被handler延遲執行了好多次,我們執行並且旋轉螢幕多次,然後dump memory 分析它


5、現在記憶體裡有很多Activity,這tm很不好啊,為啥gc沒收了他們。

6、獲取所有記憶體的Activity查詢語句是在OQL(Object Query Language)建立的,非常簡單粗暴。


7、能夠看出,其中一個Activity是被this$0引用了,這是一個來自內部匿名類指向其主類的間接引用(OMG) this$0是被callback方法引用的,callback又被一堆next()方法引用到主執行緒。

8、任何時候你建立一個非靜態內部類在你自己的類中,java會自動為其建立一個間接引用指向其主類。

9、只要你的handler呼叫了Runnable或Message,它將被儲存在LoopThread所引用的Message訊息佇列中,直到Message被執行。傳送delayed訊息是一個明顯的記憶體洩漏,洩漏的時間大於等於延遲的時間。如果訊息佇列很長的話傳送非延遲訊息也可能引發一個臨時洩漏。

10、解決方案1:Static Runnable

讓我們試著克服這個把我們弄瘋了的this$0,將匿名類轉化為靜態類

@Override
protected void onCreate(android.os.Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.inflate_textview);
	
	mTextview = (TextView) findViewById(R.id.xxwj_newtv4);
	mHandler.postDelayed(new DoneRunnable(mTextview), 80000);
};

private static final class DoneRunnable implements Runnable{
	private final TextView mTV;
	public DoneRunnable(TextView tv) {
		this.mTV = tv;
	}
		
	@Override
	public void run() {
		mTV.setText("Done");
	}
	
}

11、執行、旋轉螢幕、釋放記憶體堆:


12、啥?還tm這樣,看看誰持有了Activities的引用


13、看這顆樹的最底部,activity被DoneRunnable中的mTextView類的mContext引用,用靜態內部類沒辦法解決記憶體洩漏,需要來點更狠的

14、解決方案2:弱引用的Static Runnable

我們繼續用之前的修復辦法並且弱引組織Activity被釋放的TextView

@Override
protected void onCreate(android.os.Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.inflate_textview);
	
	mTextview = (TextView) findViewById(R.id.xxwj_newtv4);
	mHandler.postDelayed(new DoneRunnable(mTextview), 80000);
};
	
private static final class DoneRunnable implements Runnable{
	private final WeakReference<TextView> mTV;
	public DoneRunnable(TextView tv) {
		this.mTV = new WeakReference<TextView>(tv);
	}
	
	@Override
	public void run() {
		final TextView tt = mTV.get();
		if(tt != null){
			tt.setText("Done");
		}
	}
	
}

15、重新執行,旋轉,釋放記憶體:


16、woops!只有一個Activity例項,解決了我們的記憶體洩漏問題

17、使用弱引用時要小心謹慎,他們能在任何時候被置為空,解決辦法就是先賦值給一個區域性變數(強引用),然後在使用前檢查是否為空

18、解決handler記憶體洩漏我們需要做的:

1、用靜態內部類或外部類

2、操縱Handler/Runnable時用弱引用

19、如果和一開始的程式碼進行對比,會發現在可讀性和清晰度方面有很大的不同。一開始的程式碼很短和清晰,這麼寫太麻煩

20、解決方案3:在onDestroy中清空所有Message

Handler類中有個牛逼方法removeCallbacksAndMessages(),它可以接受空欄位作為引數,它將移除所有特定Handler的Runnable和Message:

@Override
protected void onDestroy() {
	mHandler.removeCallbacksAndMessages(null);
	super.onDestroy();
}


21、完美!這比上一個解決辦法要好很多。唯一難搞的是你需要在所有的Activity/Fragment中呼叫onDestory()方法來清除message,聽著都噁心

22、解決方案4:用WeakHandler

隆重介紹一下Badoo團隊整的WeakHeadler,Handler的替代方案,安全多了。


23、程式碼很像吧,經實測記憶體中也只有一個例項,簡單吧,程式碼和記憶體都一樣簡潔。使用方法gradle一下即可

24、總結:

本文一共介紹了3種方案:

1、宣告一個繼承Runnable類的靜態內部類,並弱引用要操作的控制元件

2、在onDestroy()中呼叫removeCallbacksAndMessages()

3、呼叫Badoo團隊的WeakHandler來替代os.Handler

666、WeakHandler只能替代postDelay(New Runnable())方法造成的記憶體洩漏,沒辦法替代os.Handler中接收訊息的操作,所以正常情況下需要呼叫方法(2)