使用Android studio分析內存泄露

jopen 9年前發布 | 123K 次閱讀 Android開發 移動開發 Android Studio

Android使用java作為平臺開發,幫助了我們解決了很多底層問題,比如內存管理,平臺依賴等等。然而,我們也經常遇到OutOfMemoey問題,垃圾回收到底去哪了?

接下來是一個Handler Leak的例子,它一般會在編譯器中被警告提示。

所需要的工具

  • Android Studio 1.1 or higher
  • Eclipse MemoryAnalyzer

示例代碼

public class MainActivity extends ActionBarActivity {

  TextView textView;

  public static final String TAG = NonStaticNestedClassLeakActivity.class.getSimpleName();

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_non_static_nested_class_leak);
    textView = (TextView)findViewById(R.id.textview);
    Handler handler = new Handler();

    handler.postDelayed(new Runnable() {
      @Override public void 
        textView.setText("Done");
      }
    }, 800000L);


  }
}

這是一個非常基礎的Activity.注意這個匿名的Runnable被送到了Handler中,而且延遲非常的長。現在我們運行這個Activity,反復旋轉屏幕,然后導出內存并分析。

導入 Memory 到Eclipse MemoryAnalyzer

使用Androidstudio導出 heap dump

Android Studio dump Memory Analyze
Android Studio dump Memory Analyze
  • 點擊左下角的Android
  • 選中你的程序的包名
  • 點擊 initiates garbage collection on selected vm
  • 點擊 dump java heap for selected client

打開EMA,進行分析

EMA是對java heap中變量分析的一個工具,它可以用于分析內存泄露。

  • 點擊OQL圖標
  • 在窗口輸入select * from instanceof android.app.Activity并按Ctrl + F5或者!按鈕
  • 奇跡出現了,現在你發現泄露了許多的activity
  • 這個真是相當的不容樂觀,我們來分析一下為什么GC沒有回收它
EMA
EMA

在OQL(Object Query Language)窗口下輸入的查詢命令可以獲得所有在內存中的Activities,這段查詢代碼是不是非常簡單高效呢?

點擊一個activity對象,右鍵選中Path to GC roots

GC root
GC root
Message in looper hold a reference to Activity
Message in looper hold a reference to Activity

在打開的新窗口中,你可以發現,你的Activity是被this$0所引用的,它實際上是匿名類對當前類的引用。this$0又被callback所引用,接著它又被Message中一串的next所引用,最后到主線程才結束。

任何情況下你在class中創建非靜態內部類,內部類會(自動)擁有對當前類的一個強引用。

一旦你把Runnable或者Message發送到Handler中,它就會被放入LooperThread的消息隊列,并且被保持引用,直到Message被處理。發送postDelayed這樣的消息,你輸入延遲多少秒,它就會泄露至少多少秒。而發送沒有延遲的消息的話,當隊列中的消息過多時,也會照成一個臨時的泄露。

嘗試使用static inner class來解決

現在把Runnable變成靜態的class

StaticClass
StaticClass

現在,搖一搖手機,導出內存

StaticClass_memory_analyze
StaticClass_memory_analyze

為什么又出現了泄露呢?我們看一看Activities的引用.

StaticClass_memory_analyze_explained
StaticClass_memory_analyze_explained

看到下面的mContext的引用了嗎,它被mTextView引用,這樣說明,使用靜態內部類還遠遠不夠,我們仍然需要修改。

使用弱引用 + static Runnable

現在我們把剛剛內存泄露的罪魁禍首 - TextView改成弱引用。

StaticClassWithWeakRef_code
StaticClassWithWeakRef_code

再次注意我們對TextView保持的是弱引用,現在讓它運行,搖晃手機

小心地操作WeakReferences,它們隨時可以為空,在使用前要判斷是否為空.

StaticClassWithWeakRef_memory_analyze
StaticClassWithWeakRef_memory_analyze

哇!現在只有一個Activity的實例了,這回終于解決了我們的問題。

所以,我們應該記住:

  • 使用靜態內部類
  • Handler/Runnable的依賴要使用弱引用。

如果你把現在的代碼與開始的代碼相比,你會發現它們大不相同,開始的代碼易懂簡介,你甚至可以腦補出運行結果。

而現在的代碼更加復雜,有很多的模板代碼,當把postDelayed設置為一個短時間,比如50ms的情況下,寫這么多代碼就有點虧了。其實,還有一個更簡單的方法。

onDestroy中手動控制聲明周期

Handler可以使用removeCallbacksAndMessages(null),它將移除這個Handler所擁有的Runnable與Message。

//Fixed by manually control lifecycle
  @Override protected void onDestroy() {
    super.onDestroy();
    myHandler.removeCallbacksAndMessages(null);
  }

現在運行,旋轉手機,導出內存

removeCallbacks_memory_analyze
removeCallbacks_memory_analyze

Good!只有一個實例。

這樣寫可以讓你的代碼更加簡潔與可讀。唯一要記住的就是就是要記得在生命周期onDestory的時候手動移除所有的消息。

使用WeakHander

(這個是第三方庫,我就不翻譯了,大家去Github上去學習吧)

結論

在Handler中使用postDelayed需要額外的注意,為了解決問題,我們有三種方法

  • 使用靜態內部Handler/Runnable + 弱引用
  • 在onDestory的時候,手動清除Message
  • 使用Badoo開發的第三方的 WeakHandler

這三種你可以任意選用,第二種看起來更加合理,但是需要額外的工作。第三種方法是我最喜歡的,當然你也要注意WeakHandler不能與外部的強引用共同使用。

來自:http://www.jianshu.com/p/c49f778e7acf

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!