腾讯性能监控框架Matrix源码分析(十四)ResourcePlugin之内存泄露监控

786 阅读5分钟

什么是内存泄露?长生命周期对象持有段生命周期对象导致短生命周期对象该销毁的时候无法销毁,于是这块内存就留在系统中就是内存泄露。比如我们的activity上下文如果被一个生命周期更长的单例或者全局对象持有,那么此actvity就无法被正常销毁,matrix正是利用了这一点。

我们进入源码ResourcePlugin

public class ResourcePlugin extends Plugin {
    private static final String TAG = "Matrix.ResourcePlugin";

    private final ResourceConfig mConfig;
    private ActivityRefWatcher mWatcher = null;

    public ResourcePlugin(ResourceConfig config) {
        mConfig = config;
    }

    public static void activityLeakFixer(Application application) {
        // Auto break the path from Views in their holder to gc root when activity is destroyed.
        application.registerActivityLifecycleCallbacks(new ActivityLifeCycleCallbacksAdapter() {
            @Override
            public void onActivityDestroyed(Activity activity) {
                ActivityLeakFixer.fixInputMethodManagerLeak(activity);
                ActivityLeakFixer.unbindDrawables(activity);
                ActivityLeakFixer.fixViewLocationHolderLeakApi28(activity);
            }
        });
    }

    public ActivityRefWatcher getWatcher() {
        return mWatcher;
    }

    @Override
    public void init(Application app, PluginListener listener) {
        super.init(app, listener);
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            MatrixLog.e(TAG, "API is low Build.VERSION_CODES.ICE_CREAM_SANDWICH(14), ResourcePlugin is not supported");
            unSupportPlugin();
            return;
        }
        mWatcher = new ActivityRefWatcher(app, this);
    }

    @Override
    public void start() {
        super.start();
        if (!isSupported()) {
            MatrixLog.e(TAG, "ResourcePlugin start, ResourcePlugin is not supported, just return");
            return;
        }
        mWatcher.start();
    }

    @Override
    public void stop() {
        super.stop();
        if (!isSupported()) {
            MatrixLog.e(TAG, "ResourcePlugin stop, ResourcePlugin is not supported, just return");
            return;
        }
        mWatcher.stop();
    }

真正的观测者是mWatcher 我们进入ActivityRefWatcher

private ActivityRefWatcher(Application app,
                           ResourcePlugin resourcePlugin,
                           ComponentFactory componentFactory) {
    super(app, FILE_CONFIG_EXPIRED_TIME_MILLIS, resourcePlugin.getTag(), resourcePlugin);
    this.mResourcePlugin = resourcePlugin;
    final ResourceConfig config = resourcePlugin.getConfig();
    mHandlerThread = MatrixHandlerThread.getNewHandlerThread("matrix_res", Thread.NORM_PRIORITY); // avoid blocking default matrix thread
    mHandler = new Handler(mHandlerThread.getLooper());
    mDumpHprofMode = config.getDumpHprofMode();
    mBgScanTimes = config.getBgScanIntervalMillis();
    mFgScanTimes = config.getScanIntervalMillis();
    mDetectExecutor = componentFactory.createDetectExecutor(config, mHandlerThread);
    mMaxRedetectTimes = config.getMaxRedetectTimes();
    mLeakProcessor = componentFactory.createLeakProcess(mDumpHprofMode, this);
    mDestroyedActivityInfos = new ConcurrentLinkedQueue<>();
}

初始化配置参数,handler,线程,最核心的是生成了mDestroyedActivityInfos,来记录销毁的activity我们进入看看

public class DestroyedActivityInfo {
    public final String mKey;
    public final String mActivityName;

    public final WeakReference<Activity> mActivityRef;
    public int mDetectedCount = 0;

    public DestroyedActivityInfo(String key, Activity activity, String activityName) {
        mKey = key;
        mActivityName = activityName;
        mActivityRef = new WeakReference<>(activity);
    }
}

