如何看待Handler使用中的警告

如何看待Handler使用中的警告

相信很多時候,我們都可以看到自己在使用Handler的過程中,好像得到了一個warning,雖然只是一個警告,但這總是讓強迫症的我們有那麼一絲絲不爽。那麼應該如何解決這樣的警告呢?

如圖可見:

bcaWd.png

可以看到提示信息:

This Handler class should be static or leaks might occur

[這個Handler類應該是靜態的,否則可能會發生內存泄漏]

對於我們嚴謹的開發者,應該使用Handler纔是一種更好的體驗呢?

內存泄漏的可能性原因

爲什麼這樣子使用的時候會可能產生內存泄漏呢?

因爲這樣子的使用過程中,其實我們重寫了Handler的內部方法並生成了一個匿名內部類,而且由於創建匿名內部類或者內部類的實例會默認持有外部類對象的引用

一種情況:當我們在Activity這樣子使用的時候,如果在執行一個耗時任務,當任務未執行完畢的時候,Activity被關閉了,Activity已不再使用,此時如果回收Activity對象,由於子線程未執行完畢,子線程持有Handler的引用,而Handler又持有Activity的引用,這樣導致Activity對象無法被回收,即出現內存泄漏。

另一種情況:同樣是Activity異常關閉,這時候Handler消息傳遞機制中還存在未消耗的Message,也就是MessageQueue中還有Message沒有處理,這個時候也可能會發生內存泄漏,但是這個情況沒有更多的解釋。因爲Handler是基於消息的。每次new 出Handler,都會創建一個消息隊列用於處理你使用handler發送的消息,形如:handler.send***Message。由於消息的發送總是會有先來後到的區別(如果只是這樣都還好,畢竟再慢也不會太久,總歸可以跑完,可能會延遲個幾秒),但是如果你使用的是sendMessageDelayed(Message msg, long delayMillis)或postDelayed(Runnable r, long delayMillis)等發送延遲消息的時候,那基本內存泄漏發生的概率已經在90%以上了。

那麼如何解決這個問題呢?

有一位攻城獅RomainGuy(Android Framework)給出了一些建議。鏈接:

https://groups.google.com/forum/?fromgroups=#%21msg/android-developers/1aPZXZG6kWk/lIYDavGYn5UJ

如下爲截圖部分:

截圖

另外,在androiddesignpatterns上也有人給出瞭解決方案:
http://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html

解決方法主要在於兩點:
1.將Handler聲明爲靜態內部類。因爲靜態內部類不會持有外部類的引用,所以不會導致外部類實例出現內存泄露。
2.在Handler中添加對外部Activity的弱引用。由於Handler被聲明爲靜態內部類,不再持有外部類對象的引用,導致無法在handleMessage()中操作Activity中的對象,所以需要在Handler中增加一個對Activity的弱引用。

改善之後的代碼如下:

private static class MyHandler extends Handler {  
        private final WeakReference<MainActivity> mActivity;  

        public MyHandler(MainActivity activity) {  
            this.mActivity = new WeakReference<MainActivity>(activity);  
        }  

        @Override  
        public void handleMessage(Message msg) {  
            MainActivity mainActivity = mActivity.get();  
            if (mainActivity == null) {  
                return;  
            }  
            // your code here  
        }  
    }  

WeakReference即弱引用,與強引用(即我們常說的“引用”)相對。它的特點是,GC在回收時會忽略掉弱引用,即就算有弱引用指向某對象,但只要該對象沒有被強引用指向,該對象就會在被GC檢查到時回收掉。

改爲採用WeakReference之後,如果任務未執行完但是用戶卻關閉了MainActivity,但由於僅有一個來自MyHandler的弱引用指向MainActivity,所以GC仍然會在檢查時把MainActivity回收掉。這樣,內存泄露的問題就不會出現了。

另一種方案

如果你網上一搜你會看到很多關於弱引用的文章。這確實是一個解決的辦法。其原理就是讓所有在handler裏面使用的對象都變成弱引用,目的就是爲了可以在Android回收內存的時候,可以直接回收掉。我真覺得如果只是寫這種辦法的人,絕對是屬於拷貝黨,因爲這完全是就事論事。你想想就明白,我們寫這個Handler是因爲我們要使用它。怎麼可以通過這種弱引用的辦法去處理這類問題呢?讓JVM想回收就回收?!如果這樣,那我們還需要在使用Bitmap的時候,recycle()幹嘛,還不如直接弄成軟引用得了。

如果你運氣好,你會碰到一些除了寫弱引用這個方法後,還有一個就是handler.removeCallbacksAndMessages(null);,就是移除所有的消息和回調,簡單一句話就是清空了消息隊列。注意,不要以爲你post的是個Runnable或者只是sendEmptyMessage。你可以看一下源碼,在handler裏面都是會把這些轉成正統的Message,放入消息隊列裏面,所以清空隊列就意味着這個Handler直接被打成原型了,當然也就可以回收了。

  所以,我覺得最好的辦法就是你在使用Handler的時候,在外面的Activity或者Fragment中的關閉方法中,如onDestroy中調用一下handler.removeCallbacksAndMessages(null);就可以了,不應該改成軟引用。

示例代碼:

    Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            return false;
        }
    })

最後在外部類的onDestory方法中插入如下代碼:

mHandler.removeCallbacksAndMessages(null);

總結

對比兩種方案,第一種方案需要一個靜態內部類,第二種方案只需要重寫一個Callback接口,而且沒有可能出現內存泄漏的提示,至於那個較好,這個沒有實際的測試,看大家了!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章