探秘 Android LeakCanary 配置管理模块:原理、源码与实战全解析(2)

78 阅读17分钟

探秘 Android LeakCanary 配置管理模块:原理、源码与实战全解析

一、引言

在 Android 开发的旅程中,内存泄漏始终是一个如影随形的难题,它会让应用程序变得迟缓、不稳定,甚至直接崩溃,极大地影响用户体验。LeakCanary 作为一款强大的内存泄漏检测工具,就像是开发者的“内存侦探”,能够在应用运行时自动检测并报告内存泄漏问题,帮助开发者快速定位并解决隐患。

而配置管理模块则是 LeakCanary 的“大脑”,它允许开发者根据不同的应用场景和需求,对 LeakCanary 的行为进行细致的定制。无论是设置排除某些特定对象的检测,还是调整检测的频率和阈值,都可以通过配置管理模块来实现。本文将深入到 LeakCanary 的源码层面,详细剖析配置管理模块的工作原理,让开发者能够更好地掌握和运用这一强大工具。

二、LeakCanary 配置管理模块概述

2.1 配置管理模块的作用

配置管理模块在 LeakCanary 中承担着至关重要的角色,它负责对 LeakCanary 的各种行为进行参数化配置。这些配置项涵盖了多个方面,包括但不限于内存泄漏检测的阈值、排除特定对象的检测、指定分析结果的处理方式等。通过合理配置这些参数,开发者可以使 LeakCanary 更加贴合应用的实际需求,提高检测的准确性和效率。

2.2 配置管理模块的重要性

不同的 Android 应用具有不同的特点和需求,例如,有些应用对内存的使用非常敏感,需要更严格的内存泄漏检测;而有些应用则可能存在一些特定的对象,由于业务逻辑的原因,它们在表面上看起来像是内存泄漏,但实际上是正常的。配置管理模块允许开发者根据应用的具体情况进行定制化配置,从而避免误报和漏报,提高内存泄漏检测的效果。

2.3 配置管理模块的主要配置项

LeakCanary 的配置管理模块提供了丰富的配置选项,以下是一些常见的配置项:

  • 排除引用(Excluded References):指定某些对象或引用不参与内存泄漏检测,避免对正常业务逻辑产生误判。
  • 分析结果监听器(Analysis Result Listener):定义当检测到内存泄漏时,LeakCanary 如何处理分析结果,例如显示通知、保存报告等。
  • 堆转储触发条件(Heap Dump Trigger Conditions):设置触发堆转储(将应用的内存快照保存到文件中以便后续分析)的条件,如内存使用达到一定阈值或特定对象长时间未被回收。
  • 检测频率(Detection Frequency):控制 LeakCanary 进行内存泄漏检测的频率,以平衡检测的准确性和性能开销。

三、配置管理模块的核心类与数据结构

3.1 RefWatcherBuilder

RefWatcherBuilder 类是配置管理的入口点,它提供了一系列的方法来设置各种配置参数,并最终构建出一个 RefWatcher 实例,该实例负责实际的内存泄漏检测工作。以下是 RefWatcherBuilder 类的部分源码及详细注释:

// RefWatcherBuilder 类用于构建 RefWatcher 实例,同时设置各种配置参数
public class RefWatcherBuilder<T extends RefWatcherBuilder<T>> {
    // 应用的上下文,用于获取系统资源和执行操作
    private final Context context;
    // 排除的引用集合,存储不需要检测的对象引用
    private ExcludedRefs excludedRefs;
    // 分析结果的监听器类,用于处理内存泄漏分析结果
    private Class<? extends AbstractAnalysisResultService> listenerServiceClass;
    // 堆转储文件的目录提供者,用于指定堆转储文件的存储位置
    private LeakDirectoryProvider leakDirectoryProvider;
    // 检测内存泄漏的延迟时间,单位为毫秒
    private long watchDurationMs = TimeUnit.SECONDS.toMillis(5);
    // 堆转储的阈值,当内存使用达到该阈值时触发堆转储
    private int heapDumpAfterRetainedReferenceCount = 7;
    // 堆转储的间隔时间,控制堆转储的频率
    private long heapDumpIntervalMs = TimeUnit.MINUTES.toMillis(5);
    // 是否在调试器连接时禁用检测
    private boolean disableDumpHeap = false;

