Handler可能造成內(nèi)存泄漏(四)

Handler可能造成內(nèi)存泄漏(四)

本文原創(chuàng),轉(zhuǎn)載請經(jīng)過本人準(zhǔn)許。

寫在前面:

不知不覺中我們已經(jīng)進(jìn)行了三篇有關(guān)Android消息機(jī)制的研究,溫故知新,我們先來回顧一下:

子線程為何不能更新UI(一)

第一篇中我們探究了,在Android設(shè)計(jì)之時(shí),為何子線程允許更新UI。官方給出的解釋是由于線程安全(Thread Safe)問題。(當(dāng)然也有一些其他方面的猜想)

解決在子線程更新UI崩潰問題(二)

第二篇中,我們總結(jié)了三種崩潰解決的辦法。

  • Activity.runOnUIThread();

  • View.post();

  • Handler;

我們本著尋找最優(yōu)解的思路比較了三種解決辦法,發(fā)現(xiàn)代碼的實(shí)現(xiàn)方式都為Handler,從而真正引出了Android消息機(jī)制,Handler。

帶著這篇去通關(guān)所有Handler的提問(三)

第三篇文章是我們這個(gè)系列的重頭戲,我用一個(gè)還算生動的故事,為大家解釋了Handler和相關(guān)核心類的關(guān)系。
OK,回顧之后,我們正式來開始本篇文章。

內(nèi)存泄漏是怎么一回事?

可以看到,標(biāo)題中有一個(gè)顯眼的名詞,就是內(nèi)存泄漏,我想有必要給初學(xué)的朋友們講講何為內(nèi)存泄漏。

在討論內(nèi)存泄漏之前,先簡單的說說Android中內(nèi)存的回收

Dalivik虛擬機(jī)扮演了常規(guī)的垃圾回收角色,為了GC能夠從App中及時(shí)回收內(nèi)存,我們需要時(shí)時(shí)刻刻在適當(dāng)?shù)臅r(shí)機(jī)來釋放引用對象,Dalvik的GC會自動把離開活動線程的對象進(jìn)行回收。

什么是Android內(nèi)存泄漏

雖然Android是一個(gè)自動管理內(nèi)存的開發(fā)環(huán)境,但是垃圾回收器只會移除那些已經(jīng)失去引用的、不可達(dá)的對象,在十幾萬、幾十萬行代碼中,由于你的失誤使得一個(gè)本應(yīng)該被銷毀的對象仍然被錯誤的持有,那么該對象就永遠(yuǎn)不會被釋放掉,這些已經(jīng)沒有任何價(jià)值的對象,仍然占據(jù)聚集在你的堆內(nèi)存中,GC就會被頻繁觸發(fā),多說幾句,如果手機(jī)不錯,一次GC的時(shí)間70毫秒,不會對應(yīng)用的性能產(chǎn)生什么影響,但是如果一個(gè)手機(jī)的性能不是那么出色,一次GC時(shí)間120毫秒,出現(xiàn)大量的GC操作,我相信用戶就能感覺到了吧。這些無用的引用堆積在堆內(nèi)存中,越積越多最終導(dǎo)致Crash。

有關(guān)一些性能優(yōu)化推薦給大家一個(gè)我總結(jié)的博客。

Android性能優(yōu)化總結(jié)

扯得好像遠(yuǎn)了一點(diǎn),既然明白了內(nèi)存泄漏是怎么回事,那與Handler又有什么關(guān)系呢?

Handler造成的內(nèi)存泄漏

參考一篇外文:

inner-class-handler-memory-leak

使用Handler時(shí),給出的warnning

當(dāng)我們正常使用Handler時(shí),會給出一個(gè)warning提示。翻譯過來就是,這個(gè)Handler類應(yīng)該是靜態(tài)的,否則可能造成內(nèi)存泄漏。

當(dāng)我們簡單的使用Handler的時(shí)候,并不會踩到內(nèi)存泄漏這個(gè)坑,不過當(dāng)Handler作為一個(gè)內(nèi)部類或者匿名類時(shí),這個(gè)問題就可能發(fā)生。

在上篇中,我們知道,當(dāng)Android啟動之時(shí),ActivityThread類中,會創(chuàng)建UI線程的LooperMessageQueue

MessageQueue中的消息會被一個(gè)接一個(gè)處理。應(yīng)用的所有事件(比如Activity生命周期回調(diào)方法,按鈕點(diǎn)擊等等)都會被當(dāng)做一個(gè)消息對象放入到Looper的消息隊(duì)列中,然后再被逐一執(zhí)行。UI線程的Looper存在于整個(gè)應(yīng)用的生命周期內(nèi)。

