Android -> 如何避免Handler引起記憶體洩露
錯誤程式碼
如果在Activiy中通過內部類(Runnable)的方式定義了一個變數runnable,
final Runnable runnable = new Runnable() {
public void run() {
// ... do some work
}
};
handler.postDelayed(runnable, TimeUnit.SECONDS.toMillis(10)
因為Runnable不是static型別,所以會有一個包含Activity例項的implicit reference --- Activity.this。
如果Activity在runnable變數run之前(10s內)被finish掉了但是Activity.this仍然存在,那麼Activity的物件就不會被GC回收,從而導致memory leak
即使使用一個靜態內部類,也不能保證萬事大吉。
static class MyRunnable implements Runnable {
private View view;
public MyRunnable(View view) {
this.view = view;
}
public void run() {
// ... do something with the view
}
}
假設在runnable執行之前,View被移除了,但是成員變數view還在繼續引用它,仍然會導致memory leak。
上面的兩個例子當中,導致記憶體洩露的兩種用法分別是隱式引用(implicit reference)
解決方法
解決隱式引用的方法比較簡單,只要使用內部非靜態類(non-static inner class)或者 top-level class(在一個獨立的java檔案中定義的變數)就可以將隱式變為顯式,從而避免記憶體洩露。
如果繼續使用非靜態內部類,那麼就要在onPause的時候手動結束那些掛起的任務(pending task)。
關於如何結束任何,Handler可參考這篇文章中的Canceling a pending Runnable和Canceling pending Messages。HandlerThread可參考這篇文章。
解決第二個問題要用到WeakReference
static class MyRunnable implements Runnable {
private WeakReference>View< view;
public MyRunnable(View view) {
this.view = new WeakReference>View<(view);
}
public void run() {
View v = view.get();
if (v != null) {
// ... do something with the view
}
}
}
這樣一來問題就解決了,美中不足的是每次使用view之前都要做空指標判斷。另外一個比較高效的方法就是在onResume中為runnable的view賦值,在onPause中賦值為null。
private static class MyHandler extends Handler {
private TextView view;
public void attach(TextView view) {
this.view = view;
}
public void detach() {
view = null;
}
@Override
public void handleMessage(Message msg) {
// ....
}
總結
在繼承Handler或者HandlerThread的時候,
- 儘量定義一個static類或者top-level類。
- 如果用到了ui元素,一定要在Activity的生命週期接觸之前釋放掉。
參考
Asynchronous Android - Steve Liles