当前位置: 首页 > 工具软件 > LeakCanary > 使用案例 >

Android LeakCanary

党浩阔
2023-12-01

1.LeakCanary

LeakCanary是一个自动检测内存泄漏的工具。一旦检测到内存泄漏,LeakCanary就会dump内存信息,并通过另一个进程分析内存泄漏的信息并展示出来,可以随时发现和定位内存泄漏问题,极大地方便了Android应用程序的开发。

 

LeakCanary优点:

①针对Activity/Fragment组件完全自动化的内存泄漏检查。

②可定制一些行为(dump文件和leaktrace对象的数量、自定义例外、分析结果的自定义处理等)。

③集成到自己工程,使用的成本很低。

④友好的界面展示和通知。

 

LeakCanary工作机制:

①RefWatcher.watch()创建一个KeyedWeakReference到要被监控的对象。

②然后在后台线程检查引用是否被清除,如果没有,调用GC。

③如果引用还是未被清除,把heap内存dump到APP对应的文件系统中的一个.hprof文件中。

④在另外一个进程中的HeapAnalyzerService有一个HeapAnalyzer使用HAHA解析这个文件。

⑤得益于唯一的reference key,,HeapAnalyzer找到KeyedWeakReference,定位内存泄露。

⑥HeapAnalyzer计算到GC roots的最短强引用路径,并确定是否是泄露。如果是的话,建立导致泄露的引用链。

⑦引用链传递到APP进程中的DisplayLeakService , 并以通知的形式展示出来。

 

2.LeakCanary的用法(2.0以前的版本)

①在build.gradle中添加LeakCanary的依赖包。

dependencies {

    debugCompile 'com.squareup.leakcanary:leakcanary-android:1.6.1'

    releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'

}

在开发中一般同时集成debug和release版本,其中:

com.squareup.leakcanary:leakcanary-android:1.6.1 是debug版本,当app编译的是debug版本时,加载的是该jar包,一旦出现内存泄漏会在通知栏中通知开发者产生了内存泄漏;

com.squareup.leakcanary:leakcanary-android-no-op:1.6.1 是release版本,如果app编译的是release版本时,加载的是该jar包,no-op是指No Operation Performed,代表不会做任何操作,不会干扰正式用户的使用;

②在自定义Application的onCreate方法中注册LeakCanary。

@Override

public void onCreate() {

    super.onCreate();

    if (LeakCanary.isInAnalyzerProcess(this)) {

          return;

    }

    LeakCanary.install(this);

}

解释一下为什么要先判断LeakCanary.isInAnalyzerProcess(this):在注册之前先判断LeakCanary是否已经运行在手机上,比如同时有多个APP集成了LeakCanary,其他app已经运行了LeakCanary,则不需要重新install。

看一下isInAnalyzerProcess方法的源码:

public static boolean isInAnalyzerProcess( Context context) {

    Boolean isInAnalyzerProcess = LeakCanaryInternals.isInAnalyzerProcess;

    if (isInAnalyzerProcess == null) {

        isInAnalyzerProcess = isInServiceProcess( context, HeapAnalyzerService.class);

        LeakCanaryInternals.isInAnalyzerProcess = isInAnalyzerProcess;

    }

    return isInAnalyzerProcess;

}

从源码中可以看到真正调用的是isInServiceProcess方法,看isInServiceProcess的源码:

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.NameNotFoun dException ignored) {

        return false;

    }

    if (serviceInfo.processName.equals( mainProcess)) {

        CanaryLog.d("Did not expect service %s to run in main process %s", serviceClass, mainProcess);

        return false;

    }

    int myPid = android.os.Process.myPid();

    ActivityManager activityManager = (ActivityManager) context.getSystemService( Context.ACTIVITY_SERVICE);

    ActivityManager.RunningAppProcessInfo myProcess = null;

    List<ActivityManager.RunningAppProcessIn fo> runningProcesses;

    try {

        runningProcesses = activityManager.getRunningAppProcesses();

    } catch (SecurityException exception) {

        CanaryLog.d("Could not get running app processes %d", exception);

        return false;

    }

    if (runningProcesses != null) {

        for (ActivityManager.RunningAppProce ssInfo 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);

}