    // 构造函数,初始化应用上下文
    public RefWatcherBuilder(Context context) {
        this.context = context.getApplicationContext();
    }

    // 设置排除的引用
    public T excludedRefs(ExcludedRefs excludedRefs) {
        this.excludedRefs = excludedRefs;
        return (T) this;
    }

    // 设置分析结果的监听器类
    public T listenerServiceClass(Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
        this.listenerServiceClass = listenerServiceClass;
        return (T) this;
    }

    // 设置堆转储文件的目录提供者
    public T leakDirectoryProvider(LeakDirectoryProvider leakDirectoryProvider) {
        this.leakDirectoryProvider = leakDirectoryProvider;
        return (T) this;
    }

    // 设置检测内存泄漏的延迟时间
    public T watchDuration(long duration, TimeUnit timeUnit) {
        this.watchDurationMs = timeUnit.toMillis(duration);
        return (T) this;
    }

    // 设置堆转储的阈值
    public T heapDumpAfterRetainedReferenceCount(int heapDumpAfterRetainedReferenceCount) {
        this.heapDumpAfterRetainedReferenceCount = heapDumpAfterRetainedReferenceCount;
        return (T) this;
    }

    // 设置堆转储的间隔时间
    public T heapDumpInterval(long interval, TimeUnit timeUnit) {
        this.heapDumpIntervalMs = timeUnit.toMillis(interval);
        return (T) this;
    }

    // 设置是否在调试器连接时禁用检测
    public T disableDumpHeap(boolean disableDumpHeap) {
        this.disableDumpHeap = disableDumpHeap;
        return (T) this;
    }

    // 构建并安装 RefWatcher 实例
    public RefWatcher buildAndInstall() {
        if (isInAnalyzerProcess(context)) {
            // 如果当前进程是分析进程,则不进行安装,直接返回禁用的 RefWatcher
            return RefWatcher.DISABLED;
        }
        // 启用显示泄漏信息的 Activity
        enableDisplayLeakActivity(context);
        // 创建 Android 资源提供者,用于获取字符串资源等
        AndroidResourceProvider resourceProvider = new AndroidResourceProvider(context);
        // 创建调试器控制实例,用于判断是否有调试器连接
        DebuggerControl debuggerControl = new AndroidDebuggerControl();
        // 创建堆转储器实例,用于将应用的内存快照保存到文件中
        HeapDumper heapDumper = new AndroidHeapDumper(context, resourceProvider);
        // 创建资源释放执行器,用于执行资源释放任务
        ResourceReleasingExecutor watchExecutor = new AndroidWatchExecutor(Looper.getMainLooper());
        // 创建 LeakCanary 内部管理实例,负责管理一些内部状态和操作
        LeakCanaryInternals leakCanaryInternals = new LeakCanaryInternals(context);
        if (listenerServiceClass != null) {
            // 如果设置了分析结果监听器类,则将其设置到 LeakCanary 内部管理实例中
            leakCanaryInternals.setListenerServiceClass(listenerServiceClass);
        }
        // 监听 Activity 的生命周期,当 Activity 销毁时进行内存泄漏检测
        leakCanaryInternals.watchActivities(context, watchExecutor);
        // 监听 Fragment 的生命周期,当 Fragment 销毁时进行内存泄漏检测
        leakCanaryInternals.watchFragments(context, watchExecutor);
        // 创建堆转储触发器,根据配置的条件触发堆转储操作
        HeapDumpTrigger heapDumpTrigger = new HeapDumpTrigger(context, watchExecutor, debuggerControl, heapDumper,
                leakCanaryInternals.leakDirectoryProvider, excludedRefs, watchDurationMs,
                heapDumpAfterRetainedReferenceCount, heapDumpIntervalMs, disableDumpHeap);
        // 创建 RefWatcher 实例,用于实际的内存泄漏检测
        RefWatcher refWatcher = new RefWatcher(watchExecutor, debuggerControl, heapDumpTrigger, heapDumper,
                excludedRefs);
        // 将 RefWatcher 实例设置为全局的 RefWatcher
        LeakCanaryInternals.setRefWatcher(context, refWatcher);
        return refWatcher;
    }

