LeakCanary介紹
LeakCanary提供了一種很方便的方式,讓我們在開發階段測試記憶體洩露,我們不需要自己根據記憶體塊來分析記憶體洩露的原因,我們只需要在專案中整合他,然後他就會幫我們檢測記憶體洩露,並給出記憶體洩露的引用鏈
整合
- 在gradle中新增依賴,這種不區分debug和release
implementation 'com.squareup.leakcanary:leakcanary-android:1.5.1'
複製程式碼
- 重寫Application
public class App extends Application {
private RefWatcher mRefWatcher;
@Override
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
mRefWatcher = LeakCanary.install(this);
}
public static RefWatcher getRefWatcher(Context context) {
App application = (App) context.getApplicationContext();
return application.mRefWatcher;
}
}
複製程式碼
- 比如我們在Activity中製造Handler發延遲訊息的記憶體洩露
public class ActivityOne extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_one);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
}
}, 100000);
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
複製程式碼
- 然後我們開啟這個activity然後再關閉,桌面就會出現一個leaks的圖示,然後我們開啟它 這就是基本使用,其實這種形式只能監視Activity的記憶體洩露,至於為什麼只能監視Activity的記憶體洩露我們下面再分析,如果想要監視其他的記憶體洩露怎麼辦,比如要監視Fragment的記憶體洩露,可以這樣寫,主動監視想要監視的物件
public abstract class BaseFragment extends Fragment {
@Override public void onDestroy() {
super.onDestroy();
RefWatcher refWatcher = App.getRefWatcher(getActivity());
refWatcher.watch(this);
}
}
複製程式碼
原理概述
通過監聽Activity的onDestory,手動呼叫GC,然後通過ReferenceQueue+WeakReference,來判斷Activity物件是否被回收,然後結合dump Heap的hpof檔案,通過Haha開源庫分析洩露的位置
主要的知識點
註冊Activity的生命週期的監聽器
通過Application.registerActivityLifecycleCallbacks()
方法註冊Activity的生命週期的監聽器,每一個Actvity的生命週期都會回撥到這個ActivityLifecycleCallbacks上,如果一個Activity走到了onDestory,那麼就意味著他就不再存在,然後檢測這個Activity是否是真的被銷燬
通過ReferenceQueue+WeakReference,來判斷物件是否被回收
WeakReference建立時,可以傳入一個ReferenceQueue物件,假如WeakReference中引用物件被回收,那麼就會把WeakReference物件新增到ReferenceQueue中,可以通過ReferenceQueue中是否為空來判斷,被引用物件是否被回收
詳細介紹推薦部落格:www.jianshu.com/p/964fbc301…
MessageQueue中加入一個IdleHandler來得到主執行緒空閒回撥
詳細介紹請看Android Handler 原始碼解析
手動呼叫GC後還呼叫了System.runFinalization();
,這個是強制呼叫已失去引用物件的finalize方法
在可達性演算法中,不可達物件,也不是非死不可,這時他們處於“緩刑”階段,要宣告一個物件真正死亡需要至少倆個標記階段, 如果發現物件沒有引用鏈,則會進行第一次標記,並進行一次篩選,篩選的條件是此物件是否有必要進行finalize()方法,當物件沒有覆蓋finalize(),或者finalize()已經呼叫過,這倆種都視為“沒有必要執行”
Apolication中可通過processName判斷是否是任務執行程式
通過processName,來判斷程式
public static boolean isInServiceProcess(Context context, Class<? extends Service> serviceClass) {
PackageManager packageManager = context.getPackageManager();
PackageInfo packageInfo;
try {
packageInfo = packageManager.getPackageInfo(context.getPackageName(), GET_SERVICES);
} catch (Exception e) {
CanaryLog.d(e, "Could not get package info for %s", context.getPackageName());
return false;
}
String mainProcess = packageInfo.applicationInfo.processName;
ComponentName component = new ComponentName(context, serviceClass);
ServiceInfo serviceInfo;
try {
serviceInfo = packageManager.getServiceInfo(component, 0);
} catch (PackageManager.NameNotFoundException ignored) {
// Service is disabled.
return false;
}
if (serviceInfo.processName.equals(mainProcess)) {
CanaryLog.d("Did not expect service %s to run in main process %s", serviceClass, mainProcess);
// Technically we are in the service process, but we're not in the service dedicated process.
return false;
}
int myPid = android.os.Process.myPid();
ActivityManager activityManager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.RunningAppProcessInfo myProcess = null;
List<ActivityManager.RunningAppProcessInfo> runningProcesses =
activityManager.getRunningAppProcesses();
if (runningProcesses != null) {
for (ActivityManager.RunningAppProcessInfo process : runningProcesses) {
if (process.pid == myPid) {
myProcess = process;
break;
}
}
}
if (myProcess == null) {
CanaryLog.d("Could not find running process for %d", myPid);
return false;
}
return myProcess.processName.equals(serviceInfo.processName);
}
複製程式碼
原始碼分析
SDK初始化
mRefWatcher = LeakCanary.install(this);
複製程式碼
這個是SDK向外暴露的方法,我們以此為入口進行原始碼的分析
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
public static AndroidRefWatcherBuilder refWatcher(Context context) {
return new AndroidRefWatcherBuilder(context);
}
複製程式碼
install
方法首先初始化了一個AndroidRefWatcherBuilder
類,然後通過listenerServiceClass
方法設定了DisplayLeakService
,這個類主要用於分析記憶體洩露的結果資訊,然後傳送通知給使用者
public final class AndroidRefWatcherBuilder extends RefWatcherBuilder<AndroidRefWatcherBuilder> {
/**
* Sets a custom {@link AbstractAnalysisResultService} to listen to analysis results. This
* overrides any call to {@link #heapDumpListener(HeapDump.Listener)}.
*/
public AndroidRefWatcherBuilder listenerServiceClass(
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}
...
}
public class RefWatcherBuilder<T extends RefWatcherBuilder<T>> {
...
/** @see HeapDump.Listener */
public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
this.heapDumpListener = heapDumpListener;
return self();
}
...
}
複製程式碼
然後呼叫excludedRefs
方法新增白名單,在AndroidExcludedRefs
列舉類中定義了忽略列表資訊,如果這些列表中的類發生了記憶體洩露,並不會顯示出來,同時HeapAnalyzer
計算GCRoot強引用路徑,也會忽略這些類,如果你希望自己專案中某個類洩露的,但是不希望他顯示,就可以把類新增到這個上面
public enum AndroidExcludedRefs {
// ######## Android SDK Excluded refs ########
ACTIVITY_CLIENT_RECORD__NEXT_IDLE(SDK_INT >= KITKAT && SDK_INT <= LOLLIPOP) {
@Override void add(ExcludedRefs.Builder excluded) {
excluded.instanceField("android.app.ActivityThread$ActivityClientRecord", "nextIdle")
.reason("Android AOSP sometimes keeps a reference to a destroyed activity as a"
+ " nextIdle client record in the android.app.ActivityThread.mActivities map."
+ " Not sure what's going on there, input welcome.");
}
}
...
}
複製程式碼
最後呼叫了buildAndInstall
方法,建立了一個RefWatcher
物件並返回,這個物件是用於檢測是否有物件未被回收導致的記憶體洩露
/**
* Creates a {@link RefWatcher} instance and starts watching activity references (on ICS+).
*/
public RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
ActivityRefWatcher.install((Application) context, refWatcher);
}
return refWatcher;
}
複製程式碼
因為分析洩露是在另一個程式進行的,所以判斷當前啟動的Application是否在分析記憶體洩露的程式中,如果是就直接返回DISABLED
,不在進行後續初始化,如果發現是在程式主程式中,就進行初始化
LeakCanary.enableDisplayLeakActivity(context);
主要作用是呼叫PackageManager
將DisplayLeakActivity
設定為可用。
public static void enableDisplayLeakActivity(Context context) {
setEnabled(context, DisplayLeakActivity.class, true);
}
public static void setEnabled(Context context, final Class<?> componentClass,
final boolean enabled) {
final Context appContext = context.getApplicationContext();
executeOnFileIoThread(new Runnable() {
@Override public void run() {
setEnabledBlocking(appContext, componentClass, enabled);
}
});
}
public static void setEnabledBlocking(Context appContext, Class<?> componentClass,
boolean enabled) {
ComponentName component = new ComponentName(appContext, componentClass);
PackageManager packageManager = appContext.getPackageManager();
int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
// Blocks on IPC.
packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
}
複製程式碼
從配置檔案看LeakCanary這幾個檔案都是執行在新程式的,DisplayLeakActivity
預設enable=false
,這樣就可以一開始隱藏啟動圖示
<application>
<service
android:name=".internal.HeapAnalyzerService"
android:process=":leakcanary"
android:enabled="false"/>
<service
android:name=".DisplayLeakService"
android:process=":leakcanary"
android:enabled="false"/>
<activity
android:theme="@style/leak_canary_LeakCanary.Base"
android:name=".internal.DisplayLeakActivity"
android:process=":leakcanary"
android:enabled="false"
android:label="@string/leak_canary_display_activity_label"
android:icon="@drawable/leak_canary_icon"
android:taskAffinity="com.squareup.leakcanary.${applicationId}">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:theme="@style/leak_canary_Theme.Transparent"
android:name=".internal.RequestStoragePermissionActivity"
android:process=":leakcanary"
android:taskAffinity="com.squareup.leakcanary.${applicationId}"
android:enabled="false"
android:excludeFromRecents="true"
android:icon="@drawable/leak_canary_icon"
android:label="@string/leak_canary_storage_permission_activity_label"/>
</application>
複製程式碼
接著 ActivityRefWatcher.install((Application) context, refWatcher);
這裡把refWatcher
當做引數傳入,同時對Activity的生命週期進行了監聽
public static void install(Application application, RefWatcher refWatcher) {
new ActivityRefWatcher(application, refWatcher).watchActivities();
}
public void watchActivities() {
// Make sure you don't get installed twice.
stopWatchingActivities();
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}
public void stopWatchingActivities() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
}
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new Application.ActivityLifecycleCallbacks() {
@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override public void onActivityStarted(Activity activity) {
}
@Override public void onActivityResumed(Activity activity) {
}
@Override public void onActivityPaused(Activity activity) {
}
@Override public void onActivityStopped(Activity activity) {
}
@Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override public void onActivityDestroyed(Activity activity) {
ActivityRefWatcher.this.onActivityDestroyed(activity);
}
};
void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
複製程式碼
首先就是註冊Activity的生命週期的監聽器lifecycleCallbacks
,這個監聽器可以監聽專案中所有Activity的的生命週期,然後在Activity銷燬呼叫onActivityDestroyed
的時候,LeakCanary就會獲取這個Activity,然後對其進行分析,看是否有記憶體洩露
分析記憶體洩露
這裡分析物件是否記憶體洩露的是RefWatcher
類,下面簡單介紹一下這個類的成員變數
- WatchExecutor watchExecutor:確保任務在主執行緒進行,同時預設延遲5s執行任務,留時間給系統GC
- DebuggerControl debuggerControl:控制中心
- GcTrigger gcTrigger:內部呼叫
Runtime.getRuntime().gc()
,手動觸發GC - HeapDumper heapDumper:用於建立.hprof檔案,用於儲存head堆快照,可以知道哪些程式在大部分使用記憶體
- HeapDump.Listener heapdumpListener:分析結果完成後會告訴這個監聽器
- ExcludedRefs excludedRefs:分析記憶體洩露的白名單
從上面可以看出,每當Activity銷燬,就會呼叫RefWatcher
的watch
方法,去分析是否是記憶體洩露
public void watch(Object watchedReference) {
watch(watchedReference, "");
}
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
複製程式碼
上面程式碼主要作用是,先生成一個隨機數key放在retainedKeys
容器裡,用來區分物件是否被回收,建立了一個弱引用,然後把要分析的Activity物件存入,然後呼叫了ensureGoneAsync
方法
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
複製程式碼
然後用watchExecutor
去排程分析任務,這個主要是保證,在主執行緒進行,延遲5s,讓系統有時間GC
@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
removeWeaklyReachableReferences();
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
if (gone(reference)) {
return DONE;
}
gcTrigger.runGc();
removeWeaklyReachableReferences();
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
return DONE;
}
private void removeWeaklyReachableReferences() {
// WeakReferences are enqueued as soon as the object to which they point to becomes weakly
// reachable. This is before finalization or garbage collection has actually happened.
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
複製程式碼
首先通過removeWeaklyReachableReferences()
方法,嘗試從弱引用佇列獲取待分析物件,如果不為空說明被系統回收了,就把retainedKeys
中的key值移除,如果被系統回收直接返回DONE,如果沒有被系統回收,就手動呼叫 gcTrigger.runGc();
手動觸發系統gc,然後再次呼叫removeWeaklyReachableReferences()
方法,如過還是為空,則判斷為記憶體洩露
GcTrigger DEFAULT = new GcTrigger() {
@Override public void runGc() {
// Code taken from AOSP FinalizationTest:
// https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
// java/lang/ref/FinalizationTester.java
// System.gc() does not garbage collect every time. Runtime.gc() is
// more likely to perfom a gc.
Runtime.getRuntime().gc();
enqueueReferences();
System.runFinalization();
}
private void enqueueReferences() {
// Hack. We don't have a programmatic way to wait for the reference queue daemon to move
// references to the appropriate queues.
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new AssertionError();
}
}
};
複製程式碼
手動觸發GC後,呼叫enqueueReferences
方法沉睡100ms,給系統GC時間, System.runFinalization();
,這個是強制呼叫已失去引用物件的finalize方法
確定有記憶體洩漏後,呼叫heapDumper.dumpHeap();
生成.hprof
檔案,然後回撥到heapdumpListener
監聽,這個監聽實現是ServiceHeapDumpListener
類,會調analyze()
方法
public final class ServiceHeapDumpListener implements HeapDump.Listener {
...
@Override public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
}
複製程式碼
HeapDump
是一個modle類,裡面用於儲存一些分析類強引用的需要資訊
HeapAnalyzerService.runAnalysis
方法是傳送了一個intent,啟動了HeapAnalyzerService服務,這是一個intentService
public static void runAnalysis(Context context, HeapDump heapDump,
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
Intent intent = new Intent(context, HeapAnalyzerService.class);
intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
intent.putExtra(HEAPDUMP_EXTRA, heapDump);
context.startService(intent);
}
複製程式碼
啟動服務後,會在onHandleIntent
方法啟動分析,找到記憶體洩露的引用關係
@Override
protected void onHandleIntent(Intent intent) {
if (intent == null) {
CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
return;
}
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
複製程式碼
找到引用關係
- 啟動分析記憶體洩露的服務後,會例項化一個
HeapAnalyzer
物件,然後呼叫checkForLeak
方法來分析最終得到的結果, checkForLeak
這裡用到了Square的另一個庫haha,哈哈哈哈哈,名字真的就是叫這個,開源地址:github.com/square/haha…- 得到結果後呼叫
AbstractAnalysisResultService.sendResultToListener()
方法,這個方法啟動了另一個服務
public static void sendResultToListener(Context context, String listenerServiceClassName,
HeapDump heapDump, AnalysisResult result) {
Class<?> listenerServiceClass;
try {
listenerServiceClass = Class.forName(listenerServiceClassName);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
Intent intent = new Intent(context, listenerServiceClass);
intent.putExtra(HEAP_DUMP_EXTRA, heapDump);
intent.putExtra(RESULT_EXTRA, result);
context.startService(intent);
}
複製程式碼
listenerServiceClassName
就是開始LeakCanary.install
方法傳入的DisplayLeakService
,它本身也是一個intentService
@Override
protected final void onHandleIntent(Intent intent) {
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAP_DUMP_EXTRA);
AnalysisResult result = (AnalysisResult) intent.getSerializableExtra(RESULT_EXTRA);
try {
onHeapAnalyzed(heapDump, result);
} finally {
//noinspection ResultOfMethodCallIgnored
heapDump.heapDumpFile.delete();
}
}
複製程式碼
然後呼叫自身的onHeapAnalyzed
方法
protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
String leakInfo = LeakCanary.leakInfo(this, heapDump, result, true);
CanaryLog.d("%s", new Object[]{leakInfo});
boolean resultSaved = false;
boolean shouldSaveResult = result.leakFound || result.failure != null;
if(shouldSaveResult) {
heapDump = this.renameHeapdump(heapDump);
resultSaved = this.saveResult(heapDump, result);
}
PendingIntent pendingIntent;
String contentTitle;
String contentText;
// 設定訊息通知的 pendingIntent/contentTitle/contentText
...
int notificationId1 = (int)(SystemClock.uptimeMillis() / 1000L);
LeakCanaryInternals.showNotification(this, contentTitle, contentText, pendingIntent, notificationId1);
this.afterDefaultHandling(heapDump, result, leakInfo);
}
複製程式碼
這個方法首先判斷是否需要把資訊存到本地,如果需要就存到本地,然後設定訊息通知的基本資訊,最後通過LeakCanaryInternals.showNotification方法呼叫系統的系統通知欄,告訴使用者有記憶體洩露
至此LeakCanary的檢測記憶體洩露原始碼,已經分析完了