activity的最终包裹在mActivityRef也就是弱引用WeakReference内,为什么呢?因为弱引用的包裹一旦gc就会被回收,除非你内存泄露了,以此特性正好可以判断是否泄露。那么在什么时机记录销毁对象?

我们进入start

@Override
public void start() {
    //停止,防止重复监测
    stopDetect();
    final Application app = mResourcePlugin.getApplication();
    if (app != null) {
        //注册活动生命周期回调
        app.registerActivityLifecycleCallbacks(mRemovedActivityMonitor);
        scheduleDetectProcedure();
        MatrixLog.i(TAG, "watcher is started.");
    }
}

没错,系统的生命周期监控,无侵入无耦合,进入看看

private final Application.ActivityLifecycleCallbacks mRemovedActivityMonitor = new ActivityLifeCycleCallbacksAdapter() {

    @Override
    public void onActivityDestroyed(Activity activity) {
        pushDestroyedActivityInfo(activity);
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                triggerGc();
            }
        }, 2000);
    }
};

根据系统的回调记录每个一个活动销毁的周期 同时在2s后进行一次gc

pushDestroyedActivityInfo被内部可以看到对象的生成规则并且通知阻塞的线程重新唤醒

private void pushDestroyedActivityInfo(Activity activity) {
    final String activityName = activity.getClass().getName();
    if ((mDumpHprofMode == ResourceConfig.DumpMode.NO_DUMP || mDumpHprofMode == ResourceConfig.DumpMode.AUTO_DUMP)
            && !mResourcePlugin.getConfig().getDetectDebugger()
            && isPublished(activityName)) {
        MatrixLog.i(TAG, "activity leak with name %s had published, just ignore", activityName);
        return;
    }
    //唯一标识
    final UUID uuid = UUID.randomUUID();
    final StringBuilder keyBuilder = new StringBuilder();
    keyBuilder.append(ACTIVITY_REFKEY_PREFIX).append(activityName)
            .append('_').append(Long.toHexString(uuid.getMostSignificantBits())).append(Long.toHexString(uuid.getLeastSignificantBits()));
    final String key = keyBuilder.toString();
    final DestroyedActivityInfo destroyedActivityInfo
            = new DestroyedActivityInfo(key, activity, activityName);
    mDestroyedActivityInfos.add(destroyedActivityInfo);
    synchronized (mDestroyedActivityInfos) {
        //通知等待的线程锁开始行动,去查一下泄露没有
        mDestroyedActivityInfos.notifyAll();
    }
    MatrixLog.d(TAG, "mDestroyedActivityInfos add %s", activityName);
}

然后执行scheduleDetectProcedure

private void scheduleDetectProcedure() {
    mDetectExecutor.executeInBackground(mScanDestroyedActivitiesTask);
}