可以看到ComponentName component = new ComponentName(context, serviceClass);而这个参数serviceClass是isInAnalyzerProcess方法中传入的HeapAnalyzerService,HeapAnalyzerService正是用来分析内存泄漏的单独进程,所以说LeakCanary在同一个手机只需要执行一次install就可以了,当然执行多次也是可以的。

正常情况下Application调用LeakCanary.install( this)后就可以正常监听该app程序的内存泄漏了。

 

3.LeakCanary监听指定对象的内存泄漏

如果想让LeakCanary监听指定对象的内存泄漏,就需要使用到RefWatcher的watch功能,使用方式如下:

①在Application的onCreate中调用install方法,并获取RefWatcher对象:

private static RefWatcher sRefWatcher;

@Override

public void onCreate() {

    super.onCreate();

    sRefWatcher = LeakCanary.install(this);

}

public static RefWatcher getRefWatcher() {

    return sRefWatcher;

}

注意:因为这时候需要获取sRefWatcher对象,所以sRefWatcher = LeakCanary.install(this)一定需要执行,不需要判断LeakCanary.isInAnalyzerProcess(this)。

②在需要监听的对象中调用RefWatcher的watch方法进行监听,比如想监听一个Activity,就可以在该Acitivity中onCreate方法中添加DemoApp.getRefWatcher().watch(this);,如下:

@Override

protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    DemoApp.getRefWatcher().watch(this);

    setContentView(R.layout.activity_second);

    ……

}

当app出现内存泄漏后,过一小段时间就会在通知栏中通知出现内存泄漏的情况,同时会在桌面上生成一个Leaks的图标,这个就是展示内存泄漏列表的,可以通过点击进入查看泄漏的内容。

这里总结一下产生内存泄漏的常见场景和常用的解决方案

常见的内存泄漏场景:

①单例模式造成内存泄漏

单例里传入Activity的context会造成内存泄露。解决办法是传入Application的context。

②非静态内部类创建的静态实例造成内存泄漏

public class StaticLeakActivity extends Activity {

    private static NoneStaticInnerClass mResource = null;

    @override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        if(mResource == null) {

            mResource = new NoneStaticInnerClass();

        }

    }

    private class NoneStaticInnerClass{

        ……

    }

}

非静态内部类创建的静态实例会造成内存泄漏,解决办法是将这个非静态内部类改成静态内部类,这样就不会持有Activity的引用

③Handler造成的内存泄漏

④线程造成的内存泄漏

⑤资源未关闭造成的内存泄漏

常见的解决方案(思路):

①尽量使用Application的Context而不是Activity的

②使用弱引用或者软引用

③手动设置null,解除引用关系

④将内部类设置为static,不隐式持有外部的实例

⑤注册与反注册成对出现,在对象合适的生命周期进行反注册操作。

⑥如果没有修改的权限,比如系统或者第三方SDK,可以使用反射进行解决持有关系

⑦在使用完BroadcastReceiver、ContentObserver、File、Cursor、Stream、Bitmap等资源时,一定要在Activity中的OnDestry中及时的关闭、注销或者释放内存

 

4.源码分析(2.0以前的版本)

LeakCanary在Appaction的初始化方式:

@Override

public void onCreate() {

    super.onCreate();

    if (LeakCanary.isInAnalyzerProcess(this)) {

        return;

    }

    LeakCanary.install(this);

}

LeakCanary.install(this)调用之后LeakCanary已经可以正常监控应用中所有Activity的内存泄漏情况了,下面从源码的角度来看看实现原理:

LeakCanary.java:

public static RefWatcher install(Application application) {

    return refWatcher(application).listenerServi ceClass(DisplayLeakService.class).excludedRefs(AndroidExcludedRefs.createAppDefaults().build()).buildAndInstall();

}

public static AndroidRefWatcherBuilder refWatcher(Context context) {

    return new AndroidRefWatcherBuilder( context);

}

①install方法的返回值是RefWatcher,这个对象非常重要,LeakCanary就是通过这个对象进行Activity的内存监控。

②RefWatcher是通过Builder模式创建的。

③install方法内部一连串的链式调用,最后调用了buildAndInstall()方法。

AndroidRefWatcherBuilder.java:

private boolean watchActivities = true;

private boolean watchFragments = true;

