天天看點

Android - 看似記憶體洩漏,實則不是,記一次記憶體洩漏的案例分析

  APP中常常會存在記憶體洩漏的問題,一個簡單的測試方法是,多次進入和退出同一頁面(Activity),使用adb shell中的dumpsys meminfo com.android.settings | grep "Activities"來檢視Activity的數量(以com.android.settings為例)。

  如果随着多次進入和退出,Activity的數量一緻在增長,沒有下降,那麼便很大有可能是記憶體洩漏的問題。當然有可能是GC還沒有回收的緣故,如果再顯示地對調用GC回收(DDMS工具的Cause GC按鈕),如果Acitivity的數量仍然沒有降低,那麼機率就更大了。需要從代碼層面進一步分析。

  

  今天遇到的例子就是,通過上述方法,看似遇到了記憶體洩漏,其實不是。

  關鍵點:通過MAT工具和代碼分析,未回收的對象被system_process程序引用,顯示調用system_process GC即可解決問題,不屬于記憶體洩漏。

  案例簡介:在原生Android Open Source Project的Settings APP代碼中,有一個Fragment類叫AccountPreferenceBase,運作在程序com.android.settings中,通過以上方法,發現這個類可能存在記憶體洩漏,于是在重制問題後,借助MAT工具,來分析,得到與此對象相關的引用鍊如下:

Android - 看似記憶體洩漏,實則不是,記一次記憶體洩漏的案例分析

  由上圖可知未被GC回收的AccountPreferenceBase與ContentResolver有關。通過代碼分析,在AccountPreferenceBase中,相關的代碼是如下,

Android - 看似記憶體洩漏,實則不是,記一次記憶體洩漏的案例分析

  進一步分析,在onResume時,調用addStatusChangeListener時,内部會調用RemoteCallbackList的register方法(将callback的binder對象push進一個ArrayMap)。如果不再頁面退出時,及時從ArrayMap中delete掉此binder對象,就會有記憶體洩漏的問題。但是我們在onPause中發現,其實已經調用了removeStatusChangeListener,其内部就會調用unregister方法,從ArrayMap中delete掉正确的binder對象。是以代碼的寫法沒有問題。

   

  那是什麼原因導緻GC沒有回收我們的Activity呢?

  原因就是,此ArrayMap是在system_process程序中,并非在com.android.settings的程序中,delete之後,如果執行一次GC(或者我們顯示地對system_process調用一次GC),那麼對象就會被回收。引用的settings程序中的Activity也會被回收釋放。

  是以在此案例中,記憶體洩漏不存在。

  是以在遇到記憶體洩露的情況時,還是需要根據代碼來具體分析,GC回收的時機不确定,可通過顯示地調用GC來回收對象,排除某些記憶體洩露的可能。當然跨程序時,要調用正确程序的GC來回收。