Handler記憶體洩露分析與解決方案
一、記憶體洩露分析
內部類會有一個指向外部類的引用。
垃圾回收機制中約定,當記憶體中的一個物件的引用計數為0時,將會被回收。
Handler 作為 Android 上的非同步訊息處理機制(好吧,我大多用來進行 worker thread 與 UI 執行緒同步),它的工作是需要 Looper 和 MessageQueue 配合的。簡單的說,要維護一個迴圈體(Looper)處理訊息佇列(MessageQueue)。每迴圈一次就從 MessageQueue 中取出一個 Message,然後回撥相應的訊息處理函式。
如果,我是說如果,迴圈體中有訊息未處理(Message 排隊中),那麼 Handler 會一直存在,那麼 Handler 的外部類(通常是 Activity)的引用計數一直不會是0,所以那個外部類就不能被垃圾回收。很多人會遇到 activity的onDestroy方法一直不執行就是這個原因。
一旦 Handler 被宣告為內部類,那麼可能導致它的外部類不能夠被垃圾回收。如果 Handler 是在其他執行緒(我們通常成為 worker thread)使用 Looper 或 MessageQueue(訊息佇列),而不是 main 執行緒(UI 執行緒),那麼就沒有這個問題。如果 Handler 使用 Looper 或 MessageQueue 在主執行緒(main thread),你需要對 Handler 的宣告做如下修改:
宣告 Handler 為 static 類;在外部類中例項化一個外部類的 WeakReference(弱引用)並且在 Handler 初始化時傳入這個物件給你的 Handler;將所有引用的外部類成員使用 WeakReference 物件。
二、解決方案
解決方法(一)
宣告 Handler 為 static 類;在外部類中例項化一個外部類的 WeakReference(弱引用)並且在 Handler 初始化時傳入這個物件給你的 Handler;將所有引用的外部類成員使用 WeakReference 物件。
public class MethodSolveOne extends AppCompatActivity { private static final String TAG = "MethodSolveOne"; private CopyFileHandler mHandler; private TextView mDisplayTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.method_solve_one); mDisplayTextView = (TextView)findViewById(R.id.display_text_view); mHandler = new CopyFileHandler(this); startCopyFileThread(); } private void startCopyFileThread() { Log.d(TAG, "startCopyFileThread"); new Thread(new Runnable() { @Override public void run() { //DO SOMETHING LIKE: copyDBFile(); Message msg = mHandler.obtainMessage(); msg.what = 1; mHandler.sendMessage(msg); } }).start(); } private void changeDisplayTextView(String str){ mDisplayTextView.setText(str); } private static class CopyFileHandler extends Handler { WeakReference<MethodSolveOne> mActivity; public CopyFileHandler(MethodSolveOne activity) { mActivity = new WeakReference<>(activity); } public void handleMessage(Message msg) { final MethodSolveOne activity = mActivity.get(); switch (msg.what){ case 1: activity.changeDisplayTextView("I have changed!!!"); } } } }
解決方法(二)
在worker thread 中使用 Looper 或 MessageQueue
private Handler testHandler;
private Thread mThread = new Thread() {
public void run() {
Log.d(TAG, "mThread run");
Looper.prepare();
testHandler = new Handler() {
public void handleMessage(Message msg) {
Log.d("TAG", "worker thread:" + Thread.currentThread().getName());
switch (msg.what) {
//handle message here
}
}
};
Looper.loop();
}
};
//start thread here
if(Thread.State.NEW==mThread.getState())
{
Log.d(TAG, "mThread name: " + mThread.getName());
mThread.start();
}
//send message here
testHandler.sendEmptyMessage(1);