在子线程执行了mScanDestroyedActivitiesTask 就是一个可以不断重试调用线程

 private final RetryableTask mScanDestroyedActivitiesTask = new RetryableTask() {

        @Override
        public Status execute() {
            // 没有销毁就阻塞卡在这
            if (mDestroyedActivityInfos.isEmpty()) {
                MatrixLog.i(TAG, "DestroyedActivityInfo is empty! wait...");
                synchronized (mDestroyedActivityInfos) {
                    try {
                        while (mDestroyedActivityInfos.isEmpty()) {
                            mDestroyedActivityInfos.wait();
                        }
                    } catch (Throwable ignored) {
                        // Ignored.
                    }
                }
                MatrixLog.i(TAG, "DestroyedActivityInfo is NOT empty! resume check");
               //上层封装,会再次执行线程
               return Status.RETRY;
            }

            // debugg模式直接生成可忽略
            if (Debug.isDebuggerConnected() && !mResourcePlugin.getConfig().getDetectDebugger()) {
                MatrixLog.w(TAG, "debugger is connected, to avoid fake result, detection was delayed.");
                return Status.RETRY;
            }

            //主打调用gc,正常情况没有泄露的对象都会被清理掉
            triggerGc();
            triggerGc();
            triggerGc();

            final Iterator<DestroyedActivityInfo> infoIt = mDestroyedActivityInfos.iterator();

            while (infoIt.hasNext()) {
                final DestroyedActivityInfo destroyedActivityInfo = infoIt.next();
                //不处理模式只是打个log
                if ((mDumpHprofMode == ResourceConfig.DumpMode.NO_DUMP || mDumpHprofMode == ResourceConfig.DumpMode.AUTO_DUMP)
                        && !mResourcePlugin.getConfig().getDetectDebugger()
                        && isPublished(destroyedActivityInfo.mActivityName)) {
                    MatrixLog.v(TAG, "activity with key [%s] was already published.", destroyedActivityInfo.mActivityName);
                    infoIt.remove();
                    continue;
                }
                triggerGc();
                //没有对象说明没有泄露,都被gc了
                if (destroyedActivityInfo.mActivityRef.get() == null) {
                    // The activity was recycled by a gc triggered outside.
                    MatrixLog.v(TAG, "activity with key [%s] was already recycled.", destroyedActivityInfo.mKey);
                    infoIt.remove();
                    continue;
                }               
                //走到这里代表已经泄露了,泄露次数加1
                ++destroyedActivityInfo.mDetectedCount;
                //为了保证数据准确,这边泄露次数超过10次才认为真正的泄露
                if (destroyedActivityInfo.mDetectedCount < mMaxRedetectTimes
                        && !mResourcePlugin.getConfig().getDetectDebugger()) {
                 
                    triggerGc();
                    continue;
                }

                MatrixLog.i(TAG, "activity with key [%s] was suspected to be a leaked instance. mode[%s]", destroyedActivityInfo.mKey, mDumpHprofMode);
                
                if (mLeakProcessor == null) {
                    throw new NullPointerException("LeakProcessor not found!!!");
                }

                triggerGc();
                //走到这里最终被判刑,那么开始处理数据吧
                if (mLeakProcessor.process(destroyedActivityInfo)) {
                    MatrixLog.i(TAG, "the leaked activity [%s] with key [%s] has been processed. stop polling", destroyedActivityInfo.mActivityName, destroyedActivityInfo.mKey);
                    infoIt.remove();
                }
            }

            triggerGc();
            return Status.RETRY;
        }
    };

根据我们的模式抽取了不同的处理逻辑

public enum DumpMode {
  NO_DUMP, // report only
  AUTO_DUMP, // auto dump hprof
  MANUAL_DUMP, // notify only
  SILENCE_ANALYSE, // dump and analyse hprof when screen off
  FORK_DUMP, // fork dump hprof immediately
  FORK_ANALYSE, // fork dump and analyse hprof immediately
  LAZY_FORK_ANALYZE, // fork dump immediately but analyze hprof until the screen is off
}
  • NO_DUMP:直接对内存泄露阶段检测到的泄露项名称进行上报,不进行引用链的分析,自然就不需要dump hprof。
  • AUTO_DUMP:检测到内存泄露之后,先dump并到子进程中进行hprof文件裁剪,最后将泄露项名称、裁剪后的hprof等信息打成zip进行上报
  • MANUAL_DUMP:检测到内存泄露之后,发送通知。用户点击通知之后进行dump、分析。这里dump与分析都是发生在fork出来的进程中。
  • SILENCE_ANALYSE:锁屏时进行dump、分析
  • FORK_DUMP:fork出子进程,在子进程中进行dump,然后进行裁剪上报。
  • FORK_ANALYSE:先 fork dump,然后在fork出来的进程中进行分析。
  • LAZY_FORK_ANALYZE:先 fork dump,然后在锁屏时进行分析。

**ResourcesCanary 提供的内存泄露检测机制比较多,但都是几种基本功能的组合: **

  1. 是否需要dump hprof

    • Debug接口直接dump
    • 利用COW机制的fork dump
  2. 是否需要在客户端进行hprof文件的分析:

    • 不需要客户端分析(dump后裁剪hprof文件,带文件进行上报)

    • 需要客户端分析:

      • 使用HAHA分析
      • 使用native代码分析