當(dāng)在UI線程中創(chuàng)建Handler對象時(shí),它就會和UI線程中Looper的消息隊(duì)列進(jìn)行關(guān)聯(lián)。發(fā)送到這個(gè)消息隊(duì)列中的消息會持有這個(gè)Handler的引用,這樣當(dāng)Looper最終處理這個(gè)消息的時(shí)候framework就會調(diào)用Handler#handleMessage(Message)方法來處理具體的邏輯。

在Java中,非靜態(tài)的內(nèi)部類或者匿名類會隱式的持有其外部類的引用,而靜態(tài)的內(nèi)部類則不會。

那么,內(nèi)存到底是在哪里泄露的呢?其實(shí)泄漏發(fā)生的還是比較隱晦的,但是再看看下面這段代碼:

public class SampleActivity extends Activity {

  private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Post a message and delay its execution for 10 minutes.
    mLeakyHandler.postDelayed(new Runnable() {
      @Override
      public void run() { /* ... */ }
    }, 1000 * 60 * 10);

    // Go back to the previous Activity.
    finish();
  }
}

當(dāng)activity被finish的時(shí)候,延遲發(fā)送的消息仍然會存活在UI線程的消息隊(duì)列中,直到10分鐘后它被處理掉。這個(gè)消息持有activity的Handler的引用,Handler又隱式的持有它的外部類(這里就是SampleActivity)的引用。這個(gè)引用會一直存在直到這個(gè)消息被處理,所以垃圾回收機(jī)制就沒法回收這個(gè)activity,內(nèi)存泄露就發(fā)生了。需要注意的是:15行的匿名Runnable子類也會導(dǎo)致內(nèi)存泄露。非靜態(tài)的匿名類會隱式的持有外部類的引用,所以context會被泄露掉。

解決這個(gè)問題也很簡單:在新的類文件中實(shí)現(xiàn)Handler的子類或者使用static修飾內(nèi)部類。靜態(tài)的內(nèi)部類不會持有外部類的引用,所以activity不會被泄露。如果你要在Handler內(nèi)調(diào)用外部activity類的方法的話,可以讓Handler持有外部activity類的弱引用,這樣也不會有泄露activity的風(fēng)險(xiǎn)。關(guān)于匿名類造成的泄露問題,我們可以用static修飾這個(gè)匿名類對象解決這個(gè)問題,因?yàn)殪o態(tài)的匿名類也不會持有它外部類的引用。

public class SampleActivity extends Activity {

  /**
   * Instances of static inner classes do not hold an implicit
   * reference to their outer class.
   */
  private static class MyHandler extends Handler {
    private final WeakReference<SampleActivity> mActivity;

    public MyHandler(SampleActivity activity) {
      mActivity = new WeakReference<SampleActivity>(activity);
    }

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

  private final MyHandler mHandler = new MyHandler(this);

  /**
   * Instances of anonymous classes do not hold an implicit
   * reference to their outer class when they are "static".
   */
  private static final Runnable sRunnable = new Runnable() {
      @Override
      public void run() { /* ... */ }
  };

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Post a message and delay its execution for 10 minutes.
    mHandler.postDelayed(sRunnable, 1000 * 60 * 10);

    // Go back to the previous Activity.
    finish();
  }
}

靜態(tài)和非靜態(tài)內(nèi)部類的區(qū)別是非常微妙的,但這個(gè)區(qū)別是每個(gè)Android開發(fā)者應(yīng)該清楚的。那么底線是什么?如果要實(shí)例化一個(gè)超出activity生命周期的內(nèi)部類對象,避免使用非靜態(tài)的內(nèi)部類。建議使用靜態(tài)內(nèi)部類并且在內(nèi)部類中持有外部類的弱引用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,954評論 25 709
  • 內(nèi)存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題。內(nèi)存泄漏大家都不陌生了,簡單粗俗的講,...
    宇宙只有巴掌大閱讀 2,488評論 0 12
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題。內(nèi)存泄漏...
    _痞子閱讀 1,700評論 0 8
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題。內(nèi)存泄漏...
    apkcore閱讀 1,304評論 2 7
  • 也許你是一個(gè)朝九晚五在整天忙碌的上班族 也許你是一個(gè)背井離鄉(xiāng)在遠(yuǎn)處求學(xué)的學(xué)生黨 也是你是一個(gè)顛沛流離到處流浪的無業(yè)...
    燦鴨子閱讀 227評論 0 0

友情鏈接更多精彩內(nèi)容