android non-static內部類導致的內存泄露

從.class文件分析非靜態(tài)內部類和靜態(tài)內部類的區(qū)別

我們看一個例子就明白了.

public class OuterClass {

    public class NormallInnerClass {
        public void call() {
            fun();
        }
    }
    public static class StaticInnerClass {
        public void ask() {
//            fun(); //compile error
        }
    }

    public void fun() {

    }
}

在OuterClass中定義了2個內部類朗涩, 一個是普通的非靜態(tài)內部類困介, 另一個是靜態(tài)內部類.
用javap -c命令對.class文件反編譯看下, 注意$前要加上\.
反編譯OuterClass$NormallInnerClass.class :

wangxin@wangxin:~/src/browser_6.9.7_forcoopad$ javap -c ./out/production/browser/com/qihoo/browser/OuterClass\$NormallInnerClass.class
Compiled from "OuterClass.java"
public class com.qihoo.browser.OuterClass$NormallInnerClass {
  final com.qihoo.browser.OuterClass this$0;

  public com.qihoo.browser.OuterClass$NormallInnerClass(com.qihoo.browser.OuterClass);
    Code:
       0: aload_0       
       1: aload_1       
       2: putfield      #1                  // Field this$0:Lcom/qihoo/browser/OuterClass;
       5: aload_0       
       6: invokespecial #2                  // Method java/lang/Object."<init>":()V
       9: return        

  public void call();
    Code:
       0: aload_0       
       1: getfield      #1                  // Field this$0:Lcom/qihoo/browser/OuterClass;
       4: invokevirtual #3                  // Method com/qihoo/browser/OuterClass.fun:()V
       7: return        
}

反編譯OuterClass$StaticInnerClass.class :

wangxin@wangxin:~/src/browser_6.9.7_forcoopad$ javap -c ./out/production/browser/com/qihoo/browser/OuterClass\$StaticInnerClass.class
Compiled from "OuterClass.java"
public class com.qihoo.browser.OuterClass$StaticInnerClass {
  public com.qihoo.browser.OuterClass$StaticInnerClass();
    Code:
       0: aload_0       
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return        

  public void ask();
    Code:
       0: return        
}

對比兩個反編譯的結果, 普通的非static內部類比static內部類多了一個field: final com.qihoo.browser.OuterClass this$0; 在默認的構造方法中骑疆, 用外部類的對象對這個filed賦值.
用intellij idea打開OuterClass$NormallInnerClass.class, 可以看到內部類調用外部類的方法就是通過這個filed實現(xiàn)的. 這也就是static 內部類無法調用外部類普通方法的原因鲫凶,因為static內部類中沒有這個field: final com.qihoo.browser.OuterClass this$0;

package com.qihoo.browser;

import com.qihoo.browser.OuterClass;

public class OuterClass$NormallInnerClass {
    public OuterClass$NormallInnerClass(OuterClass var1) {
        this.this$0 = var1;
    }

    public void call() {
        this.this$0.fun();
    }
}
分析使用new Handler()導致的內存泄露

下面是常見的代碼片段:

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);
 
    // 延時10分鐘發(fā)送一個消息
    mLeakyHandler.postDelayed(new Runnable() {
      @Override
      public void run() { }
    }, 60 * 10 * 1000);
 
    // 返回前一個Activity
    finish();
  }
}

上面這段代碼會導致內存泄露寸爆,它如何發(fā)生的礁鲁?讓我們確定問題的根源,先寫下我們所知道的.
1盐欺、當一個Android應用程序第一次啟動時,Android框架為應用程序的主線程創(chuàng)建一個Looper對象。一個Looper實現(xiàn)了一個簡單的消息隊列,在一個循環(huán)中處理Message對象仅醇。所有主要的應用程序框架事件(如Activity生命周期方法的調用,單擊按鈕,等等)都包含在Message對象中,它被添加到Looper的消息隊列然后一個個被處理冗美。主線程的Looper在應用程序的整個生命周期中都存在。
2析二、當一個Handler在主線程中被實例化,它就被關聯(lián)到Looper的消息隊列粉洼。每個被發(fā)送到消息隊列的消息會持有一個Handler的引用,以便Android框架可以在Looper最終處理這個消息的時候,調用這個Message對應的Handler的handleMessage(Message)叶摄。

public final class Message implements Parcelable {
    ...
    Handler target;
    ...
}

3属韧、在Java中,非靜態(tài)的內部類和匿名類會隱式地持有一個他們外部類的引用, 也就是之前提到的final com.qihoo.browser.OuterClass this$0;蛤吓。靜態(tài)內部類則不會宵喂。
4、通過handler發(fā)送的runnable對象会傲,會被進一步包裝為message對象锅棕,放入消息隊列.

