Summary:
private final ResourceConfig mConfig;
private ActivityRefWatcher mWatcher = null;
@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();
mWatcher.start();
}
@Override
public void stop() {
super.stop();
mWatcher.stop();
}
@Override
public void destroy() {
super.destroy();
mWatcher.destroy();
}
public ActivityRefWatcher(Application app,
final ResourcePlugin resourcePlugin) {
this(app, resourcePlugin, new ComponentFactory());
}
private ActivityRefWatcher(Application app,
ResourcePlugin resourcePlugin,
ComponentFactory componentFactory) {
super(app, FILE_CONFIG_EXPIRED_TIME, resourcePlugin.getTag(), resourcePlugin);
this.mResourcePlugin = resourcePlugin;
final ResourceConfig config = resourcePlugin.getConfig();
final Context context = app;
HandlerThread handlerThread = MatrixHandlerThread.getDefaultHandlerThread();
mDumpHprofMode = config.getDumpHprofMode();
mBgScanTimes = config.getBgScanIntervalMillis();
mFgScanTimes = config.getScanIntervalMillis();
mContentIntent = config.getNotificationContentIntent();
mDetectExecutor = componentFactory.createDetectExecutor(config, handlerThread);
mMaxRedetectTimes = config.getMaxRedetectTimes();
mDumpStorageManager = componentFactory.createDumpStorageManager(context);
mHeapDumper = componentFactory.createHeapDumper(context, mDumpStorageManager);
mHeapDumpHandler = componentFactory.createHeapDumpHandler(context, config);
mDestroyedActivityInfos = new ConcurrentLinkedQueue<>();
}
@Override
public void start() {
stopDetect();//main
final Application app = mResourcePlugin.getApplication();
if (app != null) {
app.registerActivityLifecycleCallbacks(mRemovedActivityMonitor);//main
AppActiveMatrixDelegate.INSTANCE.addListener(this);//main-->onForeground
scheduleDetectProcedure();//main
MatrixLog.i(TAG, "watcher is started.");
}
}
@Override
public void onForeground(boolean isForeground) {
if (isForeground) {
MatrixLog.i(TAG, "we are in foreground, modify scan time[%sms].", mFgScanTimes);
mDetectExecutor.clearTasks();
mDetectExecutor.setDelayMillis(mFgScanTimes);
mDetectExecutor.executeInBackground(mScanDestroyedActivitiesTask);//main
} else {
MatrixLog.i(TAG, "we are in background, modify scan time[%sms].", mBgScanTimes);
mDetectExecutor.setDelayMillis(mBgScanTimes);
}
}
private void scheduleDetectProcedure() {
mDetectExecutor.executeInBackground(mScanDestroyedActivitiesTask);
}
@Override
public void stop() {
stopDetect();//main
MatrixLog.i(TAG, "watcher is stopped.");
}
private void stopDetect() {
final Application app = mResourcePlugin.getApplication();
if (app != null) {
app.unregisterActivityLifecycleCallbacks(mRemovedActivityMonitor);
AppActiveMatrixDelegate.INSTANCE.removeListener(this);
unscheduleDetectProcedure();
}
}
private final Application.ActivityLifecycleCallbacks mRemovedActivityMonitor = new ActivityLifeCycleCallbacksAdapter() {
@Override
public void onActivityDestroyed(Activity activity) {
pushDestroyedActivityInfo(activity);
}
}
private final RetryableTask mScanDestroyedActivitiesTask = new RetryableTask() {
@Override
public Status execute() {
// If destroyed activity list is empty, just wait to save power.
if (mDestroyedActivityInfos.isEmpty()) {
MatrixLog.i(TAG, "DestroyedActivityInfo isEmpty!");
return Status.RETRY;
}
final WeakReference<Object> sentinelRef = new WeakReference<>(new Object());
triggerGc();
if (sentinelRef.get() != null) {
// System ignored our gc request, we will retry later.
MatrixLog.d(TAG, "system ignore our gc request, wait for next detection.");
return Status.RETRY;
}
final Iterator<DestroyedActivityInfo> infoIt = mDestroyedActivityInfos.iterator();
while (infoIt.hasNext()) {
final DestroyedActivityInfo destroyedActivityInfo = infoIt.next();
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;
}
++destroyedActivityInfo.mDetectedCount;
if (destroyedActivityInfo.mDetectedCount < mMaxRedetectTimes
|| !mResourcePlugin.getConfig().getDetectDebugger()) {
// Although the sentinel tell us the activity should have been recycled,
// system may still ignore it, so try again until we reach max retry times.
MatrixLog.i(TAG, "activity with key [%s] should be recycled but actually still \n"
\+ "exists in %s times, wait for next detection to confirm.",
destroyedActivityInfo.mKey, destroyedActivityInfo.mDetectedCount);
continue;
}
MatrixLog.i(TAG, "activity with key [%s] was suspected to be a leaked instance. mode[%s]", destroyedActivityInfo.mKey, mDumpHprofMode);
if (mDumpHprofMode == ResourceConfig.DumpMode.SILENCE_DUMP) {
....
mResourcePlugin.onDetectIssue(new Issue(resultJson));
} else if (mDumpHprofMode == ResourceConfig.DumpMode.AUTO_DUMP) {
final File hprofFile = mHeapDumper.dumpHeap();//main
if (hprofFile != null) {
markPublished(destroyedActivityInfo.mActivityName);
final HeapDump heapDump = new HeapDump(hprofFile, destroyedActivityInfo.mKey, destroyedActivityInfo.mActivityName);
mHeapDumpHandler.process(heapDump);//main
infoIt.remove();
} else {
MatrixLog.i(TAG, "heap dump for further analyzing activity with key [%s] was failed, just ignore.",
destroyedActivityInfo.mKey);
infoIt.remove();
}
} else if (mDumpHprofMode == ResourceConfig.DumpMode.MANUAL_DUMP) {
......
MatrixLog.i(TAG, "show notification for notify activity leak. %s", destroyedActivityInfo.mActivityName);
}
This class is ported from LeakCanary.
public File dumpHeap() {
final File hprofFile = mDumpStorageManager.newHprofFile();
try {
Debug.dumpHprofData(hprofFile.getAbsolutePath());
return hprofFile;
} catch (Exception e) {
MatrixLog.printErrStackTrace(TAG, e, "failed to dump heap into file: %s.", hprofFile.getAbsolutePath());
return null;
}
}
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);//main
} 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);//main
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=" + Build.MANUFACTURER);
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));
//回调通知shrinkedHProfile文件路径等信息
CanaryResultService.reportHprofResult(this, zipResFile.getAbsolutePath(), heapDump.getActivityName());
}}
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());//main
// Reset.
is.getChannel().position(0);
reader.accept(new HprofKeptBufferCollectVisitor());//main
// Reset.
is.getChannel().position(0);
reader.accept(new HprofBufferShrinkVisitor(new HprofWriter(os)));//main
}
public HprofReader(InputStream in) {
mStreamIn = in;
}
public void accept(HprofVisitor hv) throws IOException {
acceptHeader(hv);
acceptRecord(hv);
hv.visitEnd();
}
private void acceptHeader(HprofVisitor hv) throws IOException {
final String text = IOUtil.readNullTerminatedString(mStreamIn);
final int idSize = IOUtil.readBEInt(mStreamIn);
if (idSize <= 0 || idSize >= (Integer.MAX_VALUE >> 1)) {
throw new IOException("bad idSize: " + idSize);
}
final long timestamp = IOUtil.readBELong(mStreamIn);
mIdSize = idSize;
hv.visitHeader(text, idSize, timestamp);
}
class HprofInfoCollectVisitor extends HprofVisitor {
@Override
public void visitHeader(String text, int idSize, long timestamp) {
mIdSize = idSize;
mNullBufferId = ID.createNullID(idSize);
}
}
对hprof文件的生成和分析相分离,分析位于matrix-resource-canary/matrix-resource-canary-analyzer模块中的com.tencent.matrix.resource.analyzer.CLIMain.main方法
//CLIMain
public static void main(String[] args) {
if (args.length == 0) {
printUsage(System.out);
System.exit(ERROR_NEED_ARGUMENTS);
}
try {
final CommandLine cmdline = new DefaultParser().parse(sOptions, args);
if (cmdline.hasOption(OPTION_HELP.mOption.getLongOpt())) {
printUsage(System.out);
System.exit(ERROR_SUCCESS);
}
parseArguments(cmdline);
doAnalyze();//main
}
}
private static void doAnalyze() throws IOException {
// Then do analyzing works and output into directory or zip according to the option. Besides,
// store extra info into the result json by the way.
analyzeAndStoreResult(tempHprofFile, sdkVersion, manufacturer, leakedActivityKey, extraInfo);
}
private static void analyzeAndStoreResult(File hprofFile, int sdkVersion, String manufacturer,
String leakedActivityKey, JSONObject extraInfo) throws IOException {
final HeapSnapshot heapSnapshot = new HeapSnapshot(hprofFile);
final ExcludedRefs excludedRefs = AndroidExcludedRefs.createAppDefaults(sdkVersion, manufacturer).build();
final ActivityLeakResult activityLeakResult
= new ActivityLeakAnalyzer(leakedActivityKey, excludedRefs).analyze(heapSnapshot);//main
DuplicatedBitmapResult duplicatedBmpResult = DuplicatedBitmapResult.noDuplicatedBitmap(0);
if (sdkVersion < 26) {//main
final ExcludedBmps excludedBmps = AndroidExcludedBmpRefs.createDefaults().build();
duplicatedBmpResult = new DuplicatedBitmapAnalyzer(mMinBmpLeakSize, excludedBmps).analyze(heapSnapshot);//main
} else {
System.err.println("\n ! SDK version of target device is larger or equal to 26, "
\+ "which is not supported by DuplicatedBitmapAnalyzer.");
}
public static class ComponentFactory {
protected RetryableTaskExecutor createDetectExecutor(ResourceConfig config, HandlerThread handlerThread) {
return new RetryableTaskExecutor(config.getScanIntervalMillis(), handlerThread);
}
protected DumpStorageManager createDumpStorageManager(Context context) {
return new DumpStorageManager(context);
}
protected AndroidHeapDumper createHeapDumper(Context context, DumpStorageManager dumpStorageManager) {
return new AndroidHeapDumper(context, dumpStorageManager);
}
protected AndroidHeapDumper.HeapDumpHandler createHeapDumpHandler(final Context context, ResourceConfig resourceConfig) {
return new AndroidHeapDumper.HeapDumpHandler() {
@Override
public void process(HeapDump result) {//main
CanaryWorkerService.shrinkHprofAndReport(context, result);//main
}
};
}
}