这里先看看 AUTO_DUMP 模式

public class AutoDumpProcessor extends BaseLeakProcessor {

    private static final String TAG = "Matrix.LeakProcessor.AutoDump";

    public AutoDumpProcessor(ActivityRefWatcher watcher) {
        super(watcher);
    }

    @Override
    public boolean process(DestroyedActivityInfo destroyedActivityInfo) {
        // 如果是 AUTO_DUMP 模式,那么就去自动分析 heap 文件了,与 LeakCanary 类似
        final File hprofFile = getHeapDumper().dumpHeap(true);
        if (hprofFile != null) {
           //标记是为了不重复分析,毕竟耗时 getWatcher().markPublished(destroyedActivityInfo.mActivityName);
            getWatcher().triggerGc();
            // dump hprof 文件
            final HeapDump heapDump = new HeapDump(hprofFile, destroyedActivityInfo.mKey, destroyedActivityInfo.mActivityName);
            // 处理 dump 出来的 hprof 文件
            getHeapDumpHandler().process(heapDump);
        } else {
            MatrixLog.i(TAG, "heap dump for further analyzing activity with key [%s] was failed, just ignore.",
                    destroyedActivityInfo.mKey);
        }
        return true;
    }
}

getHeapDumper().dumpHeap经过层层调用最终会走到

系统api
/**
 * Dump "hprof" data to the specified file.  This may cause a GC.
 *
 * @param fileName Full pathname of output file (e.g. "/sdcard/dump.hprof").
 * @throws UnsupportedOperationException if the VM was built without
 *         HPROF support.
 * @throws IOException if an error occurs while opening or writing files.
 */
public static void dumpHprofData(String fileName) throws IOException {
    VMDebug.dumpHprofData(fileName);
}

之后进行剪裁处理 getHeapDumpHandler().process(heapDump);

protected AndroidHeapDumper.HeapDumpHandler getHeapDumpHandler() {
    if (mHeapDumpHandler == null) {
        mHeapDumpHandler = new AndroidHeapDumper.HeapDumpHandler() {
            @Override
            public void process(HeapDump result) {
                CanaryWorkerService.shrinkHprofAndReport(mWatcher.getContext(), result);
            }
        };
    }

    return mHeapDumpHandler;
}

也就是交给了CanaryWorkerService,这是啥玩意?

public class CanaryWorkerService extends MatrixJobIntentService {
    private static final String TAG = "Matrix.CanaryWorkerService";

    private static final int JOB_ID = 0xFAFBFCFD;
    private static final String ACTION_SHRINK_HPROF = "com.tencent.matrix.resource.worker.action.SHRINK_HPROF";
    private static final String EXTRA_PARAM_HEAPDUMP = "com.tencent.matrix.resource.worker.param.HEAPDUMP";

    public static void shrinkHprofAndReport(Context context, HeapDump heapDump) {
        final Intent intent = new Intent(context, CanaryWorkerService.class);
        intent.setAction(ACTION_SHRINK_HPROF);
        intent.putExtra(EXTRA_PARAM_HEAPDUMP, heapDump);
        enqueueWork(context, CanaryWorkerService.class, JOB_ID, intent);
    }

    @Override
    protected void onHandleWork(Intent intent) {
        if (intent != null) {
            final String action = intent.getAction();
            if (ACTION_SHRINK_HPROF.equals(action)) {
                try {
                    intent.setExtrasClassLoader(this.getClassLoader());
                    final HeapDump heapDump = (HeapDump) intent.getSerializableExtra(EXTRA_PARAM_HEAPDUMP);
                    if (heapDump != null) {
                        doShrinkHprofAndReport(heapDump);
                    } else {
                        MatrixLog.e(TAG, "failed to deserialize heap dump, give up shrinking and reporting.");
                    }
                } catch (Throwable thr) {
                    MatrixLog.printErrStackTrace(TAG, thr,  "failed to deserialize heap dump, give up shrinking and reporting.");
                }
            }
        }
    }
    //剪裁
    private void doShrinkHprofAndReport(HeapDump heapDump) {
        final File hprofDir = heapDump.getHprofFile().getParentFile();
        final File shrinkedHProfFile = new File(hprofDir, getShrinkHprofName(heapDump.getHprofFile()));
        final File zipResFile = new File(hprofDir, getResultZipName("dump_result_" + android.os.Process.myPid()));
        final File hprofFile = heapDump.getHprofFile();
        ZipOutputStream zos = null;
        try {
            long startTime = System.currentTimeMillis();
            new HprofBufferShrinker().shrink(hprofFile, shrinkedHProfFile);
            MatrixLog.i(TAG, "shrink hprof file %s, size: %dk to %s, size: %dk, use time:%d",
                    hprofFile.getPath(), hprofFile.length() / 1024, shrinkedHProfFile.getPath(), shrinkedHProfFile.length() / 1024, (System.currentTimeMillis() - startTime));

            zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipResFile)));

            final ZipEntry resultInfoEntry = new ZipEntry("result.info");
            final ZipEntry shrinkedHProfEntry = new ZipEntry(shrinkedHProfFile.getName());

            zos.putNextEntry(resultInfoEntry);
            final PrintWriter pw = new PrintWriter(new OutputStreamWriter(zos, Charset.forName("UTF-8")));
            pw.println("# Resource Canary Result Infomation. THIS FILE IS IMPORTANT FOR THE ANALYZER !!");
            pw.println("sdkVersion=" + Build.VERSION.SDK_INT);
            pw.println("manufacturer=" + Matrix.with().getPluginByClass(ResourcePlugin.class).getConfig().getManufacture());
            pw.println("hprofEntry=" + shrinkedHProfEntry.getName());
            pw.println("leakedActivityKey=" + heapDump.getReferenceKey());
            pw.flush();
            zos.closeEntry();

            zos.putNextEntry(shrinkedHProfEntry);
            copyFileToStream(shrinkedHProfFile, zos);
            zos.closeEntry();

            shrinkedHProfFile.delete();
            hprofFile.delete();

            MatrixLog.i(TAG, "process hprof file use total time:%d", (System.currentTimeMillis() - startTime));

            CanaryResultService.reportHprofResult(this, zipResFile.getAbsolutePath(), heapDump.getActivityName());
        } catch (IOException e) {
            MatrixLog.printErrStackTrace(TAG, e, "");
        } finally {
            closeQuietly(zos);
        }
    }

其实就是一个JobIntentService,方便我们后台处理一些任务,在高版本的系统谷歌已经禁止普通的service长时间运行了,超过几秒就会被杀死甚至提醒anr,现在官方推荐workmanager,底层其实根本不同api用到了JobService AlarmManager库,具体可以百度,我们只需要知道这个服务一定会走onHandleWork

并且注意到这是一个新的进程喔,因为剪裁资源,开新进程不影响原app体验

<service
        android:name=".CanaryWorkerService"
        android:process=":res_can_worker"
        android:permission="android.permission.BIND_JOB_SERVICE"
        android:exported="false">
</service>

最终走到new HprofBufferShrinker().shrink(hprofFile, shrinkedHProfFile)进行剪裁

public void shrink(File hprofIn, File hprofOut) throws IOException {
    FileInputStream is = null;
    OutputStream os = null;
    try {
        is = new FileInputStream(hprofIn);
        os = new BufferedOutputStream(new FileOutputStream(hprofOut));
        final HprofReader reader = new HprofReader(new BufferedInputStream(is));
        reader.accept(new HprofInfoCollectVisitor());
        // Reset.
        is.getChannel().position(0);
        reader.accept(new HprofKeptBufferCollectVisitor());
        // Reset.
        is.getChannel().position(0);
        reader.accept(new HprofBufferShrinkVisitor(new HprofWriter(os)));
    } finally {
        if (os != null) {
            try {
                os.close();
            } catch (Throwable thr) {
                // Ignored.
            }
        }
        if (is != null) {
            try {
                is.close();
            } catch (Throwable thr) {
                // Ignored.
            }
        }
    }
}

我对 hprof 结构还不熟悉,查了些文档,大概情况如下 里面的逻辑大致分为几步:

  • 收集 hprof 文件的 bitmap 与 string 对象(索引id)
  • 收集 string 与 bitmap 的字段(索引id),重要的是 bitmap 的 mBuffer 的索引
  • 去除非 bitmap 的 buffer重复的bitmap的buffer(String 的除外) 更详细的剪裁细节可以看看这篇文章 剖析hprof文件的两种主要裁剪流派

经过这些步骤之后,hprof 文件就裁剪完成了。裁剪完成之后,会执行 CanaryResultService,回调到:

private void doReportHprofResult(String resultPath, String activityName) {
        try {
            final JSONObject resultJson = new JSONObject();
//            resultJson = DeviceUtil.getDeviceInfo(resultJson, getApplication());
			// resultPath 裁剪后的hprof压缩文件路径
            resultJson.put(SharePluginInfo.ISSUE_RESULT_PATH, resultPath);
            resultJson.put(SharePluginInfo.ISSUE_ACTIVITY_NAME, activityName);
            Plugin plugin =  Matrix.with().getPluginByClass(ResourcePlugin.class);

            if (plugin != null) {
                plugin.onDetectIssue(new Issue(resultJson));
            }
        } catch (Throwable thr) {
            MatrixLog.printErrStackTrace(TAG, thr, "unexpected exception, skip reporting.");
        }
    }

最终会调用,plugin 的 onDetectIssue 方法,这里我们可以进行对应的处理。 因为分析泄露泄露链路非常耗时,一般都是放到服务器去做了。

当然也是支持本地分析的,我们看下FORK_ANALYSE模式 也就是ForkAnalyseProcessor

@Override
public boolean process(DestroyedActivityInfo destroyedActivityInfo) {
    if (Build.VERSION.SDK_INT > ResourceConfig.FORK_DUMP_SUPPORTED_API_GUARD) {
        MatrixLog.e(TAG, "cannot fork-dump with unsupported API version " + Build.VERSION.SDK_INT);
        publishIssue(
                SharePluginInfo.IssueType.ERR_UNSUPPORTED_API,
                ResourceConfig.DumpMode.FORK_ANALYSE,
                destroyedActivityInfo.mActivityName, destroyedActivityInfo.mKey,
                "Unsupported API", "0");
        return false;
    }

    getWatcher().triggerGc();
     //分析
    if (dumpAndAnalyse(
            destroyedActivityInfo.mActivityName,
            destroyedActivityInfo.mKey
    )) {
        getWatcher().markPublished(destroyedActivityInfo.mActivityName, false);
        return true;
    }

    return false;
}

进入dumpAndAnalyse

private boolean dumpAndAnalyse(String activity, String key) {

    /* Dump */

    final long dumpStart = System.currentTimeMillis();

    final File hprof = getDumpStorageManager().newHprofFile();

    if (hprof != null) {
          //fork新的进程去进行dump,这样不阻塞app业务
        if (!MemoryDumpManager.dumpBlock(hprof.getPath())) {
            MatrixLog.e(TAG, String.format("heap dump for further analyzing activity with key [%s] was failed, just ignore.",
                    key));
            return false;
        }
    }

    if (hprof == null || hprof.length() == 0) {
        publishIssue(
                SharePluginInfo.IssueType.ERR_FILE_NOT_FOUND,
                ResourceConfig.DumpMode.FORK_ANALYSE,
                activity, key, "FileNull", "0");
        MatrixLog.e(TAG, "cannot create hprof file");
        return false;
    }

    MatrixLog.i(TAG, String.format("dump cost=%sms refString=%s path=%s",
            System.currentTimeMillis() - dumpStart, key, hprof.getPath()));

    /* Analyse */

    try {
        final long analyseStart = System.currentTimeMillis();
        //分析
        final ActivityLeakResult leaks = analyze(hprof, key);
        MatrixLog.i(TAG, String.format("analyze cost=%sms refString=%s",
                System.currentTimeMillis() - analyseStart, key));

        if (leaks.mLeakFound) {
            final String leakChain = leaks.toString();
            publishIssue(
                    SharePluginInfo.IssueType.LEAK_FOUND,
                    ResourceConfig.DumpMode.FORK_ANALYSE,
                    activity, key, leakChain,
                    String.valueOf(System.currentTimeMillis() - dumpStart));
            MatrixLog.i(TAG, leakChain);
        } else {
            MatrixLog.i(TAG, "leak not found");
        }

    } catch (OutOfMemoryError error) {
        publishIssue(
                SharePluginInfo.IssueType.ERR_ANALYSE_OOM,
                ResourceConfig.DumpMode.FORK_ANALYSE,
                activity, key, "OutOfMemoryError",
                "0");
        MatrixLog.printErrStackTrace(TAG, error.getCause(), "");
    } finally {
        //noinspection ResultOfMethodCallIgnored
        hprof.delete();
    }

    /* Done */

    return true;
}