    // 判断当前进程是否是分析进程
    private static boolean isInAnalyzerProcess(Context context) {
        String processName = LeakCanaryInternals.getProcessName(context);
        String analyzerProcessName = context.getPackageName() + ":leakcanary-analyzer";
        return analyzerProcessName.equals(processName);
    }

    // 启用显示泄漏信息的 Activity
    private static void enableDisplayLeakActivity(Context context) {
        PackageManager packageManager = context.getPackageManager();
        ComponentName componentName = new ComponentName(context, DisplayLeakActivity.class);
        packageManager.setComponentEnabledSetting(componentName,
                PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
    }
}

3.2 ExcludedRefs

ExcludedRefs 类用于管理需要排除在内存泄漏检测之外的引用。有时候,应用中可能存在一些对象,它们由于业务逻辑的原因,在表面上看起来像是内存泄漏,但实际上是正常的。通过配置 ExcludedRefs,可以避免 LeakCanary 对这些对象产生误判。以下是 ExcludedRefs 类的源码及注释:

// ExcludedRefs 类用于存储需要排除在内存泄漏检测之外的引用
public final class ExcludedRefs {
    // 存储排除的静态字段引用,键为类,值为该类中需要排除的静态字段名集合
    private final Map<Class<?>, Set<String>> excludedStaticFields;
    // 存储排除的实例字段引用,键为类,值为该类中需要排除的实例字段名集合
    private final Map<Class<?>, Set<String>> excludedInstanceFields;

    // 私有构造函数,用于初始化排除的静态字段和实例字段集合
    private ExcludedRefs(Map<Class<?>, Set<String>> excludedStaticFields,
                         Map<Class<?>, Set<String>> excludedInstanceFields) {
        this.excludedStaticFields = excludedStaticFields;
        this.excludedInstanceFields = excludedInstanceFields;
    }

    // 静态内部类,用于构建 ExcludedRefs 实例
    public static final class Builder {
        // 用于存储排除的静态字段引用
        private final Map<Class<?>, Set<String>> excludedStaticFields = new HashMap<>();
        // 用于存储排除的实例字段引用
        private final Map<Class<?>, Set<String>> excludedInstanceFields = new HashMap<>();

        // 添加需要排除的实例字段引用
        public Builder instanceField(Class<?> clazz, String fieldName) {
            // 获取该类对应的排除实例字段集合
            Set<String> fields = excludedInstanceFields.computeIfAbsent(clazz, k -> new HashSet<>());
            // 将字段名添加到集合中
            fields.add(fieldName);
            return this;
        }

        // 添加需要排除的静态字段引用
        public Builder staticField(Class<?> clazz, String fieldName) {
            // 获取该类对应的排除静态字段集合
            Set<String> fields = excludedStaticFields.computeIfAbsent(clazz, k -> new HashSet<>());
            // 将字段名添加到集合中
            fields.add(fieldName);
            return this;
        }

        // 构建 ExcludedRefs 实例
        public ExcludedRefs build() {
            return new ExcludedRefs(excludedStaticFields, excludedInstanceFields);
        }
    }

    // 获取排除的静态字段引用
    public Map<Class<?>, Set<String>> getExcludedStaticFields() {
        return excludedStaticFields;
    }

    // 获取排除的实例字段引用
    public Map<Class<?>, Set<String>> getExcludedInstanceFields() {
        return excludedInstanceFields;
    }
}

3.3 LeakCanaryConfig

LeakCanaryConfig 类封装了 LeakCanary 的所有配置信息,它是一个不可变类,确保配置信息在创建后不会被意外修改。以下是 LeakCanaryConfig 类的源码及注释:

// LeakCanaryConfig 类封装了 LeakCanary 的所有配置信息
public final class LeakCanaryConfig {
    // 排除的引用集合
    public final ExcludedRefs excludedRefs;
    // 分析结果的监听器类
    public final Class<? extends AbstractAnalysisResultService> listenerServiceClass;
    // 堆转储文件的目录提供者
    public final LeakDirectoryProvider leakDirectoryProvider;
    // 检测内存泄漏的延迟时间,单位为毫秒
    public final long watchDurationMs;
    // 堆转储的阈值,当内存使用达到该阈值时触发堆转储
    public final int heapDumpAfterRetainedReferenceCount;
    // 堆转储的间隔时间,控制堆转储的频率
    public final long heapDumpIntervalMs;
    // 是否在调试器连接时禁用检测
    public final boolean disableDumpHeap;