所以, 對上面的例子來說唆铐, 當這個Activity被finished后哲戚,延時發(fā)送的消息會繼續(xù)在主線程的消息隊列中存活10分鐘,直到他們被處理艾岂。這個message持有handler對象顺少,這個handler對象又隱式持有著SampleActivity對象.直到消息被處理前,這個handler對象都不會被釋放, 因此SampleActivity也不會被釋放王浴。注意脆炎,這個匿名Runnable類對象也一樣。匿名類的非靜態(tài)實例持有一個隱式的外部類引用,因此SampleActivity將被泄露氓辣。

為了解決這個問題秒裕,Handler的子類應該定義在一個新文件中或使用靜態(tài)內部類。靜態(tài)內部類不會隱式持有外部類的引用钞啸。所以不會導致它的Activity泄露几蜻。如果你需要在Handler內部調用外部Activity的方法,那么讓Handler持有一個Activity的弱引用(WeakReference)是正確的解決方案体斩。為了解決我們實例化匿名Runnable類可能導致的內存泄露梭稚,我們將用一個靜態(tài)變量來引用他(因為匿名類的靜態(tài)實例不會隱式持有它的外部類的引用)。

public class SampleActivity extends Activity {
    /**
    * 匿名類的靜態(tài)實例不會隱式持有他們外部類的引用
    */
    private static final Runnable sRunnable = new Runnable() {
            @Override
            public void run() {
            }
        };

    private final MyHandler mHandler = new MyHandler(this);

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

        // 延時10分鐘發(fā)送一個消息.
        mHandler.postDelayed(sRunnable, 60 * 10 * 1000);

        // 返回前一個Activity
        finish();
    }

    /**
    * 靜態(tài)內部類的實例不會隱式持有他們外部類的引用絮吵。
    */
    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) {
                // ...
            }
        }
    }
}

一句話弧烤, 都是java語法上隱式持有特性惹的禍,所以我們要對java語法有深入的理解蹬敲, 不能只浮于表面.

refer:
http://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html
http://www.cnblogs.com/kissazi2/p/4121852.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末暇昂,一起剝皮案震驚了整個濱河市莺戒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌急波,老刑警劉巖从铲,帶你破解...
    沈念sama閱讀 210,835評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異幔崖,居然都是意外死亡食店,警方通過查閱死者的電腦和手機渣淤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,900評論 2 383
  • 文/潘曉璐 我一進店門赏寇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人价认,你說我怎么就攤上這事嗅定。” “怎么了用踩?”我有些...
    開封第一講書人閱讀 156,481評論 0 345
  • 文/不壞的土叔 我叫張陵渠退,是天一觀的道長。 經常有香客問我脐彩,道長碎乃,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,303評論 1 282
  • 正文 為了忘掉前任惠奸,我火速辦了婚禮梅誓,結果婚禮上,老公的妹妹穿的比我還像新娘佛南。我一直安慰自己梗掰,他們只是感情好,可當我...
    茶點故事閱讀 65,375評論 5 384
  • 文/花漫 我一把揭開白布嗅回。 她就那樣靜靜地躺著及穗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪绵载。 梳的紋絲不亂的頭發(fā)上埂陆,一...
    開封第一講書人閱讀 49,729評論 1 289
  • 那天,我揣著相機與錄音娃豹,去河邊找鬼焚虱。 笑死,一個胖子當著我的面吹牛培愁,可吹牛的內容都是我干的著摔。 我是一名探鬼主播,決...
    沈念sama閱讀 38,877評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼定续,長吁一口氣:“原來是場噩夢啊……” “哼谍咆!你這毒婦竟也來了禾锤?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,633評論 0 266
  • 序言:老撾萬榮一對情侶失蹤摹察,失蹤者是張志新(化名)和其女友劉穎恩掷,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體供嚎,經...
    沈念sama閱讀 44,088評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡黄娘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,443評論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了克滴。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逼争。...
    茶點故事閱讀 38,563評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖劝赔,靈堂內的尸體忽然破棺而出誓焦,到底是詐尸還是另有隱情,我是刑警寧澤着帽,帶...
    沈念sama閱讀 34,251評論 4 328
  • 正文 年R本政府宣布杂伟,位于F島的核電站,受9級特大地震影響仍翰,放射性物質發(fā)生泄漏赫粥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,827評論 3 312
  • 文/蒙蒙 一予借、第九天 我趴在偏房一處隱蔽的房頂上張望越平。 院中可真熱鬧,春花似錦蕾羊、人聲如沸喧笔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,712評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽书闸。三九已至,卻和暖如春利凑,著一層夾襖步出監(jiān)牢的瞬間浆劲,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,943評論 1 264
  • 我被黑心中介騙來泰國打工哀澈, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留牌借,地道東北人。 一個月前我還...
    沈念sama閱讀 46,240評論 2 360
  • 正文 我出身青樓割按,卻偏偏與公主長得像膨报,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,435評論 2 348

推薦閱讀更多精彩內容