Handler爲何可能會形成內存泄漏以及可用的四種解決方法

在Android系統中,Handler是一個消息發送和處理機制的核心組件之一,與之配套的其餘主要組件還有Looper和Message,MessageQueue。java

根據官網的描述android

There are two main uses for a Handler: (1) to schedule messages and runnables to be executed at some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.ide

Handler有兩個主要做用:oop

1.安排調度(scheule)消息和可執行的runnable,能夠當即執行,也能夠安排在某個未來的時間點執行。spa

2.讓某一個行爲(action)在其餘線程中執行。.net

上面翻譯的意思也就是Handler主要做爲一種消息收發的機制。線程

這個消息能夠是單純的基本類型,也能夠是某個類,或者一個可執行的行爲(runnable)。Message和Runnable類是消息的載體。MessageQueue是消息等待的隊列。Looper則負責從隊列中取消息。翻譯

在官網描述中,有一段描述很重要:Each Handler instance is associated with a single thread and that thread's message queue。意思是一個Handler的實例和單個的線程和這個線程的MessageQueue相關聯。這得出了一個結論:若是這個MessageQueue中的消息是有某個Handler的instance(實例)的引用的。code

關於這一點,其實不難理解:Looper處理消息Message類的時候,須要調用Handler的handleMessage吧,這就須要知道是哪一個Handler的實例,才能調用Handler.handleMessge()orm

如今回到題目的問題上。Handler爲何可能形成內存泄漏。這裏的內存泄漏,經常指的是泄漏了Activity等組件。可能引發泄漏的操做是這種格式的代碼:

public class TestActivity extends Activity{

    public Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}

注意這是上面的代碼是有問題的代碼,但不必定會引發內存泄漏,只是有可能,泄漏對象的是SampleActivity實例。

這有什麼問題呢。問題在於該Handler的實例採用了內部類的寫法,它是SampleActivity這個實例的內部類,在Java中,關於內部類有一個特色:在java中,非靜態的內部類和匿名內部類都會隱式的持有一個外部類的引用。因此,該handler實例持有了SampleActivity的一個引用。到這裏,是否是有點頭緒了呢。

關於內存泄漏,在android中一個通用的說法是:生命週期較短的組件引用了生命週期較長的組件。Handler就是一種典型的示例,以上面的代碼舉例。SampleActivity可能會被泄漏,也就是該組件沒有用了,好比調用了finish()後,垃圾回收器卻遲遲沒有回收該Activity。緣由出在該實例的handler內部類引用了它,而該handler實例可能被MessageQueue引用着。好比發送了一個延時消息到隊列中,那麼就可能在隊列中存在很長時間,而消息隊列(MessageQueue)的生命週期等於它所在的線程。當大到Activity被finish()了後還在隊列中時,就知足了上面的短生命週期引用長生命週期的條件。根據Java GC的規則,SampleActivity的引用計數不爲0,故不會回收,回收的時機在handler發送的消息出隊列時。

從上面的說法中,能夠思考獲得相應的解決方法:

1.保證Activity被finish()時該線程的消息隊列沒有這個Activity的handler內部類的引用。

2.要麼讓這個handler不持有Activity等外部組件實例,讓該Handler成爲靜態內部類。(靜態內部類是不持有外部類的實例的,於是也就調用不了外部的實例方法了)

3.在2方法的基礎上,爲了能調用外部的實例方法,傳遞一個外部的弱引用進來)

4.將Handler放到一個單獨的頂層類文件中。

最好的方法是哪種呢?其實前三種方法都差很少,第四種若是是一些輕量的操做就太多餘了。不過要說通用性,第三種是最爲通用的。

若是用第一種,其具體的解決方法是當組件銷燬時,在恰當的時機調用handler的removeCallbacksAndMessages(null),若是是在Activity中,則是在onDestroy()的生命週期回調中調用。若是是Activity等具備明確生命週期的組件時能夠這麼作,但要是在自定義的類中,好比一個單例中,每每不能找好釋放的時機。並且開發人員有時會忘記調用remove消息的方法。

若是用第二種,當在handler內部須要調用外部類的非靜態方法時就達不到要求了。由於在Java中,靜態的內部類中不能調用外部非靜態的方法。

第三種,須要一些額外的代碼,但方法最爲通用。

 public class TestActivity extends Activity {

    private static class MyHandler extends Handler {
    private final WeakReference<TestActivity> mActivity;
    public MyHandler(SampleActivity activity) {
      mActivity = new WeakReference<TestActivity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
      TestActivity activity = mActivity.get();
      if (activity != null) {
         //do Something
      }
    }
 }

採用哪一種方法其實都是能夠的,具體看實際狀況。

 

參考資料:

https://developer.android.com/reference/android/os/Handler 官方Reference。

https://blog.csdn.net/lqw_student/article/details/52954837

相關文章
相關標籤/搜索