    // 私有构造函数,用于初始化配置信息
    private LeakCanaryConfig(ExcludedRefs excludedRefs,
                             Class<? extends AbstractAnalysisResultService> listenerServiceClass,
                             LeakDirectoryProvider leakDirectoryProvider,
                             long watchDurationMs,
                             int heapDumpAfterRetainedReferenceCount,
                             long heapDumpIntervalMs,
                             boolean disableDumpHeap) {
        this.excludedRefs = excludedRefs;
        this.listenerServiceClass = listenerServiceClass;
        this.leakDirectoryProvider = leakDirectoryProvider;
        this.watchDurationMs = watchDurationMs;
        this.heapDumpAfterRetainedReferenceCount = heapDumpAfterRetainedReferenceCount;
        this.heapDumpIntervalMs = heapDumpIntervalMs;
        this.disableDumpHeap = disableDumpHeap;
    }

    // 静态内部类,用于构建 LeakCanaryConfig 实例
    public static final class Builder {
        // 排除的引用集合
        private ExcludedRefs excludedRefs;
        // 分析结果的监听器类
        private Class<? extends AbstractAnalysisResultService> listenerServiceClass;
        // 堆转储文件的目录提供者
        private LeakDirectoryProvider leakDirectoryProvider;
        // 检测内存泄漏的延迟时间,单位为毫秒
        private long watchDurationMs = TimeUnit.SECONDS.toMillis(5);
        // 堆转储的阈值,当内存使用达到该阈值时触发堆转储
        private int heapDumpAfterRetainedReferenceCount = 7;
        // 堆转储的间隔时间,控制堆转储的频率
        private long heapDumpIntervalMs = TimeUnit.MINUTES.toMillis(5);
        // 是否在调试器连接时禁用检测
        private boolean disableDumpHeap = false;

        // 设置排除的引用集合
        public Builder excludedRefs(ExcludedRefs excludedRefs) {
            this.excludedRefs = excludedRefs;
            return this;
        }

        // 设置分析结果的监听器类
        public Builder listenerServiceClass(Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
            this.listenerServiceClass = listenerServiceClass;
            return this;
        }

        // 设置堆转储文件的目录提供者
        public Builder leakDirectoryProvider(LeakDirectoryProvider leakDirectoryProvider) {
            this.leakDirectoryProvider = leakDirectoryProvider;
            return this;
        }

        // 设置检测内存泄漏的延迟时间
        public Builder watchDuration(long duration, TimeUnit timeUnit) {
            this.watchDurationMs = timeUnit.toMillis(duration);
            return this;
        }

        // 设置堆转储的阈值
        public Builder heapDumpAfterRetainedReferenceCount(int heapDumpAfterRetainedReferenceCount) {
            this.heapDumpAfterRetainedReferenceCount = heapDumpAfterRetainedReferenceCount;
            return this;
        }

        // 设置堆转储的间隔时间
        public Builder heapDumpInterval(long interval, TimeUnit timeUnit) {
            this.heapDumpIntervalMs = timeUnit.toMillis(interval);
            return this;
        }

        // 设置是否在调试器连接时禁用检测
        public Builder disableDumpHeap(boolean disableDumpHeap) {
            this.disableDumpHeap = disableDumpHeap;
            return this;
        }

        // 构建 LeakCanaryConfig 实例
        public LeakCanaryConfig build() {
            return new LeakCanaryConfig(excludedRefs, listenerServiceClass, leakDirectoryProvider,
                    watchDurationMs, heapDumpAfterRetainedReferenceCount, heapDumpIntervalMs, disableDumpHeap);
        }
    }
}

四、配置管理模块的初始化流程

4.1 初始化入口

LeakCanary 的配置管理模块初始化通常从 LeakCanary.install() 方法开始,该方法会创建一个 RefWatcherBuilder 实例,并进行一系列的默认配置。以下是 LeakCanary.install() 方法的源码及注释:

// LeakCanary 类提供了安装 LeakCanary 的入口方法
public final class LeakCanary {
    // 安装 LeakCanary 并返回 RefWatcher 实例
    public static RefWatcher install(Application application) {
        // 创建 RefWatcherBuilder 实例,传入应用上下文
        return refWatcher(application)
               .listenerServiceClass(DisplayLeakService.class)
               .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
               .buildAndInstall();
    }

    // 创建 RefWatcherBuilder 实例
    public static RefWatcherBuilder refWatcher(Context context) {
        return new RefWatcherBuilder<>(context);
    }
}

4.2 配置参数设置

RefWatcherBuilder 中,可以通过链式调用的方式设置各种配置参数。例如:

RefWatcher refWatcher = LeakCanary.refWatcher(context)
       .excludedRefs(excludedRefs)
       .listenerServiceClass(MyAnalysisResultService.class)
       .watchDuration(10, TimeUnit.SECONDS)
       .buildAndInstall();

4.3 构建 RefWatcher 实例

在调用 RefWatcherBuilderbuildAndInstall() 方法时,会根据配置参数构建 RefWatcher 实例,并将其安装到应用中。以下是 buildAndInstall() 方法的部分关键源码及注释:

public RefWatcher buildAndInstall() {
    if (isInAnalyzerProcess(context)) {
        // 如果当前进程是分析进程,则不进行安装,直接返回禁用的 RefWatcher
        return RefWatcher.DISABLED;
    }
    // 启用显示泄漏信息的 Activity
    enableDisplayLeakActivity(context);
    // 创建 Android 资源提供者,用于获取字符串资源等
    AndroidResourceProvider resourceProvider = new AndroidResourceProvider(context);
    // 创建调试器控制实例,用于判断是否有调试器连接
    DebuggerControl debuggerControl = new AndroidDebuggerControl();
    // 创建堆转储器实例,用于将应用的内存快照保存到文件中
    HeapDumper heapDumper = new AndroidHeapDumper(context, resourceProvider);
    // 创建资源释放执行器,用于执行资源释放任务
    ResourceReleasingExecutor watchExecutor = new AndroidWatchExecutor(Looper.getMainLooper());
    // 创建 LeakCanary 内部管理实例,负责管理一些内部状态和操作
    LeakCanaryInternals leakCanaryInternals = new LeakCanaryInternals(context);
    if (listenerServiceClass != null) {
        // 如果设置了分析结果监听器类,则将其设置到 LeakCanary 内部管理实例中
        leakCanaryInternals.setListenerServiceClass(listenerServiceClass);
    }
    // 监听 Activity 的生命周期,当 Activity 销毁时进行内存泄漏检测
    leakCanaryInternals.watchActivities(context, watchExecutor);
    // 监听 Fragment 的生命周期,当 Fragment 销毁时进行内存泄漏检测
    leakCanaryInternals.watchFragments(context, watchExecutor);
    // 创建堆转储触发器,根据配置的条件触发堆转储操作
    HeapDumpTrigger heapDumpTrigger = new HeapDumpTrigger(context, watchExecutor, debuggerControl, heapDumper,
            leakCanaryInternals.leakDirectoryProvider, excludedRefs, watchDurationMs,
            heapDumpAfterRetainedReferenceCount, heapDumpIntervalMs, disableDumpHeap);
    // 创建 RefWatcher 实例,用于实际的内存泄漏检测
    RefWatcher refWatcher = new RefWatcher(watchExecutor, debuggerControl, heapDumpTrigger, heapDumper,
            excludedRefs);
    // 将 RefWatcher 实例设置为全局的 RefWatcher
    LeakCanaryInternals.setRefWatcher(context, refWatcher);
    return refWatcher;
}

五、常见配置项的使用及原理

5.1 排除引用配置

5.1.1 排除引用的作用

在某些情况下,应用中可能存在一些对象或引用,它们由于业务逻辑的原因,在表面上看起来像是内存泄漏,但实际上是正常的。通过排除这些引用,可以避免 LeakCanary 对这些对象产生误判,提高检测的准确性。

5.1.2 配置排除引用的方法

可以使用 ExcludedRefs.Builder 来构建排除引用的集合,并通过 RefWatcherBuilderexcludedRefs() 方法进行设置。以下是一个示例:

ExcludedRefs excludedRefs = new ExcludedRefs.Builder()
       .instanceField(MyClass.class, "myField")
       .staticField(MyOtherClass.class, "staticField")
       .build();

RefWatcher refWatcher = LeakCanary.refWatcher(context)
       .excludedRefs(excludedRefs)
       .buildAndInstall();
5.1.3 排除引用的原理

在内存泄漏检测过程中,LeakCanary 会遍历对象的引用链,当发现某个对象的引用链中包含排除的引用时,会将该对象排除在内存泄漏检测之外。具体实现可以在 HeapAnalyzer 类中找到相关逻辑:

// HeapAnalyzer 类用于分析堆转储文件,检测内存泄漏
public class HeapAnalyzer {
    // 排除的引用集合
    private final ExcludedRefs excludedRefs;

    // 构造函数,初始化排除的引用集合
    public HeapAnalyzer(ExcludedRefs excludedRefs) {
        this.excludedRefs = excludedRefs;
    }

    // 分析堆转储文件,检测内存泄漏
    public AnalysisResult analyze(HeapDump heapDump) {
        // 遍历对象的引用链
        for (ObjectInstance objectInstance : heapDump.getInstances()) {
            // 检查对象的引用链中是否包含排除的引用
            if (isExcluded(objectInstance)) {
                // 如果包含排除的引用,则跳过该对象的检测
                continue;
            }
            // 进行内存泄漏检测
            // ...
        }
        // 返回分析结果
        return analysisResult;
    }

    // 检查对象的引用链中是否包含排除的引用
    private boolean isExcluded(ObjectInstance objectInstance) {
        // 获取对象的类
        ClassObj classObj = objectInstance.getClassObj();
        // 获取排除的实例字段引用
        Set<String> excludedInstanceFields = excludedRefs.getExcludedInstanceFields().get(classObj.getClazz());
        if (excludedInstanceFields != null) {
            // 检查对象的实例字段是否包含排除的引用
            for (FieldInstance fieldInstance : objectInstance.getFields()) {
                if (excludedInstanceFields.contains(fieldInstance.getName())) {
                    return true;
                }
            }
        }
        // 获取排除的静态字段引用
        Set<String> excludedStaticFields = excludedRefs.getExcludedStaticFields().get(classObj.getClazz());
        if (excludedStaticFields != null) {
            // 检查对象的静态字段是否包含排除的引用
            for (StaticFieldInstance staticFieldInstance : classObj.getStaticFields()) {
                if (excludedStaticFields.contains(staticFieldInstance.getName())) {
                    return true;
                }
            }
        }
        return false;
    }
}

5.2 分析结果监听器配置

5.2.1 分析结果监听器的作用

当 LeakCanary 检测到内存泄漏并完成分析后,需要将分析结果传递给开发者进行处理。分析结果监听器就是负责接收和处理这些分析结果的组件,开发者可以根据需要自定义监听器的行为,例如显示通知、保存报告等。

5.2.2 配置分析结果监听器的方法

可以通过 RefWatcherBuilderlistenerServiceClass() 方法设置分析结果监听器类。以下是一个示例:

RefWatcher refWatcher = LeakCanary.refWatcher(context)
       .listenerServiceClass(MyAnalysisResultService.class)
       .buildAndInstall();
5.2.3 分析结果监听器的原理

当 LeakCanary 完成内存泄漏分析后,会通过 AbstractAnalysisResultService 的子类(即分析结果监听器类)来处理分析结果。以下是 AbstractAnalysisResultService 类的部分源码及注释:

// AbstractAnalysisResultService 类是分析结果监听器的抽象基类
public abstract class AbstractAnalysisResultService extends IntentService {
    // 构造函数,初始化服务名称
    protected AbstractAnalysisResultService(String name) {
        super(name);
    }

    // 处理分析结果的方法
    protected abstract void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result);

    // 处理服务启动的意图
    @Override
    protected final void onHandleIntent(@Nullable Intent intent) {
        if (intent == null) {
            CanaryLog.d("AbstractAnalysisResultService received a null intent, ignoring.");
            return;
        }
        // 从意图中获取堆转储文件和分析结果
        HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAP_DUMP_EXTRA);
        AnalysisResult result = (AnalysisResult) intent.getSerializableExtra(RESULT_EXTRA);
        if (heapDump == null || result == null) {
            CanaryLog.d("AbstractAnalysisResultService received an intent without both heap dump and result, ignoring.");
            return;
        }
        // 调用子类实现的处理方法
        onHeapAnalyzed(heapDump, result);
    }

    // 发送分析结果到监听器
    public static void sendResultToListener(Context context,
                                            Class<? extends AbstractAnalysisResultService> listenerServiceClass,
                                            HeapDump heapDump, AnalysisResult result) {
        Intent intent = new Intent(context, listenerServiceClass);
        intent.putExtra(HEAP_DUMP_EXTRA, heapDump);
        intent.putExtra(RESULT_EXTRA, result);
        context.startService(intent);
    }
}