public RefWatcher buildAndInstall() {

    if (LeakCanaryInternals.installedRefWatcher != null) {

        throw new UnsupportedOperationException( "buildAndInstall() should only be called once.");

    }

    RefWatcher refWatcher = build();//RefWatch就是在这边创建的

    if (refWatcher != DISABLED) {

        LeakCanaryInternals.setEnabledAsync( context, DisplayLeakActivity.class, true);

        if (watchActivities) {

            ActivityRefWatcher.install(context, refWatcher);

        }

        if (watchFragments) {

            FragmentRefWatcher.Helper.install( context, refWatcher);

        }

    }

    LeakCanaryInternals.installedRefWatcher = refWatcher;

    return refWatcher;

}

①LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);允许泄漏报告页面的显示;

②watchActivities/watchFragments默认值都是true,所以ActivityRefWatcher和FragmentRefWatcher都将被启用;

③ActivityRefWatcher用于监控Activity的内存泄漏

④FragmentRefWatcher用于监控Fragment的内存泄漏;

接下来通过ActivityRefWatcher进一步分析LeakCanary是如何监控内存泄漏的:

ActivityRefWatcher.java:

public static void install(Context context, RefWatcher refWatcher) {

    Application application = (Application) context.getApplicationContext();

    ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);

    application.registerActivityLifecycleCallbacks( activityRefWatcher.lifecycleCallbacks);

}

①registerActivityLifecycleCallbacks是Android Application的一个方法,注册了该方法,应用中每一个Activity的生命周期变化都会通过该方法回调回来;

②registerActivityLifecycleCallbacks方法传入的参数是activityRefWatcher.lifecycleCallbacks,到ActivityRefWatcher中看下该方法的实现:

ActivityRefWatcher.java:

private final Application.ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacksAdapter() {

    @Override

    public void onActivityDestroyed(Activity activity) {

        refWatcher.watch(activity);

    }

};

可以看到,ActivityRefWatcher只监听了onActivityDestroyed方法,也就是说每一个Activity调用onDestory的时候都会回调到onActivityDestroyed这个方法,通知LeakCanary该Activity应该被销毁。

到这来就明白了为什么只执行LeakCanary.install( this)一条语句就可以完成对整个app的Activity内存泄漏进行监听了。

接下来看看ActivityRefWatcher是如何监听内存泄漏的,RefWatcher有两个核心方法: watch() 和 ensureGone():

RefWatcher.java:

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);

}

①官方对watch方法的注释:监听提供的引用,检查该引用是否可以被回收。这个方法是非阻塞的,因为检测功能是在Executor中的异步线程执行的。

②checkNotNull:分别检测watchedReference、referenceName是否为空,如果为空则抛出异常结束。

③随机生成一个key,该key用于唯一标识已产生内存泄漏的对象,或者准备检测的对象。

④创建KeyedWeakReference对象,并调用另一个核心方法ensureGone。

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {

    watchExecutor.execute(new Retryable() {

        @Override

        public Retryable.Result run() {

            return ensureGone(reference, watchStartNanoTime);

        }

    });

}

Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {

    long gcStartNanoTime = System.nanoTime();

    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);

    removeWeaklyReachableReferences();

    if (debuggerControl.isDebuggerAttached()) {

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

            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);

    }

    return DONE;

}

①ensureGone方法是在ensureGoneAsync方法中开启的,并且是一个异步任务,不会对主线程造成阻塞。

②removeWeaklyReachableReferences():移除已经回收的弱引用对象的key,如果需要判断的对象已经销毁,则不继续执行。

if (gone(reference)) {

    return DONE;

}

③如果移除之后还是存在该引用则手动再GC一次:gcTrigger.runGc();

④然后二次调用removeWeaklyReachableReferences()再判断是否已经销毁。

⑤如果二次确认还是存在,则判断为内存泄漏了,开始.hprof文件的dump。

⑥调用heapdumpListener.analyze(heapDump)进行泄漏分析。

总结一下:

①弱引用与ReferenceQueue联合使用,如果弱引用关联的对象被回收,则会把这个弱引用加入到ReferenceQueue中。通过这个原理,可以看出removeWeaklyReachableReferences()执行后,会对应删除KeyedWeakReference的数据。如果这个引用继续存在,那么就说明没有被回收。

