Handler為什么可能會造成內存泄漏以及可用的四種解決方法


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

根據官網的描述

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.

Handler有兩個主要作用:

1.安排調度(scheule)消息和可執行的runnable,可以立即執行,也可以安排在某個將來的時間點執行。

2.讓某一個行為(action)在其他線程中執行。

上面翻譯的意思也就是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(實例)的引用的。

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

現在回到題目的問題上。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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM