轉(zhuǎn)自我的csdn博客迫卢,轉(zhuǎn)載請(qǐng)注明出處。
Leakcanary是square推出的內(nèi)存泄露分析工具邓梅,使用很簡(jiǎn)單脱盲,可謂“傻瓜式”應(yīng)用。
但其內(nèi)部原理實(shí)現(xiàn)直的深究學(xué)習(xí)日缨,今天我們就層層剖析其使用方式钱反、源碼實(shí)現(xiàn),了解一下大牛是如何寫代碼的匣距。
前期知識(shí)點(diǎn)準(zhǔn)備:內(nèi)存泄漏 GC回收原理 java四種引用等等面哥。
一、使用方法
1毅待、gradle中添加依賴尚卫,目前最新版本為1.6.1
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'//debug版本
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'//發(fā)布版本,將該工具失效尸红,避免影響線上業(yè)務(wù)
2吱涉、Application類中調(diào)用 LeakCanary.install(this);即可以開啟該工具監(jiān)控Activity內(nèi)存泄漏刹泄,其他對(duì)象fragmen、service等其他對(duì)象的監(jiān)控需要在對(duì)應(yīng)類的中調(diào)用refWatcher.watch(this);
public class MyApplacition extends Application {
? ? static MyApplacition instance;
? ? private RefWatcher refWatcher;
? ? public static RefWatcher getRefWatcher(Context context) {
? ? ? ? return instance.refWatcher;
? ? }
? ? @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;
? ? ? ? }
? ? ? ? LeakCanary.install(this);
public class MyFragment extends Fragment {
? ? @Override
? ? public void onDestroy() {
? ? ? ? super.onDestroy();
? ? ? ? RefWatcher refWatcher = Myapplacition.getRefWatcher(getActivity());
? ? ? ? refWatcher.watch(this);
//? ? ? RefWatcher refWatcher = LeakCanary.installedRefWatcher();//1.6.1版本中提供該方法獲取refWatcher對(duì)象怎爵,之前的版本需要按上面方法自行獲取循签。另,android8.0以上工具中添加了fragment的生命周期監(jiān)測(cè)疙咸,不需要再添加此處代碼县匠。
? ? }
二、源碼分析
在講源碼之前撒轮,先上一段栗子乞旦,
void refTest(){
? ? A a = new A();//a為強(qiáng)引用
? ? ReferenceQueue queue = new ReferenceQueue();
? ? WeakReference aa = new WeakReference(a, queue);//aa為弱引用
? ? a = null;
? ? Runtime.getRuntime().gc();//通知系統(tǒng)GC
? ? System.runFinalization();//強(qiáng)制系統(tǒng)回收已經(jīng)沒有強(qiáng)引用的對(duì)象
? ? Reference poll = null;
? ? while ((poll = queue.poll()) != null) {
? ? ? ? Log.i(TAG,"Reference"+poll.toString());
? ? }
}
以上這段代碼中,強(qiáng)引用a置為null题山,則A對(duì)象只有aa這個(gè)弱引用存在兰粉。之后手動(dòng)觸發(fā)GC,log中可以看到aa的弱引用已經(jīng)放到了引用隊(duì)列中顶瞳,說明A對(duì)象已經(jīng)被回收玖姑。Leakcanary就是(1)利用此原理初步定位內(nèi)存泄漏對(duì)象后,(2)再調(diào)用系統(tǒng)接口dump出堆轉(zhuǎn)儲(chǔ)文件快照.hprof慨菱,(3)調(diào)用haha庫(kù)分析該文件解析出最短引用路徑焰络,(4)提示給用戶的。
如圖所示
1符喝、初步定位內(nèi)存泄漏對(duì)象
(1)//在調(diào)用LeakCanary的intsall方法之后闪彼,會(huì)調(diào)用buildAndInstall()生成refWatcher對(duì)象
public RefWatcher buildAndInstall() {
? if (LeakCanaryInternals.installedRefWatcher != null) {
? ? throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
? }
? RefWatcher refWatcher = build();//構(gòu)造模式,獲取RefWatcher對(duì)象
? if (refWatcher != DISABLED) {
? ? if (watchActivities) {//監(jiān)控activity协饲,默認(rèn)為true
? ? ? ActivityRefWatcher.install(context, refWatcher);
? ? }
? ? if (watchFragments) {//監(jiān)控fragment畏腕,默認(rèn)為true
? ? ? FragmentRefWatcher.Helper.install(context, refWatcher);
? ? }
? }
? LeakCanaryInternals.installedRefWatcher = refWatcher;
? return refWatcher;
}
(2)//refWatcher開始監(jiān)控Activity、Fragment等對(duì)象
public final class ActivityRefWatcher {
? public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
? ? install(application, refWatcher);
? }
? public static void install(Context context, RefWatcher refWatcher) {
? ? Application application = (Application) context.getApplicationContext();
? ? ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
? ? application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
? }
? private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
? ? ? new ActivityLifecycleCallbacksAdapter() {
? ? ? ? @Override public void onActivityDestroyed(Activity activity) {
? ? ? ? ? refWatcher.watch(activity);
? ? ? ? }
? ? ? };
public interface FragmentRefWatcher {
? void watchFragments(Activity activity);
? final class Helper {
? ? private static final String SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME =
? ? ? ? "com.squareup.leakcanary.internal.SupportFragmentRefWatcher";
? ? public static void install(Context context, RefWatcher refWatcher) {
ListfragmentRefWatchers = new ArrayList<>();
? ? ? if (SDK_INT >= O) {
? ? ? ? fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
? ? ? }
(3)//Android8.0以上引入了fragment的生命周期茉稠,用戶不需要在onDestroy中自行調(diào)用
@RequiresApi(Build.VERSION_CODES.O) //
class AndroidOFragmentRefWatcher implements FragmentRefWatcher {
? private final RefWatcher refWatcher;
? AndroidOFragmentRefWatcher(RefWatcher refWatcher) {
? ? this.refWatcher = refWatcher;
? }
? private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
? ? ? new FragmentManager.FragmentLifecycleCallbacks() {
? ? ? ? @Override
? ? ? ? public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
? ? ? ? ? refWatcher.watch(fragment);
? ? ? ? }
? ? ? };
(4)//生成唯一key標(biāo)識(shí)對(duì)象描馅,并建立該對(duì)象的弱引用關(guān)聯(lián)到引用隊(duì)列,如上述栗子中所示而线。
//其中retainedKeys為CopyOnWriteArraySet類型铭污,解決并發(fā)讀寫問題
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);
}
(5)//watchExecutor子線程中進(jìn)行分析
? private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
? ? watchExecutor.execute(new Retryable() {
? ? ? @Override public Retryable.Result run() {
? ? ? ? return ensureGone(reference, watchStartNanoTime);
? ? ? }
? ? });
? }
(6)//初步分析定位出泄漏對(duì)象
? @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();//retainedKeys中,移除未泄露的對(duì)象(弱引用已被GC添加到引用隊(duì)列的對(duì)象)
? ? if (debuggerControl.isDebuggerAttached()) {
? ? ? // The debugger can create false leaks.
? ? ? return RETRY;
? ? }
? ? if (gone(reference)) {//未泄露則返回吞获,結(jié)束此次分析
? ? ? return DONE;
? ? }
? ? gcTrigger.runGc();//手動(dòng)GC
? ? removeWeaklyReachableReferences();//再次移除未泄漏對(duì)象
? ? if (!gone(reference)) {//初步確認(rèn)該對(duì)象內(nèi)存泄漏
? ? ? long startDumpHeap = System.nanoTime();
? ? ? long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
? ? ? File heapDumpFile = heapDumper.dumpHeap();//生成hprof文件
? ? ? if (heapDumpFile == RETRY_LATER) {
? ? ? ? // Could not dump the heap.
? ? ? ? return RETRY;
? ? ? }
? ? ? long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
? ? ? HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
? ? ? ? ? .referenceName(reference.name)
? ? ? ? ? .watchDurationMs(watchDurationMs)
? ? ? ? ? .gcDurationMs(gcDurationMs)
? ? ? ? ? .heapDumpDurationMs(heapDumpDurationMs)
? ? ? ? ? .build();
? ? ? heapdumpListener.analyze(heapDump);//分析hprof文件
? ? }
? ? return DONE;
? }
? private boolean gone(KeyedWeakReference reference) {
? ? return !retainedKeys.contains(reference.key);
? }
? 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);
? ? }
? }
2况凉、調(diào)用系統(tǒng)接口dump出堆轉(zhuǎn)儲(chǔ)文件快照.hprof
@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
@Override public File dumpHeap() {
? File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
? if (heapDumpFile == RETRY_LATER) {
? ? return RETRY_LATER;
? }
FutureResultwaitingForToast = new FutureResult<>();
? showToast(waitingForToast);//在開始生成hprof文件之前谚鄙,在主線程中顯示toast各拷,如附圖
? if (!waitingForToast.wait(5, SECONDS)) {//等待主線程完成toast的展示
? ? CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
? ? return RETRY_LATER;
? }
? Notification.Builder builder = new Notification.Builder(context)
? ? ? .setContentTitle(context.getString(R.string.leak_canary_notification_dumping));
? Notification notification = LeakCanaryInternals.buildNotification(context, builder);
? NotificationManager notificationManager =
? ? ? (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
? int notificationId = (int) SystemClock.uptimeMillis();
? notificationManager.notify(notificationId, notification);
? Toast toast = waitingForToast.get();
? try {
? ? Debug.dumpHprofData(heapDumpFile.getAbsolutePath());//調(diào)用系統(tǒng)方法生成.hprof文件
? ? cancelToast(toast);
? ? notificationManager.cancel(notificationId);
? ? return heapDumpFile;
? } catch (Exception e) {
? ? CanaryLog.d(e, "Could not dump heap");
? ? // Abort heap dump
? ? return RETRY_LATER;
? }
}
private void showToast(final FutureResultwaitingForToast) {
? mainHandler.post(new Runnable() {
? ? @Override public void run() {
? ? ? final Toast toast = new Toast(context);
? ? ? toast.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
? ? ? toast.setDuration(Toast.LENGTH_LONG);
? ? ? LayoutInflater inflater = LayoutInflater.from(context);
? ? ? toast.setView(inflater.inflate(R.layout.leak_canary_heap_dump_toast, null));
? ? ? show(toast);
? ? ? // Waiting for Idle to make sure Toast gets rendered.
? ? ? Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
? ? ? ? @Override public boolean queueIdle() {
? ? ? ? ? waitingForToast.set(toast);
? ? ? ? ? return false;
? ? ? ? }
? ? ? });
? ? }
? });
}
3、調(diào)用haha庫(kù)分析該文件解析出最短引用路徑
和MAT分析.hprof文件類似闷营,此處調(diào)用haha開源庫(kù)分析出最短引用路徑烤黍。
haha庫(kù)的github路徑https://github.com/square/haha知市。
4、提示用戶
將分析出的結(jié)果最終通過DisplayLeakService在狀態(tài)欄提示展示出來速蕊,通知用戶嫂丙。點(diǎn)擊通知調(diào)起DisplayLeakActivity顯示泄露信息,即最短引用路徑规哲。這兩給類是另開進(jìn)程的跟啤,在桌面上可以看到leakcanry的圖標(biāo)就是在DisplayLeakActivity中配置的,至于另開進(jìn)程的原因唉锌,應(yīng)該是為了避免占用主應(yīng)用的內(nèi)存隅肥。
三、示例
1袄简、線程泄露
在ativity中調(diào)用此方法腥放,然后按返回鍵結(jié)束activity。
void startAsyncWork() {
? ? // This runnable is an anonymous class and therefore has a hidden reference to the outer
? ? // class MainActivity. If the activity gets destroyed before the thread finishes (e.g. rotation),
? ? // the activity instance will leak.
? ? Runnable work = new Runnable() {
? ? ? ? @Override public void run() {
? ? ? ? ? ? // Do some slow work in background
? ? ? ? ? ? SystemClock.sleep(20000);
? ? ? ? }
? ? };
? ? Thread thread=new Thread(work);
? ? thread.start();
}
測(cè)試結(jié)果如下:
結(jié)果中顯示MainActivity存在泄漏绿语,原因是被this指針引用秃症,this指針就是java中非靜態(tài)內(nèi)部類對(duì)外部類默認(rèn)的引用,this指針被thread持有吕粹,而thread里面有耗時(shí)任務(wù)种柑。所以在Activity結(jié)束的時(shí)候,因?yàn)閠hread的耗時(shí)任務(wù)沒有執(zhí)行完匹耕,導(dǎo)致Activity不能銷毀莹规,出現(xiàn)泄漏。
2泌神、handler泄露
Activity中良漱,直接new handler發(fā)送延遲消息,出現(xiàn)泄漏欢际。改為弱引用母市,則不出現(xiàn)泄漏,原理同上面栗子损趋。
public class HandlerActivity extends Activity {
? ? @Override
? ? protected void onCreate(Bundle savedInstanceState) {
? ? ? ? super.onCreate(savedInstanceState);
? ? ? ? setContentView(R.layout.handlerac_layout);
? ? ? ? handler.sendEmptyMessageDelayed(0, 10 * 60 * 1000);
//? ? ? ? new WeakHandler(this).sendEmptyMessageDelayed(0, 10 * 60 * 1000);
? ? ? ? findViewById(R.id.gc).setOnClickListener(new View.OnClickListener() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public void onClick(View view) {
? ? ? ? ? ? ? ? finish();
? ? ? ? ? ? }
? ? ? ? });
? ? }
? ? Handler handler = new Handler(){
? ? ? ? @Override
? ? ? ? public void handleMessage(Message msg) {
? ? ? ? ? ? super.handleMessage(msg);
? ? ? ? ? ? Log.i("HandlerActivity","接收消息") ;
? ? ? ? }
? ? };
? ? private static class WeakHandler extends Handler {
WeakReferenceweakReference;
? ? ? ? public WeakHandler(HandlerActivity activity) {
weakReference = new WeakReference(activity);
? ? ? ? }
? ? ? ? @Override
? ? ? ? public void handleMessage(Message msg) {
? ? ? ? ? ? HandlerActivity activity = weakReference.get();
? ? ? ? ? ? if (activity != null && activity.tvHandler != null) {
? ? ? ? ? ? ? ? Log.i("HandlerActivity","接收消息") ;
? ? ? ? ? ? }
? ? ? ? }
? ? }
測(cè)試結(jié)果如下:
this指針被Message.target即handler對(duì)象持有患久,在new handler時(shí)會(huì)關(guān)聯(lián)到當(dāng)前線程的looper,而looper創(chuàng)建了messagequeue對(duì)象浑槽,所以handler最終被messagequeue持有蒋失。栗子中發(fā)送了延時(shí)任務(wù),所以Activity結(jié)束時(shí)桐玻,因?yàn)镸essageQueue隊(duì)列中的消息沒有結(jié)束篙挽,導(dǎo)致Activity泄漏。
3镊靴、單例泄漏
4铣卡、io資源未關(guān)閉
5链韭、注冊(cè)未反注冊(cè)
6、靜態(tài)activity煮落、靜態(tài)view等
其他栗子類似敞峭,也是常見的一些泄漏問題。
四蝉仇、其他
leakcanar中一些比較好的用法旋讹,也值得我們借鑒一下
1、自定義結(jié)果處理
使用中可以繼承DisplayLeakService 自定義分析結(jié)果處理方式轿衔,比如上傳服務(wù)器等骗村,只需要將自定義的sevice傳到rewatcher對(duì)象中,因?yàn)槠鋮?shù)類型設(shè)計(jì)為邊界上限的泛型Class listenerServiceClass呀枢,在開發(fā)中可以借鑒這種方式胚股,提升代碼的擴(kuò)展性。
//繼承類
public class LeakUploadService extends DisplayLeakService {
? ? static final String TAG="ReferenceQueue";
? ? @Override
? ? protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
? ? ? ? if (!result.leakFound || result.excludedLeak) {
? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? Log.i(TAG,"leakInfo"+leakInfo);
//傳參
RefWatcher refWatcher = LeakCanary.refWatcher(this)
? ? ? ? .listenerServiceClass(LeakUploadService.class)
? ? ? ? .buildAndInstall();
2裙秋、多線程同步
源碼中的showtoast方法琅拌,使用了CountDownLatch實(shí)現(xiàn)主線程和子線程之間的同步。
public final class FutureResult{
private final AtomicReferenceresultHolder;
? private final CountDownLatch latch;
? public FutureResult() {
resultHolder = new AtomicReference<>();
? ? latch = new CountDownLatch(1);//子線程等待主線程展示toast摘刑,計(jì)數(shù)為1
? }
? public boolean wait(long timeout, TimeUnit unit) {
? ? try {
? ? ? return latch.await(timeout, unit);//計(jì)數(shù)結(jié)束
? ? } catch (InterruptedException e) {
? ? ? throw new RuntimeException("Did not expect thread to be interrupted", e);
? ? }
? }
? public T get() {
? ? if (latch.getCount() > 0) {
? ? ? throw new IllegalStateException("Call wait() and check its result");
? ? }
? ? return resultHolder.get();
? }
? public void set(T result) {
? ? resultHolder.set(result);
? ? latch.countDown();
? }
}
3进宝、并發(fā)讀寫
CopyOnWrite的讀寫數(shù)據(jù),解決并發(fā)讀寫問題
retainedKeys = new CopyOnWriteArraySet<>();
4枷恕、當(dāng)前進(jìn)程判斷
因?yàn)閔eap文件的分析服務(wù)党晋、結(jié)果處理服務(wù)、結(jié)果展示activity都是新開進(jìn)程的徐块,二新的進(jìn)程會(huì)觸發(fā)application的oncreate未玻,所以調(diào)用該方法判斷當(dāng)前進(jìn)程是否為leakcanary新開的進(jìn)程。
public static boolean isInServiceProcess(Context context, Class serviceClass) {
? PackageManager packageManager = context.getPackageManager();
? PackageInfo packageInfo;
try{
? ? packageInfo = packageManager.getPackageInfo(context.getPackageName(), GET_SERVICES);
}catch(Exceptione) {
CanaryLog.d(e,"Could not get package info for %s", context.getPackageName());
returnfalse;
? }
? String mainProcess = packageInfo.applicationInfo.processName;
ComponentName component =newComponentName(context, serviceClass);
? ServiceInfo serviceInfo;
try{
serviceInfo = packageManager.getServiceInfo(component,0);
}catch(PackageManager.NameNotFoundException ignored) {
// Service is disabled.
returnfalse;
? }
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.
returnfalse;
? }
? int myPid = android.os.Process.myPid();
? ActivityManager activityManager =
? ? ? (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.RunningAppProcessInfo myProcess =null;
List runningProcesses;
try{
? ? runningProcesses = activityManager.getRunningAppProcesses();
}catch(SecurityExceptionexception) {
// https://github.com/square/leakcanary/issues/948
CanaryLog.d("Could not get running app processes %d",exception);
returnfalse;
? }
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);
returnfalse;
? }
returnmyProcess.processName.equals(serviceInfo.processName);
}
5胡控、構(gòu)造模式
類似dialog的builder扳剿,rewatcher對(duì)象采用了構(gòu)造模式,通過rewatcherbuilder生成昼激。
6庇绽、IdleHandler
源碼中多處使用idlehandler,是一種很巧妙的用法橙困,首先觸發(fā)UI更新操作瞧掺,然后等待主線程空閑,則說明主線程已經(jīng)完成UI更新操作凡傅,繼而執(zhí)行下一步操作辟狈。
// Waiting for Idle to make sure Toast gets rendered.
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
? @Override public boolean queueIdle() {
? ? waitingForToast.set(toast);
? ? return false;
? }
});
7、手動(dòng)gc
源碼中使用該方法保證只有弱引用的對(duì)象被回收像捶,即首先調(diào)用Runtime.gc()上陕,等待100ms后,再調(diào)用System.runFinalization()強(qiáng)制系統(tǒng)回收已經(jīng)沒有強(qiáng)引用的對(duì)象釋放內(nèi)存拓春,并確保該對(duì)象的弱引用被添加到引用隊(duì)列释簿。
public interface GcTrigger {
? 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();
? ? }
8、 監(jiān)控對(duì)象類型
rewatcher的watch方法入?yún)⑹莖bject類型硼莽,所以本質(zhì)上是可以監(jiān)控任意對(duì)象類型的庶溶,關(guān)鍵在于監(jiān)控的時(shí)機(jī),像activity懂鸵、service偏螺、fragmen是有生命周期的,可以在ondestroy時(shí)開始監(jiān)控匆光,其他的對(duì)象類型用戶可以選擇合適的時(shí)機(jī)調(diào)用該方法進(jìn)行監(jiān)控套像,所以網(wǎng)上一般說的leakcanary只能監(jiān)控activity是不準(zhǔn)確的。
public void watch(Object watchedReference) {