②为了确保最大保险的判定是否被回收,一共执行了两次回收判定,包括一次手动GC后的回收判定。两次都没有被回收,很大程度上说明了这个对象的内存被泄漏了,但并不能100%保证;因此LeakCanary是存在极小程度的误差的。

hprof文件生成好以后,接下来就通过heapdumpListener.analyze(heapDump)进行内存泄漏最短路径分析:

@Override

public void analyze(HeapDump heapDump) {

    checkNotNull(heapDump, "heapDump");

    HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);

}

调用了HeapAnalyzerService,在单独的进程中进行分析。

直接把HeapAnalyzerService整个类的代码贴上:

public final class HeapAnalyzerService extends ForegroundService implements AnalyzerProgressListener {

    private static final String LISTENER_CLASS_EXTRA = "listener_class_extra";

    private static final String HEAPDUMP_EXTRA = "heapdump_extra";

    public static void runAnalysis(Context context, HeapDump heapDump, Class<? extends AbstractAnalysisResultService> listenerServiceClass) {

        setEnabledBlocking(context, HeapAnalyzerService.class, true);

        setEnabledBlocking(context, listenerServiceClass, true);

        Intent intent = new Intent(context, HeapAnalyzerService.class);

        intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());

        intent.putExtra(HEAPDUMP_EXTRA, heapDump);

        ContextCompat.startForegroundService( context, intent);

    }

    public HeapAnalyzerService() {

        super(HeapAnalyzerService.class.getSim pleName(),R.string.leak_canary_notification_analysing);

    }

    @Override

    protected void onHandleIntentInForeground( 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, this, heapDump.reachabilityInspectorClasses);

        AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey, heapDump.computeRetainedHeapSize);

        AbstractAnalysisResultService.sendResu ltToListener(this, listenerClassName, heapDump, result);

    }

    @Override

    public void onProgressUpdate(Step step) {

        int percent = (int) ((100f * step.ordinal()) / Step.values().length);

        CanaryLog.d("Analysis in progress, working on: %s", step.name());

        String lowercase = step.name().replace("_", " ").toLowerCase();

        String message = lowercase.substring(0, 1).toUpperCase() + lowercase.substring(1);

        showForegroundNotification(100, percent, false, message);

    }

}

①runAnalysis方法中最后一句:ContextCompat.startForegroundService(context, intent)启动HeapAnalyzerService。

②HeapAnalyzerService继承自ForegroundService,服务启动时会调用onHandleIntentInForeground方法。

onHandleIntentInForeground方法中比较核心的方法调用是:

HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);

AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey, heapDump.computeRetainedHeapSize);

HeapAnalyzer中核心的方法是checkForLeak,来看下它的具体实现:

public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey, boolean computeRetainedSize) {

    long analysisStartNanoTime = System.nanoTime();

    if (!heapDumpFile.exists()) {

        Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);

        return failure(exception, since(analysisStartNanoTime));

    }

    try {

        listener.onProgressUpdate( READING_HEAP_DUMP_FILE);

        HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);

        HprofParser parser = new HprofParser(buffer);

        listener.onProgressUpdate( PARSING_HEAP_DUMP);

        Snapshot snapshot = parser.parse();

        listener.onProgressUpdate( DEDUPLICATING_GC_ROOTS);

        deduplicateGcRoots(snapshot);

        listener.onProgressUpdate( FINDING_LEAKING_REF);

        Instance leakingRef = findLeakingReference( referenceKey, snapshot);

        if (leakingRef == null) {

            return noLeak(since( analysisStartNanoTime));

        }

        return findLeakTrace( analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);

    } catch (Throwable e) {

        return failure(e, since(analysisStartNanoTime));

    }

}

①官方对该方法的注释:在dump的堆信息中通过key查找出内存泄漏的弱引用对象,并且计算出最短的GC路径。

②Snapshot snapshot = parser.parse()–生成文件的快照。

③deduplicateGcRoots(snapshot)–过滤重复的内存泄漏对象。

④findLeakingReference(referenceKey, snapshot)–在快照中根据referenceKey查找是否有对应的内存泄漏对象,如果获取到的leakingRef为空,则说明内存已经被回收了,不存在内存泄漏的情况;如果leakingRef不为空则进入下一步查找内存泄漏对象的GC最短路径。

⑤findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize)–查找内存泄漏对象的GC最短路径。