进入analyze

protected ActivityLeakResult analyze(File hprofFile, String referenceKey) {
    setAnalyzing(true);
    final HeapSnapshot heapSnapshot;
    ActivityLeakResult result;
    String manufacture = Matrix.with().getPluginByClass(ResourcePlugin.class).getConfig().getManufacture();
    final ExcludedRefs excludedRefs = AndroidExcludedRefs.createAppDefaults(Build.VERSION.SDK_INT, manufacture).build();
    try {
        heapSnapshot = new HeapSnapshot(hprofFile);
        result = new ActivityLeakAnalyzer(referenceKey, excludedRefs).analyze(heapSnapshot);
    } catch (IOException e) {
        result = ActivityLeakResult.failure(e, 0);
    }
    getWatcher().triggerGc();
    setAnalyzing(false);
    return result;
}

最终走到ActivityLeakAnalyzer 底层的分析调用了haha库,具体原理可以了解一下,这个算法已经被淘汰,目前不是最优解决,现在有更快的算法比如shark等

其他模式大同小异,可以进源码详细看看

private ActivityLeakResult checkForLeak(HeapSnapshot heapSnapshot, String refKey) {
    long analysisStartNanoTime = System.nanoTime();

    try {
        final Snapshot snapshot = heapSnapshot.getSnapshot();
        final Instance leakingRef = findLeakingReference(refKey, snapshot);

        // False alarm, weak reference was cleared in between key check and heap dump.
        if (leakingRef == null) {
            return ActivityLeakResult.noLeak(AnalyzeUtil.since(analysisStartNanoTime));
        }

        return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
    } catch (Throwable e) {
        e.printStackTrace();
        return ActivityLeakResult.failure(e, AnalyzeUtil.since(analysisStartNanoTime));
    }
}

另外,ResourceCanary 里面还提供了一个 Activity 级别泄露解决方案 ActivityLeakFixer :它可以解决 IME 泄露问题;以及在 Activity 销毁时清除所有 View 的 Drawable、Listener 等,这样泄露的就是空壳了。

public static void activityLeakFixer(Application application) {
    // Auto break the path from Views in their holder to gc root when activity is destroyed.
    application.registerActivityLifecycleCallbacks(new ActivityLifeCycleCallbacksAdapter() {
        @Override
        public void onActivityDestroyed(Activity activity) {
            ActivityLeakFixer.fixInputMethodManagerLeak(activity);
            ActivityLeakFixer.unbindDrawables(activity);
            ActivityLeakFixer.fixViewLocationHolderLeakApi28(activity);
        }
    });
}

