1. 程式人生 > >Android 非靜態內部類導致的記憶體洩露(非static內部類)

Android 非靜態內部類導致的記憶體洩露(非static內部類)

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

               
從.class檔案分析非靜態內部類和靜態內部類的區別

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

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個內部類, 一個是普通的非靜態內部類, 另一個是靜態內部類.
用javap -c命令對.class檔案反編譯看下, 注意$前要加上\.
反編譯OuterClass$NormallInnerClass.class :

[email protected]:~/src/browser_6.9.7_forcoopad$ javap -c ./out/production/browser/com/qihoo/browser/OuterClass\$NormallInnerClass.classCompiled from "OuterClass.java"public class com.qihoo.browser.OuterClass$NormallInnerClass {  final com.qihoo.browser.OuterClass this$0public 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 :

[email protected]:~/src/browser_6.9.7_forcoopad$ javap -c ./out/production/browser/com/qihoo/browser/OuterClass\$StaticInnerClass.classCompiled 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實現的. 這也就是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分鐘傳送一個訊息    mLeakyHandler.postDelayed(new Runnable() {      @Override      public void run() { }    }, 60 * 10 * 1000);    // 返回前一個Activity    finish();  }}

上面這段程式碼會導致記憶體洩露,它如何發生的?讓我們確定問題的根源,先寫下我們所知道的.
1、當一個Android應用程式第一次啟動時,Android框架為應用程式的主執行緒建立一個Looper物件。一個Looper實現了一個簡單的訊息佇列,在一個迴圈中處理Message物件。所有主要的應用程式框架事件(如Activity生命週期方法的呼叫,單擊按鈕,等等)都包含在Message物件中,它被新增到Looper的訊息佇列然後一個個被處理。主執行緒的Looper在應用程式的整個生命週期中都存在。
2、當一個Handler在主執行緒中被例項化,它就被關聯到Looper的訊息佇列。每個被髮送到訊息佇列的訊息會持有一個Handler的引用,以便Android框架可以在Looper最終處理這個訊息的時候,呼叫這個Message對應的Handler的handleMessage(Message)。

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

3、在Java中,非靜態的內部類和匿名類會隱式地持有一個他們外部類的引用, 也就是之前提到的final com.qihoo.browser.OuterClass this$0;。靜態內部類則不會。
4、通過handler傳送的runnable物件,會被進一步包裝為message物件,放入訊息佇列.

所以, 對上面的例子來說, 當這個Activity被finished後,延時傳送的訊息會繼續在主執行緒的訊息佇列中存活10分鐘,直到他們被處理。這個message持有handler物件,這個handler物件又隱式持有著SampleActivity物件.直到訊息被處理前,這個handler物件都不會被釋放, 因此SampleActivity也不會被釋放。注意,這個匿名Runnable類物件也一樣。匿名類的非靜態例項持有一個隱式的外部類引用,因此SampleActivity將被洩露。

為了解決這個問題,Handler的子類應該定義在一個新檔案中或使用靜態內部類。靜態內部類不會隱式持有外部類的引用。所以不會導致它的Activity洩露。如果你需要在Handler內部呼叫外部Activity的方法,那麼讓Handler持有一個Activity的弱引用(WeakReference)是正確的解決方案。為了解決我們例項化匿名Runnable類可能導致的記憶體洩露,我們將用一個靜態變數來引用他(因為匿名類的靜態例項不會隱式持有它的外部類的引用)。

public class SampleActivity extends Activity {    /**    * 匿名類的靜態例項不會隱式持有他們外部類的引用    */    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分鐘傳送一個訊息.        mHandler.postDelayed(sRunnable, 60 * 10 * 1000);        // 返回前一個Activity        finish();    }    /**    * 靜態內部類的例項不會隱式持有他們外部類的引用。    */    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



文/ahking17(簡書作者)
原文連結:http://www.jianshu.com/p/6a362ea4dfd8
著作權歸作者所有,轉載請聯絡作者獲得授權,並標註“簡書作者”。
           

給我老師的人工智慧教程打call!http://blog.csdn.net/jiangjunshow

這裡寫圖片描述