Context是如何泄漏的:Handlers和內(nèi)部類(譯)

本文翻譯自:How to Leak a Context: Handlers & Inner Classes

在Android日常編程中怖侦,Handler在進(jìn)行異步操作并處理返回結(jié)果時(shí)經(jīng)常被使用。通常我會(huì)用這樣的代碼實(shí)現(xiàn)

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

其實(shí)不太明顯的是馅扣,上面的代碼可能導(dǎo)致嚴(yán)重的內(nèi)存泄露,Android lint工具會(huì)給你如下警告:

In Android, Handler classes should be static or leaks might occur.

Issue: Ensures that Handler classes do not hold on to a reference to an outer class  
Id: HandlerLeak  
  
Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected.  
If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue.   
If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration,   
as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer   
class and pass this object to your Handler when you instantiate the Handler; Make all references to members   
of the outer class using the WeakReference object.

但是究竟是哪里泄漏了,它又是如何發(fā)生的呢向族?首先讓我們通過(guò)已知的文檔來(lái)探個(gè)究竟:

  1. When an Android application first starts, the framework creates a Looper object for the application’s main thread. A Looper implements a simple message queue, processing Message objects in a loop one after another. All major application framework events (such as Activity lifecycle method calls, button clicks, etc.) are contained inside Message objects, which are added to the Looper’s message queue and are processed one-by-one. The main thread’s Looper exists throughout the application’s lifecycle.
    
  2. When a Handler is instantiated on the main thread, it is associated with the Looper’s message queue. Messages posted to the message queue will hold a reference to the Handler so that the framework can call Handler#handleMessage(Message) when the Looper eventually processes the message.
    
  3. In Java, non-static inner and anonymous classes hold an implicit reference to their outer class. Static inner classes, on the other hand, do not.
    
  • 1.當(dāng)一個(gè)Android應(yīng)用啟動(dòng)的時(shí)候,會(huì)自動(dòng)創(chuàng)建一個(gè)供應(yīng)用主線程使用的Looper實(shí)例说搅。Looper的主要工作就是一個(gè)一個(gè)處理消息隊(duì)列中的消息對(duì)象炸枣。在Android中,所有Android框架的事件(比如Activity的生命周期方法調(diào)用和按鈕點(diǎn)擊等)都是放入到消息中,然后加入到Looper要處理的消息隊(duì)列中适肠,由Looper負(fù)責(zé)一條一條地進(jìn)行處理霍衫。主線程中的Looper生命周期和當(dāng)前應(yīng)用一樣長(zhǎng)。
  • 2.當(dāng)一個(gè)Handler在主線程進(jìn)行了初始化之后侯养,我們發(fā)送一個(gè)target為這個(gè)Handler的消息到Looper處理的消息隊(duì)列時(shí)敦跌,實(shí)際上已經(jīng)發(fā)送的消息已經(jīng)包含了一個(gè)Handler實(shí)例的引用,只有這樣Looper在處理到這條消息時(shí)才可以調(diào)用Handler#handleMessage(Message)完成消息的正確處理逛揩。
  • 3.在Java中柠傍,非靜態(tài)的內(nèi)部類和匿名內(nèi)部類都會(huì)隱式地持有其外部類的引用。靜態(tài)的內(nèi)部類不會(huì)持有外部類的引用辩稽。

所以看出哪里內(nèi)存泄漏了嗎惧笛?它十分巧妙,讓我們一起思考如下示例代碼

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)我們執(zhí)行了Activity的finish方法逞泄,被延遲的消息會(huì)在被處理之前存在于主線程消息隊(duì)列中10分鐘患整,而這個(gè)消息中又包含了Handler的引用,而Handler是一個(gè)匿名內(nèi)部類的實(shí)例喷众,其持有外面的SampleActivity的引用各谚,所以這導(dǎo)致了SampleActivity無(wú)法回收,進(jìn)行導(dǎo)致SampleActivity持有的很多資源都無(wú)法回收到千,這就是我們常說(shuō)的內(nèi)存泄露昌渤。

注意上面的new Runnable這里也是匿名內(nèi)部類實(shí)現(xiàn)的,同樣也會(huì)持有SampleActivity的引用憔四,也會(huì)阻止SampleActivity被回收膀息。

要解決這種問(wèn)題,就不能用非靜態(tài)內(nèi)部類了赵,繼承Handler時(shí)履婉,要么是放在單獨(dú)的類文件中,要么就是使用靜態(tài)內(nèi)部類斟览。因?yàn)殪o態(tài)的內(nèi)部類不會(huì)持有外部類的引用毁腿,所以不會(huì)導(dǎo)致外部類實(shí)例的內(nèi)存泄露。當(dāng)你需要在靜態(tài)內(nèi)部類中調(diào)用外部的Activity時(shí)苛茂,我們可以使用弱引用來(lái)處理已烤。另外關(guān)于同樣也需要將Runnable設(shè)置為靜態(tài)的成員屬性。注意:一個(gè)靜態(tài)的匿名內(nèi)部類實(shí)例不會(huì)持有外部類的引用妓羊。 修改后不會(huì)導(dǎo)致內(nè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();
  }
}

其實(shí)在Android中很多的內(nèi)存泄露都是由于在Activity中使用了非靜態(tài)內(nèi)部類導(dǎo)致的胯究,就像本文提到的一樣,所以當(dāng)我們使用時(shí)要非靜態(tài)內(nèi)部類時(shí)要格外注意躁绸,如果其實(shí)例的持有對(duì)象的生命周期大于其外部類對(duì)象裕循,那么就有可能導(dǎo)致內(nèi)存泄露臣嚣。個(gè)人傾向于使用文章的靜態(tài)類和弱引用的方法解決這種問(wèn)題。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末剥哑,一起剝皮案震驚了整個(gè)濱河市硅则,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌株婴,老刑警劉巖怎虫,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異困介,居然都是意外死亡大审,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門座哩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)徒扶,“玉大人,你說(shuō)我怎么就攤上這事根穷】崂ⅲ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵缠诅,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我乍迄,道長(zhǎng)管引,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任闯两,我火速辦了婚禮褥伴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘漾狼。我一直安慰自己重慢,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布逊躁。 她就那樣靜靜地躺著似踱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪稽煤。 梳的紋絲不亂的頭發(fā)上核芽,一...
    開(kāi)封第一講書(shū)人閱讀 51,146評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音酵熙,去河邊找鬼轧简。 笑死,一個(gè)胖子當(dāng)著我的面吹牛匾二,可吹牛的內(nèi)容都是我干的哮独。 我是一名探鬼主播拳芙,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼皮璧!你這毒婦竟也來(lái)了舟扎?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤恶导,失蹤者是張志新(化名)和其女友劉穎浆竭,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體惨寿,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡邦泄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了裂垦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片顺囊。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蕉拢,靈堂內(nèi)的尸體忽然破棺而出特碳,到底是詐尸還是另有隱情,我是刑警寧澤晕换,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布午乓,位于F島的核電站,受9級(jí)特大地震影響闸准,放射性物質(zhì)發(fā)生泄漏益愈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一夷家、第九天 我趴在偏房一處隱蔽的房頂上張望蒸其。 院中可真熱鬧,春花似錦库快、人聲如沸摸袁。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)靠汁。三九已至,卻和暖如春闽铐,著一層夾襖步出監(jiān)牢的瞬間膀曾,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工阳啥, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留添谊,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓察迟,卻偏偏與公主長(zhǎng)得像斩狱,于是被迫代替她去往敵國(guó)和親耳高。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353