LeakCanary的核心源码已经都已经分析完了,剩下的就是如果在页面上显示出来了。

总结一下用到的重要的类和方法:

①LeakCanary#install()

SDK初始化接口, 通常只需要调用该方法就可以监控app的内存泄漏情况。

②DisplayLeakActivity类

内存泄漏的查看显示页面

③RefWatcher#watch() / ensureGone()

watch():监听提供的引用,检查该引用是否可以被回收。这个方法是非阻塞的,因为检测功能是在Executor中的异步线程执行的。

ensureGone():2次确认是否确实存在内存泄漏,如果存在内存泄漏则dump泄漏信息到hprof文件中,并调用ServiceHeapDumpListener回调进行内存分析。

④ActivityRefWatcher类

Activity引用检测, 包含了Activity生命周期的监听执行与停止。

⑤HeapAnalyzerService#runAnalysis

内存堆分析服务, 为了保证App进程不会因此受影响变慢&内存溢出,运行于独立的进程。

⑥HeapAnalyzer#checkForLeak

在dump的堆信息中通过key查找出内存泄漏的弱引用对象,并且计算出最短的GC路径。

 

5.LeakCanary2.0以后的版本

LeakCanary2.0版本开始使用kotlin语言,使用起来更加简单,省去了在Application中的注册,只需要在build.gradle文件中加入依赖即可,完全实现了自动化检测。

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0'

这样就可以了,代码上什么都不用写,完全做到了无感知操作。

那代码上什么都不用写,不用注册它怎么来进行调用,进行内存监测呢?答案就在AppWatcherInstaller这个类上,它继承了ContentProvider,属于四大组件之一。ContentProvider的特点就是不用显示调用初始化,在执行完application的初始化后就会调用ContentProvider的onCreate()方法。正是利用这一点,leakcanary把注册写在了这里面,由系统自动调用完成,对开发者完全无感知。

leakCananry源码分两部分来看:①添加要观察的对象 ②对对象进行观察。

①第一部分,添加观察对象

在AppWatcherInstaller的onCreate中调用了InternalAppWatcher的install方法

AppWatcherInstaller.java:

internal sealed class AppWatcherInstaller : ContentProvider {

    override fun onCreate() : Boolean {

        val application = context!!.applicationContext as Application

        InternalAppWatcher.install(application)

        return true

    }

    ……

}

InternalAppWatcher.java:

fun install(application Application) {

    checkMainThread()

    if(this::application.isInitialized) {

        return

    }

    InternalAppWatcher.application = application

    val configProvider = {AppWatcher.config}

    ActivityDestroyWatcher.install(application, objectWatcher, configProvider)

    FragmentDestroyWatcher.install(application, objectWatcher, configProvider)

    onAppWatcherInstalled(application)

}

install方法主要做了两件事情:

1)检查是否在主线程(不是的话抛出异常)

2)添加生命周期的回调,在执行onDestory的时候调用objectWatcher的watch方法,观察这个activity的对象是否回收掉了。

ActivityDestroyWatcher在执行onDestory的时候开始进行对象检测

ActivityDestroyWatcher.java:

internal class ActivityDestroyWatcher private constructor(

    private val objectWatcher : ObjectWatcher

    private val configProvider : ( ) -> Config

) {

    private val lifecycleCallbacks =

      object : Application.ActivityLifecycleCallbacks by noOpDelegate() {

        override fun onActivityDestroyed(activity: Activity) {

            if (configProvider().watchActivities) {

               objectWatcher.watch(activity)

           }

       }

    }

    companion object {

        fun install(application : Application, objectWatcher : ObjectWatcher, configProvider : () -> Config) {

            val activityDestroyWatcher = ActivityDestroyWatcher(objectWatcher, configProvider)

            application.registerActivityLifeCycleCallb acks(activityDestroyWatcher.lifecycleCallbacks)

        }

    }

}

以上为注册流程。

②第二部分就是检测对象的回收了

检测对象的回收,起始类是ObjectWatcher,即对象观察器,ObjectWatcher里面有两个集合分别是:

1)private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()

这是一个map集合,用来存放要观察对象的key和弱引用,代码会为每个观察的对象生成一个唯一的key和弱引用。

2)private val queue = ReferenceQueue<Any>()