5.3 堆转储触发条件配置

5.3.1 堆转储触发条件的作用

堆转储是将应用的内存快照保存到文件中,以便后续分析。合理配置堆转储触发条件可以在保证检测准确性的同时,减少不必要的性能开销。

5.3.2 配置堆转储触发条件的方法

可以通过 RefWatcherBuilderheapDumpAfterRetainedReferenceCount()heapDumpIntervalMs 方法设置堆转储的阈值和间隔时间。以下是一个示例:

RefWatcher refWatcher = LeakCanary.refWatcher(context)
       .heapDumpAfterRetainedReferenceCount(10)
       .heapDumpInterval(2, TimeUnit.MINUTES)
       .buildAndInstall();
5.3.3 堆转储触发条件的原理

HeapDumpTrigger 类负责根据配置的触发条件触发堆转储操作。以下是 HeapDumpTrigger 类的部分源码及注释:

// HeapDumpTrigger 类负责根据配置的触发条件触发堆转储操作
public class HeapDumpTrigger {
    // 应用上下文
    private final Context context;
    // 资源释放执行器,用于执行资源释放任务
    private final ResourceReleasingExecutor watchExecutor;
    // 调试器控制实例,用于判断是否有调试器连接
    private final DebuggerControl debuggerControl;
    // 堆转储器实例,用于将应用的内存快照保存到文件中
    private final HeapDumper heapDumper;
    // 堆转储文件的目录提供者
    private final LeakDirectoryProvider leakDirectoryProvider;
    // 排除的引用集合
    private final ExcludedRefs excludedRefs;
    // 检测内存泄漏的延迟时间,单位为毫秒
    private final long watchDurationMs;
    // 堆转储的阈值,当内存使用达到该阈值时触发堆转储
    private final int heapDumpAfterRetainedReferenceCount;
    // 堆转储的间隔时间,控制堆转储的频率
    private final long heapDumpIntervalMs;
    // 是否在调试器连接时禁用检测
    private final boolean disableDumpHeap;
    // 上次堆转储的时间戳
    private long lastHeapDumpTime;
    // 保留的引用计数
    private int retainedReferenceCount;

    // 构造函数,初始化各种配置参数
    public HeapDumpTrigger(Context context, ResourceReleasingExecutor watchExecutor,
                           DebuggerControl debuggerControl, HeapDumper heapDumper,
                           LeakDirectoryProvider leakDirectoryProvider, ExcludedRefs excludedRefs,
                           long watchDurationMs, int heapDumpAfterRetainedReferenceCount,
                           long heapDumpIntervalMs, boolean disableDumpHeap) {
        this.context = context;
        this.watchExecutor = watchExecutor;
        this.debuggerControl = debuggerControl;
        this.heapDumper = heapDumper;
        this.leakDirectoryProvider = leakDirectoryProvider;
        this.excludedRefs = excludedRefs;
        this.watchDurationMs = watchDurationMs;
        this.heapDumpAfterRetainedReferenceCount = heapDumpAfterRetainedReferenceCount;
        this.heapDumpIntervalMs = heapDumpIntervalMs;
        this.disableDumpHeap = disableDumpHeap;
        this.lastHeapDumpTime = 0;
        this.retainedReferenceCount = 0;
    }

