前言
因為Android采取了單線程UI模型,開發者無法在子線程中更新UI,為此Android為我們提供了Handler這個工具,可以開發者切換到主線程更新UI。
示例
首先看一段示例代碼
public class LeakCanaryActivity extends AppCompatActivity
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
Message message = Message.obtain();
message.what = 1;
mHandler.sendMessageDelayed(message,10*60*1000);
}
}
這段代碼的邏輯很簡單,mHandler延時了10分鐘發送消息,類似的代碼在我們的項目中也經常出現,但是這樣的代碼會出現一個問題。
問題
我們在項目中集成 Square 的開源庫 LeakCanary,有關這個庫的介紹及使用請看:Github.LeakCanary。
我們首先打開 LeakCanaryActivity ,然后按返回鍵將這個Activity finish 掉。等待幾秒屏幕上會彈出提醒和通知,這說明此時發生了內存泄露的現象。
原因
究竟是什么時候發生了內存泄露的問題呢?
我們知道在Java中,非靜態內部類會隱性地持有外部類的引用,二靜態內部類則不會。在上面的代碼中,Message在消息隊列中延時了10分鐘,然后才處理該消息。而這個消息引用了Handler對象,Handler對象又隱性地持有了Activity的對象,當發生GC是以為 message – handler – acitivity 的引用鏈導致Activity無法被回收,所以發生了內存泄露的問題。
危害
眾所周知,內存泄露在 Android 開發中是一個比較嚴重的問題,系統給每一個應用分配的內存是固定的,一旦發生了內存泄露,就會導致該應用可用內存越來越小,嚴重時會發生 OOM 導致 Force Close。
解決
這個問題該如何解決呢?
-
使用弱引用
首先我們需要理解一下相關概念:
- 強引用:強引用是使用最普遍的引用。如果一個對象具有強引用,那垃圾回收器絕不會回收它。當內存空間不足,Java虛擬機寧愿拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內存不足的問題。
- 軟應用:如果一個對象只具有軟引用,則內存空間足夠,垃圾回收器就不會回收它;如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現內存敏感的高速緩存。
- 弱引用:弱引用與軟引用的區別在于:只具有弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。不過,由于垃圾回收器是一個優先級很低的線程,因此不一定會很快發現那些只具有弱引用的對象。
用更直白的語言描述就是,java對于 強引用 的對象,就絕不收回,對于 軟引用 的對象,是能不收回就不收回,這里的能不收回就是指內存足夠的情況,對于 弱引用 的對象,是發現就收回,但是一般情況下不會發現。
很顯然,出現內存泄露問提的原因,就是 Handler 對 Activity 是強引用,導致 GC 在回收 Activity 時無法回收。為了解決這個問題,我們可以把 Handler 對 Activity 弱引用,這樣 GC 就能把 Activity 及時的回收,從而杜絕了內存泄露的問題。
public class NoLeakActivity extends AppCompatActivity { private NoLeakHandler mHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mHandler = new NoLeakHandler(this); Message message = Message.obtain(); mHandler.sendMessageDelayed(message,10*60*1000); } private static class NoLeakHandler extends Handler{ private WeakReference<NoLeakActivity> mActivity; public NoLeakHandler(NoLeakActivity activity){ mActivity = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); } } }
運行項目,并沒有發生內存泄露的問題。
-
及時清除消息
在原因中我們說到,正是因為被延時處理的 message 持有 Handler 的引用,Handler 持有對 Activity 的引用,形成了message – handler – activity 這樣一條引用鏈,導致 Activity 的泄露。因此我們可以嘗試在當前界面結束時將消息隊列中未被處理的消息清除,從源頭上解除了這條引用鏈,從而使 Activity 能被及時的回收。
public class LeakCanaryActivity extends AppCompatActivity { private Handler mHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); } }; Message message = Message.obtain(); message.what = 1; mHandler.sendMessageDelayed(message,10*60*1000); } @Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null); } }
運行項目,也沒有發生內存泄露的問題。