watchedObjects这个队列和弱引用联合使用,当弱引用中的对象被回收后,这个弱引用会被放到这个队列中。也就是说,只要这个队列中存在弱引用,就代表这个弱引用中所包含的对象被回收了。

然后调用ObjectWatcher.watch方法:

ObjectWatcher.watch(watchedObject : Any,description : String)

watch方法里面做了如下操作:

1)先移除watchedObjects和queue集合里面已经回收对象的弱引用。

removeWeaklyReachableObjects()

2)通过uuid为当前观察的对象生成一个唯一的key,并把对象用弱引用包起来,放到watchedObjects这个集合中,同时把queue和弱引用关联起来。

3)执行checkRetainedExecutor.execute {               

                moveToRetained(key)

            }

@Synchronized private fun moveToRetained(key : String) {

    removeWeaklyReachableObjects()

    val retainedRef = watchedObjects[key]

    if(retainedRef != null) {

        retainedRef.retainedUptimeMillis = clock.uptimeMillis()

        onObjectRetainedListeners.forEach { it.onObjectRetained() }

    }

}

这里是个延迟任务,延迟五秒后再次执行1)操作,这个期间对象可能已经被回收了,所以需要再次移除一次。

4)执行了3)操作后,就可以通过watchedObjects 集合找到没有被回收的对象了。这时候就可以获取到没有被回收的对象的个数,大于0,进行一次gc操作(gc操作用的是Runtime.getRuntime() .gc(),源码上注释这个操作比system.gc()更可能触发gc操作)。

var retainedReferenceCount = objectWatcher.retainedObjectCount

if (retainedReferenceCount > 0) {

  gcTrigger.runGc()

  retainedReferenceCount = objectWatcher.retainedObjectCount

}

5)再次获取未被回收的个数,这一步会有几个判断条件:

①如果个数小于5,不做操作等待5秒再次进行检查未回收的个数,一直循环,直到大于等于5个或者等于0个,为了防止频发回收堆造成卡顿。

②大于5个后,如果处于debug模式,会再等20秒,再次执行4操作。防止debug模式会减慢回收

③距离上次堆栈分析是否大于等于1分钟,如果没有超过一分钟,也需要再次延迟(1分钟-当前距离上次的时间)再次循环4操作

6)如果上面的条件都符合了,就可以开始进行堆栈的分析了:

①获取到内容文件 Debug.dumpHprofData(heapDumpFile.absolutePath)

②objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)该操作是去掉以前已经分析过的对象,也就是去除掉之前的没有回收掉的对象,不在本次分析范围内

③HeapAnalyzerService开启IntentService服务进行分析

④把结果插入数据库(泄漏区分了应用本身的内存泄漏和类库的内存泄漏),并且发送通知

 

6.总结

①LeakCanary主要是通过Application的registerActivityLifecycleCallbacks方法监控每一个Activty的Destory之后对象是否被收回。

②在Activity Destory之后ActivityRefWatch的watch方法将被调用,watch方法会通过一个随机生成的key将这个弱引用关联到一个ReferenceQueue,然后调用ensureGone()。

注:当一个软引用/弱引用对象被垃圾回收后,Java虚拟机就会把这个引用加入到与之关联的引用队列中。

③ActivityRefWatch的ensureGone()方法中会先确认一次是否已经被回收,如果发现没有被回收,则主动GC一下,然后在次确认是否被回收,如果还是没有回收则判断为内存泄漏。

④一旦确认是内存泄漏,则开始dump信息到hprof文件中,并调用heapdumpListener.analyze(heapDump)开始内存分析。

⑤内存分析是在HeapAnalyzerService服务中进行的,属于一个单独的进程。

⑥HeapAnalyzerService的runAnalysis中创建HeapAnalyzer对象并调用它的一个核心方法checkForLeak()。

⑦HeapAnalyzer的checkForLeak()会先解析hprof文件并生成快照文件,然后对快照中的泄漏对象进行去重,去重后根据第②步中的key去获取泄漏对象,如果对象为空则说明对象已经被回收,如果不为空则通过findLeakTrace()方法计算出最短GC路径,并显示到DisplayLeakActivity页面,提醒开发者存在内存泄漏。

 

 

 

 

 类似资料:

相关阅读

相关文章

相关问答