    // 监控对象是否泄漏
    public void watch(Object watchedReference, String referenceName) {
        // 创建一个 KeyedWeakReference 实例,用于弱引用被监控的对象
        final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, referenceName,
                queue, randomKey());
        // 执行任务,检查对象是否泄漏
        watchExecutor.execute(() -> checkForLeak(reference, referenceName));
    }

    // 检查对象是否泄漏
    private void checkForLeak(KeyedWeakReference reference, String referenceName) {
        // 判断是否有调试器连接,如果有则不进行检测
        if (debuggerControl.isDebuggerAttached() && disableDumpHeap) {
            return;
        }
        // 等待一段时间,让垃圾回收器有机会回收对象
        waitForGc();
        // 判断对象是否已经被回收
        if (isGone(reference)) {
            CanaryLog.d("%s: GC'd", referenceName);
            return;
        }
        // 增加保留的引用计数
        retainedReferenceCount++;
        // 判断是否满足堆转储的条件
        if (shouldDumpHeap()) {
            // 触发堆转储操作
            dumpHeap(reference, referenceName);
            // 重置保留的引用计数
            retainedReferenceCount = 0;
            // 更新上次堆转储的时间戳
            lastHeapDumpTime = System.currentTimeMillis();
        }
    }

    // 判断是否满足堆转储的条件
    private boolean shouldDumpHeap() {
        // 判断保留的引用计数是否达到阈值
        boolean reachedRetainedCountThreshold = retainedReferenceCount >= heapDumpAfterRetainedReferenceCount;
        // 判断堆转储的间隔时间是否满足要求
        boolean passedIntervalThreshold = System.currentTimeMillis() - lastHeapDumpTime >= heapDumpIntervalMs;
        return reachedRetainedCountThreshold && passedIntervalThreshold;
    }

    // 触发堆转储操作
    private void dumpHeap(KeyedWeakReference reference, String referenceName) {
        // 进行堆转储操作,将应用的内存快照保存到文件中
        File heapDumpFile = heapDumper.dumpHeap();
        if (heapDumpFile == null) {
            CanaryLog.d("Could not dump heap.");
            return;
        }
        // 创建 HeapDump 实例,包含堆转储文件和相关信息
        HeapDump heapDump = new HeapDump(heapDumpFile, reference.key, referenceName, excludedRefs);
        // 发送分析结果到监听器
        AbstractAnalysisResultService.sendResultToListener(context, listenerServiceClass, heapDump, null);
    }
}

六、配置管理模块的高级用法

6.1 动态配置

在某些情况下,可能需要在应用运行时动态修改 LeakCanary 的配置。可以通过重新构建 RefWatcher 实例来实现动态配置。以下是一个示例:

// 原始配置
RefWatcher refWatcher = LeakCanary.refWatcher(context)
       .excludedRefs(excludedRefs)
       .listenerServiceClass(MyAnalysisResultService.class)
       .buildAndInstall();

// 动态修改配置
ExcludedRefs newExcludedRefs = new ExcludedRefs.Builder()
       .instanceField(NewClass.class, "newField")
       .build();

RefWatcher newRefWatcher = LeakCanary.refWatcher(context)
       .excludedRefs(newExcludedRefs)
       .listenerServiceClass(MyAnalysisResultService.class)
       .buildAndInstall();

6.2 自定义配置项

如果 LeakCanary 提供的配置项无法满足需求,可以通过继承 RefWatcherBuilder 类来添加自定义配置项。以下是一个示例:

// 自定义 RefWatcherBuilder 类
public class CustomRefWatcherBuilder extends RefWatcherBuilder<CustomRefWatcherBuilder> {
    // 自定义配置项
    private boolean customOption;

    public CustomRefWatcherBuilder(Context context) {
        super(context);
    }

    // 设置自定义配置项
    public CustomRefWatcherBuilder customOption(boolean customOption) {
        this.customOption = customOption;
        return this;
    }

    @Override
    public RefWatcher buildAndInstall() {
        // 在构建 RefWatcher 实例之前,可以根据自定义配置项进行一些操作
        if (customOption) {
            // 执行自定义操作
        }
        return super.buildAndInstall();
    }
}

// 使用自定义 RefWatcherBuilder
RefWatcher refWatcher = new CustomRefWatcherBuilder(context)
       .excludedRefs(excludedRefs)
       .listenerServiceClass(MyAnalysisResultService.class)
       .customOption(true)
       .buildAndInstall();

七、配置管理模块的性能优化

7.1 减少不必要的配置

在配置 LeakCanary 时,应尽量减少不必要的排除引用和配置项。过多的排除引用会增加内存泄漏检测的复杂度,降低检测效率。

7.2 合理设置堆转储触发条件

堆转储是一个比较耗时的操作,会对应用的性能产生一定的