相信很多人都這樣用過Handler:
Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
//更新UI
//...
super.handleMessage(msg);
}
};
new Thread(new Runnable() {
@Override
public void run() {
//耗時操作
//...
handler.sendEmptyMessage(0);
}
}).start();
很常見的一段段碼,對沒錯,但是確實發生了內存泄漏。假如當在處理耗時操作的時候,可能是某段網絡請求,也有可能是很復雜的計算,這時用戶等的不耐煩了退出當前界面,由于異步操作,此時雖然界面銷毀了,但是任務還在繼續,由于線程中,引用這當前Activity中的對象handler,而handler對象又隱式的應用了當前的Activity,因此雖然界面銷毀了,但是Activity并不會被回收。當任務執行完后,調用了handler方法,方法中又調用了更新界面,就會出現空指針異常崩潰。這算是最好的情況了,更嚴重的是由于Activity無法被銷毀,那么就是占著那個地方不做事,這就是所謂的站著茅坑不拉屎。那么這塊資源便浪費了,當用戶來回反復切換的時候,資源占用越來越大,最終就算不空指針異常 也會出現OOM,這是由于內存泄漏導致的。
內存泄露的危害
只有一個,那就是虛擬機占用內存過高,導致OOM(內存溢出),程序出錯。對于Android應用來說,就是你的用戶打開一個Activity,使用完之后關閉它,內存泄露;又打開,又關閉,又泄露;幾次之后,程序占用內存超過系統限制。
解決方案
- 在關閉Activity的時候停掉你的后臺線程任務。線程停掉了,就相當于切斷了Handler和外部連接的線,Activity自然會在合適的時候被回收。
- 如果你的Handler是被delay的Message持有了引用,那么使用相應的Handler的removeCallbacks()方法,把消息對象從消息隊列移除就行了。
- 我們換一種寫法,將handler對象聲明成靜態內部類,靜態類不持有外部類的對象,所以你的Activity可以隨意被回收。
private TestHandler testHandler=new TestHandler();
static class TestHandler extends Handler{
@Override
public void handleMessage(Message msg) {
//更新界面操作
//...
super.handleMessage(msg);
}
}
很快你會發現編譯無法通過。因為上面說過了。靜態類是不持有外部類的對象的,因此如果你在里面寫了更新界面的操作當然會編譯無法通過。解決辦法當然有,就在靜態類中在增加一個外部類的弱引用。
static class MyHandler extends Handler{
private WeakReference<SearchContantActivity> mContext;
public MyHandler(SearchContantActivity activity){
mContext=new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
if(msg.what==0){
SearchContantActivity activity=mContext.get();
if(activity!=null){
activity.xxx
}
}
super.handleMessage(msg);
}
}
但是這種仍然會出現異常,當你的界面退出后,界面的控件都會被銷毀了,此時異步任務執行完,觸發了handler發送消息,這時handler仍然會受到,從弱引用中拿到的Activity還是存在的,這時候你在去更新界面,那么理所當然就發生了NullPointException.所以最好的辦法就是界面在正常發送handler,界面銷毀就不發送handler,徹底切斷它與Activity的聯系,那么就一勞永逸了。