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$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 :
[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
著作權歸作者所有,轉載請聯絡作者獲得授權,並標註“簡書作者”。