/**
 * In Android P, ViewLocationHolder has an mRoot field that is not cleared in its clear() method.
 * Introduced in https://github.com/aosp-mirror/platform_frameworks_base/commit
 * /86b326012813f09d8f1de7d6d26c986a909d
 *
 * This leaks triggers very often when accessibility is on. To fix this leak we need to clear
 * the ViewGroup.ViewLocationHolder.sPool pool. Unfortunately Android P prevents accessing that
 * field through reflection. So instead, we call [ViewGroup#addChildrenForAccessibility] with
 * a view group that has 32 children (32 being the pool size), which as result fills in the pool
 * with 32 dumb views that reference a dummy context instead of an activity context.
 *
 * This fix empties the pool on every activity destroy and every AndroidX fragment view destroy.
 * You can support other cases where views get detached by calling directly
 * [ViewLocationHolderLeakFix.clearStaticPool].
 */
public static void fixViewLocationHolderLeakApi28(Context destContext) {
    if (Build.VERSION.SDK_INT != Build.VERSION_CODES.P) {
        return;
    }

    try {
        Context application = destContext.getApplicationContext();
        if (sGroupAndOutChildren == null) {
            ViewGroup sViewGroup = new FrameLayout(application);
            // ViewLocationHolder.MAX_POOL_SIZE = 32
            for (int i = 0; i < 32; i++) {
                View childView = new View(application);
                sViewGroup.addView(childView);
            }
            sGroupAndOutChildren = new Pair<>(sViewGroup, new ArrayList<View>());
        }

        sGroupAndOutChildren.first.addChildrenForAccessibility(sGroupAndOutChildren.second);
    } catch (Throwable e) {
        MatrixLog.printErrStackTrace(TAG, e, "fixViewLocationHolderLeakApi28 err");
    }
}



public static void fixInputMethodManagerLeak(Context destContext) {
    final long startTick = System.currentTimeMillis();

    do {
        if (destContext == null) {
            break;
        }

        final InputMethodManager imm = (InputMethodManager) destContext.getSystemService(Context.INPUT_METHOD_SERVICE);
        if (imm == null) {
            break;
        }

        final String[] viewFieldNames = new String[]{"mCurRootView", "mServedView", "mNextServedView"};
        for (String viewFieldName : viewFieldNames) {
            try {
                final Field paramField = imm.getClass().getDeclaredField(viewFieldName);
                if (!paramField.isAccessible()) {
                    paramField.setAccessible(true);
                }
                final Object obj = paramField.get(imm);
                if (obj instanceof View) {
                    final View view = (View) obj;
                    // Context held by InputMethodManager is what we want to split from reference chain.
                    if (view.getContext() == destContext) {
                        // Break the gc path.
                        paramField.set(imm, null);
                    } else {
                        // The first time we meet the context we don't want to split indicates that the rest context
                        // is not need to be concerned, so we break the loop in this case.
                        MatrixLog.i(TAG, "fixInputMethodManagerLeak break, context is not suitable, get_context=" + view.getContext() + " dest_context=" + destContext);
                        break;
                    }
                }
            } catch (Throwable thr) {
                MatrixLog.e(TAG, "failed to fix InputMethodManagerLeak, %s", thr.toString());
            }
        }
    } while (false);

    MatrixLog.i(TAG, "fixInputMethodManagerLeak done, cost: %s ms.", System.currentTimeMillis() - startTick);
}

public static void unbindDrawables(Activity ui) {
    final long startTick = System.currentTimeMillis();
    if (ui != null && ui.getWindow() != null && ui.getWindow().peekDecorView() != null) {
        final View viewRoot = ui.getWindow().peekDecorView().getRootView();
        try {
            unbindDrawablesAndRecycle(viewRoot);
            if (viewRoot instanceof ViewGroup) {
                ((ViewGroup) viewRoot).removeAllViews();
            }
        } catch (Throwable thr) {
            MatrixLog.w(TAG, "caught unexpected exception when unbind drawables.", thr);
        }
    } else {
        MatrixLog.i(TAG, "unbindDrawables, ui or ui's window is null, skip rest works.");
    }
    MatrixLog.i(TAG, "unbindDrawables done, cost: %s ms.", System.currentTimeMillis() - startTick);
}

本章到此结束,大致原理我们已经很清楚,其实最核心的问题还在于剪裁,如何找到最短引用链的算法,后续跟进时间会详细展开分析,你学废了吗?