android non-static內(nèi)部類(lèi)導(dǎo)致的內(nèi)存泄露

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

我們看一個(gè)例子就明白了.

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個(gè)內(nèi)部類(lèi), 一個(gè)是普通的非靜態(tài)內(nèi)部類(lèi), 另一個(gè)是靜態(tài)內(nèi)部類(lèi).
用javap -c命令對(duì).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        
}

對(duì)比兩個(gè)反編譯的結(jié)果, 普通的非static內(nèi)部類(lèi)比static內(nèi)部類(lèi)多了一個(gè)field: final com.qihoo.browser.OuterClass this$0; 在默認(rèn)的構(gòu)造方法中, 用外部類(lèi)的對(duì)象對(duì)這個(gè)filed賦值.
用intellij idea打開(kāi)OuterClass$NormallInnerClass.class, 可以看到內(nèi)部類(lèi)調(diào)用外部類(lèi)的方法就是通過(guò)這個(gè)filed實(shí)現(xiàn)的. 這也就是static 內(nèi)部類(lèi)無(wú)法調(diào)用外部類(lèi)普通方法的原因,因?yàn)閟tatic內(nèi)部類(lèi)中沒(méi)有這個(gè)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()導(dǎo)致的內(nèi)存泄露

下面是常見(jiàn)的代碼片段:

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

上面這段代碼會(huì)導(dǎo)致內(nèi)存泄露,它如何發(fā)生的?讓我們確定問(wèn)題的根源,先寫(xiě)下我們所知道的.
1、當(dāng)一個(gè)Android應(yīng)用程序第一次啟動(dòng)時(shí),Android框架為應(yīng)用程序的主線程創(chuàng)建一個(gè)Looper對(duì)象。一個(gè)Looper實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的消息隊(duì)列,在一個(gè)循環(huán)中處理Message對(duì)象。所有主要的應(yīng)用程序框架事件(如Activity生命周期方法的調(diào)用,單擊按鈕,等等)都包含在Message對(duì)象中,它被添加到Looper的消息隊(duì)列然后一個(gè)個(gè)被處理。主線程的Looper在應(yīng)用程序的整個(gè)生命周期中都存在。
2、當(dāng)一個(gè)Handler在主線程中被實(shí)例化,它就被關(guān)聯(lián)到Looper的消息隊(duì)列。每個(gè)被發(fā)送到消息隊(duì)列的消息會(huì)持有一個(gè)Handler的引用,以便Android框架可以在Looper最終處理這個(gè)消息的時(shí)候,調(diào)用這個(gè)Message對(duì)應(yīng)的Handler的handleMessage(Message)。

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

3、在Java中,非靜態(tài)的內(nèi)部類(lèi)和匿名類(lèi)會(huì)隱式地持有一個(gè)他們外部類(lèi)的引用, 也就是之前提到的final com.qihoo.browser.OuterClass this$0;。靜態(tài)內(nèi)部類(lèi)則不會(huì)。
4、通過(guò)handler發(fā)送的runnable對(duì)象,會(huì)被進(jìn)一步包裝為message對(duì)象,放入消息隊(duì)列.

所以, 對(duì)上面的例子來(lái)說(shuō), 當(dāng)這個(gè)Activity被finished后,延時(shí)發(fā)送的消息會(huì)繼續(xù)在主線程的消息隊(duì)列中存活10分鐘,直到他們被處理。這個(gè)message持有handler對(duì)象,這個(gè)handler對(duì)象又隱式持有著SampleActivity對(duì)象.直到消息被處理前,這個(gè)handler對(duì)象都不會(huì)被釋放, 因此SampleActivity也不會(huì)被釋放。注意,這個(gè)匿名Runnable類(lèi)對(duì)象也一樣。匿名類(lèi)的非靜態(tài)實(shí)例持有一個(gè)隱式的外部類(lèi)引用,因此SampleActivity將被泄露。

為了解決這個(gè)問(wèn)題,Handler的子類(lèi)應(yīng)該定義在一個(gè)新文件中或使用靜態(tài)內(nèi)部類(lèi)。靜態(tài)內(nèi)部類(lèi)不會(huì)隱式持有外部類(lèi)的引用。所以不會(huì)導(dǎo)致它的Activity泄露。如果你需要在Handler內(nèi)部調(diào)用外部Activity的方法,那么讓Handler持有一個(gè)Activity的弱引用(WeakReference)是正確的解決方案。為了解決我們實(shí)例化匿名Runnable類(lèi)可能導(dǎo)致的內(nèi)存泄露,我們將用一個(gè)靜態(tài)變量來(lái)引用他(因?yàn)槟涿?lèi)的靜態(tài)實(shí)例不會(huì)隱式持有它的外部類(lèi)的引用)。

public class SampleActivity extends Activity {
    /**
    * 匿名類(lèi)的靜態(tài)實(shí)例不會(huì)隱式持有他們外部類(lè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);

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

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

    /**
    * 靜態(tài)內(nèi)部類(lèi)的實(shí)例不會(huì)隱式持有他們外部類(lè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語(yǔ)法上隱式持有特性惹的禍,所以我們要對(duì)java語(yǔ)法有深入的理解, 不能只浮于表面.

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

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

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

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