玩轉(zhuǎn)Leakcanary內(nèi)存泄露分析

轉(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) {

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末终息,一起剝皮案震驚了整個(gè)濱河市夺巩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌周崭,老刑警劉巖柳譬,帶你破解...
    沈念sama閱讀 211,561評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異续镇,居然都是意外死亡美澳,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門摸航,熙熙樓的掌柜王于貴愁眉苦臉地迎上來制跟,“玉大人,你說我怎么就攤上這事酱虎≠灬” “怎么了?”我有些...
    開封第一講書人閱讀 157,162評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵逢净,是天一觀的道長(zhǎng)哥放。 經(jīng)常有香客問我,道長(zhǎng)爹土,這世上最難降的妖魔是什么甥雕? 我笑而不...
    開封第一講書人閱讀 56,470評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮胀茵,結(jié)果婚禮上社露,老公的妹妹穿的比我還像新娘。我一直安慰自己琼娘,他們只是感情好峭弟,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評(píng)論 6 385
  • 文/花漫 我一把揭開白布附鸽。 她就那樣靜靜地躺著,像睡著了一般瞒瘸。 火紅的嫁衣襯著肌膚如雪坷备。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,806評(píng)論 1 290
  • 那天情臭,我揣著相機(jī)與錄音省撑,去河邊找鬼。 笑死俯在,一個(gè)胖子當(dāng)著我的面吹牛竟秫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播跷乐,決...
    沈念sama閱讀 38,951評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼肥败,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了愕提?” 一聲冷哼從身側(cè)響起拙吉,我...
    開封第一講書人閱讀 37,712評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎揪荣,沒想到半個(gè)月后筷黔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,166評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仗颈,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評(píng)論 2 327
  • 正文 我和宋清朗相戀三年佛舱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挨决。...
    茶點(diǎn)故事閱讀 38,643評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡请祖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出脖祈,到底是詐尸還是另有隱情肆捕,我是刑警寧澤,帶...
    沈念sama閱讀 34,306評(píng)論 4 330
  • 正文 年R本政府宣布盖高,位于F島的核電站慎陵,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏喻奥。R本人自食惡果不足惜席纽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望撞蚕。 院中可真熱鬧润梯,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至舶赔,卻和暖如春扫倡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背顿痪。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工镊辕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留油够,地道東北人蚁袭。 一個(gè)月前我還...
    沈念sama閱讀 46,351評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像石咬,于是被迫代替她去往敵國(guó)和親揩悄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容