首页 > 免root版 > gg修改器要用root吗_gg修改器一定要root吗
gg修改器要用root吗_gg修改器一定要root吗
  • gg修改器要用root吗_gg修改器一定要root吗

  • 大小:10.72MB日期:2024-03-29 22:13:31
  • 语言:简体中文系统:Android
绿色无毒,安全可靠!部分设备误报拦截请通过!

应用详情

大家好,今天小编为大家分享关于gg修改器要用root吗_gg修改器一定要root吗的内容,赶快来一起来看看吧。

Matrix-android 当前监控范围包括:应用安装包大小、帧率变化、启动耗时、卡顿、慢方法、SQLite操作优化、文件读写、内存泄漏等。整个库主要由 5 个组件构成:

  1. APK Checker。针对 APK 安装包的分析检测工具,根据一系列设定好的规则,检测 APK 是否存在
    特定的问题,并输出较为详细的检测结果报告,用于分析排查问题以及版本追踪
  2. Resource Canary。基于 WeakReference 的特性和 Square Haha 库开发的 Activity 泄漏和Bitmap 重复创建检测工具
  3. Trace Canary。监控界面流畅性、启动耗时、页面切换耗时、慢函数及卡顿等问题
  4. IO Canary。检测文件 IO 问题,包括文件 IO 监控和 Closeable Leak 监控
  5. SQLite Lint。按官方最佳实践自动化检测 SQLite 语句的使用质量

使用

使用Matrix 的使用方式很简单,在 Application 中初始化后启动即可:

Matrix.Builder builder = new Matrix.Builder(this);
// 添加需要的插件
builder.plugin(new TracePlugin(...));
builder.plugin(new ResourcePlugin(...));
builder.plugin(new IOCanaryPlugin(...));
builder.plugin(new SQLiteLintPlugin(...));
// 初始化
Matrix matrix = Matrix.init(builder.build());
// 启动
matrix.startAllPlugins();

Matrix 类相当于整个库的统一对外接口,资源监控、IO 监控、卡顿监控等功能实现是由其它具体的Plugin 完成的。
也可以不在 Application 中启动全部插件,而是在某个场景中启动特定的插件,比如:

public class TestTraceMainActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
// 启动插件
Plugin plugin = Matrix.with().getPluginByClass(TracePlugin.class);
if (!plugin.isPluginStarted()) {
plugin.start();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
// 停止插件
Plugin plugin = Matrix.with().getPluginByClass(TracePlugin.class);
if (plugin.isPluginStarted()) {
plugin.stop();
}
}
}

每个具体的 Plugin 都会实现 IPlugin 接口:

public interface IPlugin {
Application getApplication();
void init(Application application, PluginListener pluginListener);
void start();
void stop();
void destroy();
String getTag();
// 在应用可见/不可见时回调
void onForeground(boolean isForeground);
}

可以通过 PluginListener 监听 Plugin 的生命周期变化,或在 Plugin 上报问题时回调:

public interface PluginListener {
void onInit(Plugin plugin);
void onStart(Plugin plugin);
void onStop(Plugin plugin);
void onDestroy(Plugin plugin);
void onReportIssue(Issue issue);
}

上报的问题使用实体类 Issue 包装,Issue 包含 tag、type 等通用字段,详细信息可以通过 JSON 对象content 获取:

public class Issue {
private int type;
private String tag;
private String key;
private JSONObject content;
private Plugin plugin;
}

源码简析

Matrix
Matrix 是一个单例类,在构造函数执行时,Matrix 内部的所有 Plugin 都会被初始化:

public class Matrix {
private final HashSet<Plugin> plugins;
private Matrix(Application app, PluginListener listener, HashSet<Plugin> plugins) {
this.plugins = plugins;
AppActiveMatrixDelegate.INSTANCE.init(application); // 下面会分析
// 初始化所有 Plugin,并回调 pluginListener
for (Plugin plugin : plugins) {
plugin.init(application, pluginListener);
pluginListener.onInit(plugin);
}
}
}

Plugin

Plugin 是一个抽象类,每次执行 init / start / stop / destroy 等方法时都会更新状态,并回调PluginListener:

public abstract class Plugin implements IPlugin,
IssuePublisher.OnIssueDetectListener, IAppForeground {

private int status = PLUGIN_CREATE;

@Override
public void init(Application app, PluginListener listener) {
status = PLUGIN_INITED;
AppActiveMatrixDelegate.INSTANCE.addListener(this); // 下面会分析
}

@Override
public void start() {
status = PLUGIN_STARTED;
pluginListener.onStart(this);
}
...
}

如果某个具体 Plugin 上报了一个问题,父类 Plugin 还会对该 Issue 填充 tag、type、process、time等通用字段,并回调 PluginListener 的 onReportIssue 方法:

@Override
public void onDetectIssue(Issue issue) {
issue.setPlugin(this);
JSONObject content = issue.getContent();
// 拼接 tag、type、process、time 等通用字段
content.put(Issue.ISSUE_REPORT_TAG, issue.getTag());
content.put(Issue.ISSUE_REPORT_TYPE, issue.getType());
content.put(Issue.ISSUE_REPORT_PROCESS,
MatrixUtil.getProcessName(application));
content.put(Issue.ISSUE_REPORT_TIME, System.currentTimeMillis());
// 回调
pluginListener.onReportIssue(issue);
}

AppActiveMatrixDelegate

Matrix 和 Plugin 都监听了 AppActiveMatrixDelegate,它的主要作用是在应用可见/不可见时通知观察者:

public enum AppActiveMatrixDelegate {

INSTANCE; // 单例

// 观察者列表
private final Set<IAppForeground> listeners = new HashSet();

// 应用可见时通知观察者
private void onDispatchForeground(String visibleScene) {
handler.post(() -> {
isAppForeground = true;
synchronized (listeners) {
for (IAppForeground listener : listeners) {
listener.onForeground(true);
}
}
}
}

// 应用不可见时通知观察者,逻辑和上面的一样
private void onDispatchBackground(String visibleScene) {
...
}
}

判断应用是否可见的逻辑是通过 ActivityLifecycleCallbacks 接口实现的:

private final class Controller implements
Application.ActivityLifecycleCallbacks, ComponentCallbacks2 {

@Override
public void onActivityStarted(Activity activity) {
// 应用可见
updateScene(activity);
onDispatchForeground(getVisibleScene());
}

@Override
public void onActivityStopped(Activity activity) {
// 没有可见的 Activity 了,相当于进入了后台
if (getTopActivityName() == null) {
onDispatchBackground(getVisibleScene());
}
}
...

@Override
public void onTrimMemory(int level) {
// 应用 UI 不可见
if (level == TRIM_MEMORY_UI_HIDDEN && isAppForeground) { // fallback
onDispatchBackground(visibleScene);
}
}
}

总结

Matrix-android 主要包含 5 个组件:APK Checker、Resource Canary、Trace Canary、IO Canary、SQLite Lint。其中 APK Checker 独立运行;其它 4 个模块需要在 Application 中,通过统一对外接口Matrix 配置完成后执行。每一个模块相当于一个 Plugin,在执行初始化、启动、停止、销毁、报告问题等操作时,都会回调 PluginListener,并更新状态。

每一个 Issue 都有 tag、type、process、time 等 4 个通用字段。

可以监听 AppActiveMatrixDelegate,在应用可见/不可见时,回调 onForeground 方法,以及执行相应的操作。应用可见指的是存在可见的 Activity,应用不可见指的是没有可见的 Activity,或者内存不足了,应用的 UI 不可见

2.内存泄漏监控及原理介绍

ResourceCanary介绍

Matrix 的内存泄漏监控是由 ResourceCanary 实现的,准确的说,ResourceCanary 只能实现 Activity的内存泄漏检测,但在出现 Activity 内存泄漏时,可以选择 dump 一个堆转储文件,通过该文件,可以分析应用是否存在重复的 Bitmap。

使用

ResourceCanary 是基于 WeakReference 特性和 Square Haha 库开发的 Activity 泄漏和 Bitmap 重复创建检测工具,使用之前,需要进行如下配置:

Matrix.Builder builder = new Matrix.Builder(this);
// 用于在用户点击生成的问题通知时,通过这个 Intent 跳转到指定的 Activity
Intent intent = new Intent();
intent.setClassName(this.getPackageName(),
"com.tencent.mm.ui.matrix.ManualDumpActivity");

ResourceConfig resourceConfig = new ResourceConfig.Builder()
.dynamicConfig(new DynamicConfigImplDemo()) // 用于动态获取一些自定义的选项, 不同 Plugin 有不同的选项
.setAutoDumpHprofMode(ResourceConfig.DumpMode.AUTO_DUMP) // 自动生成 Hprof 文件 //
.setDetectDebuger(true) //matrix test code
.setNotificationContentIntent(intent) // 问题通知
.build();
builder.plugin(new ResourcePlugin(resourceConfig));

// 这个类可用于修复一些内存泄漏问题
ResourcePlugin.activityLeakFixer(this);

如果想要在具体的 Activity 中检测内存泄漏,那么获取 Plugin 并执行 start方法(一般在 onCreate 方法中执行)即可:

Plugin plugin = Matrix.with().getPluginByClass(ResourcePlugin.class);
if (!plugin.isPluginStarted()) {
plugin.start();
}

捕获到问题后,会上报信息如下:

{
"tag": "memory",
"type": 0,
"process": "sample.tencent.matrix",
"time": 1590396618440,
"activity": "sample.tencent.matrix.resource.TestLeakActivity",
}

如果 DumpMode 为 AUTO_DUMP,还会生成一个压缩文件,里面包含一个堆转储文件和一个result.info 文件,可以根据 result.info 文件发现具体是哪一个 Activity 泄漏了:

{
"tag":"memory",
"process":"com.tencent.mm", "resultZipPath":"/storage/emulated/0/Android/.tencent.mm/cache/matrix_r esource/dump_result_17400_20170713183615.zip",
"activity":"com.tencent.mm.plugin.setting.ui.setting.SettingsUI",
}

配置

ResourcePlugin 执行之前,需要通过 ResourceConfig 配置,配置选项有:

public static final class Builder {
private DumpMode mDefaultDumpHprofMode = DEFAULT_DUMP_HPROF_MODE;
private IDynamicConfig dynamicConfig;
private Intent mContentIntent;
private boolean mDetectDebugger = false;
}

其中, ContentIntent 用于发送通知。
DumpMode 用于控制检测到问题后的行为,可选值有:

  1. NO_DUMP,是一个轻量级的模式,会回调 Plugin 的 onDetectIssue 方法,但只报告出现内存泄漏问题的 Activity 的名称
  2. SILENCE_DUMP,和 NO_DUMP 类似,但会回调 IActivityLeakCallback
  3. MANUAL_DUMP,用于生成一个通知,点击后跳转到对应的 Activity,Activity 由 ContentIntent指定
  4. AUTO_DUMP,用于生成堆转储文件IDynamicConfig 是一个接口,可用于动态获取一些自定义的选项值:

public interface IDynamicConfig {
String get(String key, String defStr);
int get(String key, int defInt);
long get(String key, long defLong);
boolean get(String key, boolean defBool);
float get(String key, float defFloat);
}

和 Resource Canary 相关的选项有:

enum ExptEnum {
//resource
clicfg_matrix_resource_detect_interval_millis, // 后台线程轮询间隔
clicfg_matrix_resource_detect_interval_millis_bg, // 应用不可见时的轮询间隔
clicfg_matrix_resource_max_detect_times, // 重复检测多次后才认为出现了内存泄漏,避 免误判
clicfg_matrix_resource_dump_hprof_enable, // 没见代码有用到
}

实现该接口对应的方法,即可通过 ResourceConfig 获取上述选项的值:

public final class ResourceConfig {
// 后台线程轮询间隔默认为 1min
private static final long DEFAULT_DETECT_INTERVAL_MILLIS = TimeUnit.MINUTES.toMillis(1);
// 应用不可见时,后台线程轮询间隔默认为 1min
private static final long DEFAULT_DETECT_INTERVAL_MILLIS_BG = TimeUnit.MINUTES.toMillis(20);
// 默认重复检测 10 次后,如果依然能获取到 Activity ,才认为出现了内存泄漏
private static final int DEFAULT_MAX_REDETECT_TIMES = 10;
public long getScanIntervalMillis() { ... }
public long getBgScanIntervalMillis() { ... }
public int getMaxRedetectTimes() { ... }
}

3.内存泄漏监控源码分析

修复内存泄漏

在开始监测 Activity 内存泄漏之前,Resource Canary 首先会尝试修复可能的内存泄漏问题,它是通过监听 ActivityLifeCycleCallbacks 实现的,在 Activity 回调 onDestroy 时,它会尝试解除 Activity 和InputMethodManager、View 之间的引用关系:

public static void activityLeakFixer(Application application) {
application.registerActivityLifecycleCallbacks(new ActivityLifeCycleCallbacksAdapter() {
@Override
public void onActivityDestroyed(Activity activity) {
ActivityLeakFixer.fixInputMethodManagerLeak(activity);
ActivityLeakFixer.unbindDrawables(activity);
}
});
}

对于 InputMethodManager,它可能引用了 Activity 中的某几个 View,因此,将它和这几个 View 解除引用关系即可:

public static void fixInputMethodManagerLeak(Context destContext) {
final InputMethodManager imm = (InputMethodManager)
destContext.getSystemService(Context.INPUT_METHOD_SERVICE);
final String[] viewFieldNames = new String[]{"mCurRootView", "mServedView", "mNextServedView"};
for (String viewFieldName : viewFieldNames) {
final Field paramField = imm.getClass().getDeclaredField(viewFieldName);
...
// 如果 IMM 引用的 View 引用了该 Activity,则切断引用关系
if (view.getContext() == destContext) {
paramField.set(imm, null);
}
}
}

对于 View,它可能通过监听器或 Drawable 的形式关联 Activity,因此,我们需要把每一个可能的引用关系解除掉:

public static void unbindDrawables(Activity ui) {
final View viewRoot = ui.getWindow().peekDecorView().getRootView();
unbindDrawablesAndRecycle(viewRoot);
}
private static void unbindDrawablesAndRecycle(View view) {
// 解除通用的 View 引用关系
recycleView(view);
// 不同类型的 View 可能有不同的引用关系,一一处理即可
if (view instanceof ImageView) {
recycleImageView((ImageView) view);
}

if (view instanceof TextView) {
recycleTextView((TextView) view);
}
...
}
// 将 Listener、Drawable 等可能存在的引用关系切断
private static void recycleView(View view) {
view.setOnClickListener(null);
view.setOnFocusChangeListener(null);
view.getBackground().setCallback(null);
view.setBackgroundDrawable(null);
...
}

监测内存泄漏

具体的监测工作,ResourcePlugin 交给了 ActivityRefWatcher 来完成。
ActivityRefWatcher 主要的三个方法:start、stop、destroy 分别用于启动监听线程、停止监听线程、结束监听。以 start 为例:

public class ActivityRefWatcher extends FilePublisher implements Watcher, IAppForeground {
@Override
public void start() {
stopDetect();
final Application app = mResourcePlugin.getApplication();
if (app != null) {
// 监听 Activity 的 onDestroy 回调,记录 Activity 信息
app.registerActivityLifecycleCallbacks(mRemovedActivityMonitor);
// 监听 onForeground 回调,以便根据应用可见状态修改轮询间隔时长
AppActiveMatrixDelegate.INSTANCE.addListener(this);
// 启动监听线程
scheduleDetectProcedure();
}
}
}

记录Activity信息

其中 mRemovedActivityMonitor 用于在 Activity 回调 onDestroy 时记录 Activity 信息,主要包括Activity 的类名和一个根据 UUID 生成的 key:

// 用于记录 Activity 信息
private final ConcurrentLinkedQueue<DestroyedActivityInfo>
mDestroyedActivityInfos;

private final Application.ActivityLifecycleCallbacks mRemovedActivityMonitor = new ActivityLifeCycleCallbacksAdapter() {
@Override
public void onActivityDestroyed(Activity activity) {
pushDestroyedActivityInfo(activity);
}
};

// 在 Activity 销毁时,记录 Activity 信息
private void pushDestroyedActivityInfo(Activity activity) {
final String activityName = activity.getClass().getName();
final UUID uuid = UUID.randomUUID();
final String key = keyBuilder.toString(); // 根据 uuid 生成
final DestroyedActivityInfo destroyedActivityInfo = new DestroyedActivityInfo(key, activity, activityName);
mDestroyedActivityInfos.add(destroyedActivityInfo);
}

DestroyedActivityInfo 包含信息如下:

public class DestroyedActivityInfo {
public final String mKey; // 根据 uuid 生成
public final String mActivityName; // 类名
public final WeakReference<Activity> mActivityRef; // 弱引用
public int mDetectedCount = 0; // 重复检测次数,默认检测 10 次后,依然能通过弱引用获 取,才认为发生了内存泄漏
}

启动监听线程

线程启动后,应用可见时,默认每隔 1min(通过 IDynamicConfig 指定) 将轮询任务发送到默认的后台线程(MatrixHandlerThread)执行:

// 自定义的线程切换机制,用于将指定的任务延时发送到主线程/后台线程执行
private final RetryableTaskExecutor mDetectExecutor;

private ActivityRefWatcher(...) {
HandlerThread handlerThread = MatrixHandlerThread.getDefaultHandlerThread();
mDetectExecutor = new RetryableTaskExecutor(config.getScanIntervalMillis(), handlerThread);
}
private void scheduleDetectProcedure() {
// 将任务发送到 MatrixHandlerThread 执行
mDetectExecutor.executeInBackground(mScanDestroyedActivitiesTask);
}

下面看轮询任务 mScanDestroyedActivitiesTask,它是一个内部类,代码很长,我们一点一点分析

设置哨兵检测 GC 是否执行

首先,在上一篇文章关于原理的部分介绍过,ResourceCanary 会设置了一个哨兵元素,检测是否真的执行了 GC,如果没有,它不会往下执行:

private final RetryableTask mScanDestroyedActivitiesTask = new RetryableTask() {
@Override
public Status execute() {
// 创建指向一个临时对象的弱引用
final WeakReference<Object> sentinelRef = new WeakReference<>(new Object());
// 尝试触发 GC
triggerGc();
// 检测弱引用指向的对象是否存活来判断虚拟机是否真的执行了GC
if (sentinelRef.get() != null) {
// System ignored our gc request, we will retry later.
return Status.RETRY;
}
...
return Status.RETRY; // 返回 retry,这个任务会一直执行
}
};
private void triggerGc() {
Runtime.getRuntime().gc();
Runtime.getRuntime().runFinalization();
}

过滤已上报的Activity

接着,遍历所有 DestroyedActivityInfo,并标记该 Activity,避免重复报:

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

while (infoIt.hasNext()) {
if (!mResourcePlugin.getConfig().getDetectDebugger()
&& isPublished(destroyedActivityInfo.mActivityName) // 如果已标记,则跳 过
&& mDumpHprofMode != ResourceConfig.DumpMode.SILENCE_DUMP) {
infoIt.remove();
continue;
}
if (mDumpHprofMode == ResourceConfig.DumpMode.SILENCE_DUMP) {
if (mResourcePlugin != null &&
!isPublished(destroyedActivityInfo.mActivityName)) { // 如果已标记,则跳过
...
}
if (null != activityLeakCallback) { // 但还会回调 ActivityLeakCallback
activityLeakCallback.onLeak(destroyedActivityInfo.mActivityName, destroyedActivityInfo.mKey);
}
} else if (mDumpHprofMode == ResourceConfig.DumpMode.AUTO_DUMP) {
...
markPublished(destroyedActivityInfo.mActivityName); // 标记
} else if (mDumpHprofMode == ResourceConfig.DumpMode.MANUAL_DUMP) {
...
markPublished(destroyedActivityInfo.mActivityName); // 标记
} else { // NO_DUMP
...
markPublished(destroyedActivityInfo.mActivityName); // 标记
}
}

多次检测,避免误判

同时,在重复检测大于等于 mMaxRedetectTimes 次时(由 IDynamicConfig指定,默认为 10),如果还能获取到该 Activity 的引用,才会认为出现了内存泄漏问题:

while (infoIt.hasNext()) {
...
// 获取不到,Activity 已回收
if (destroyedActivityInfo.mActivityRef.get() == null) {
continue;
}

// Activity 未回收,可能出现了内存泄漏,但为了避免误判,需要重复检测多次,如果都能获取到 Activity,才认为出现了内存泄漏
// 只有在 debug 模式下,才会上报问题,否则只会打印一个 log
++destroyedActivityInfo.mDetectedCount;
if (destroyedActivityInfo.mDetectedCount < mMaxRedetectTimes
|| !mResourcePlugin.getConfig().getDetectDebugger()) {
MatrixLog.i(TAG, "activity with key [%s] should be recycled but actually still
"
+ "exists in %s times, wait for next detection to confirm.",
destroyedActivityInfo.mKey, destroyedActivityInfo.mDetectedCount);
continue;
}
}

需要注意的是,只有在 debug 模式下,才会上报问题,否则只会打印一个 log。上报问题
对于 silence_dump 和 no_dump 模式,它只会记录 Activity 名,并回调 onDetectIssue:

final JSONObject resultJson = new JSONObject();
resultJson.put(SharePluginInfo.ISSUE_ACTIVITY_NAME,
destroyedActivityInfo.mActivityName);
mResourcePlugin.onDetectIssue(new Issue(resultJson));

对于 manual_dump 模式,它会使用 ResourceConfig 指定的 Intent 生成一个通知:

...
Notification notification = buildNotification(context, builder);
notificationManager.notify(NOTIFICATION_ID, notification);

对于 auto_dump,它会自动生成一个 hprof 文件并对该文件进行分析:

final File hprofFile = mHeapDumper.dumpHeap();
final HeapDump heapDump = new HeapDump(hprofFile,
destroyedActivityInfo.mKey, destroyedActivityInfo.mActivityName);
mHeapDumpHandler.process(heapDump);

生成hprof文件

dumpHeap 方法做了两件事:生成一个文件,写入 Hprof 数据到文件中:

public File dumpHeap() {
final File hprofFile = mDumpStorageManager.newHprofFile();
Debug.dumpHprofData(hprofFile.getAbsolutePath());
}

之后 HeapDumpHandler 就会处理该文件:

protected AndroidHeapDumper.HeapDumpHandler createHeapDumpHandler(...) {
return new AndroidHeapDumper.HeapDumpHandler() {

@Override
public void process(HeapDump result) {
CanaryWorkerService.shrinkHprofAndReport(context, result);
}
};
}

处理流程如下:

private void doShrinkHprofAndReport(HeapDump heapDump) {
// 裁剪 hprof 文件
new HprofBufferShrinker().shrink(hprofFile, shrinkedHProfFile);
// 压缩裁剪后的 hprof 文件
zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipResFile)));
copyFileToStream(shrinkedHProfFile, zos);
// 删除旧文件
shrinkedHProfFile.delete(); hprofFile.delete();
// 上报结果
CanaryResultService.reportHprofResult(this, zipResFile.getAbsolutePath(), heapDump.getActivityName());
}
private void doReportHprofResult(String resultPath, String activityName) {
final JSONObject resultJson = new JSONObject();
resultJson.put(SharePluginInfo.ISSUE_RESULT_PATH, resultPath);
resultJson.put(SharePluginInfo.ISSUE_ACTIVITY_NAME, activityName);
Plugin plugin = Matrix.with().getPluginByClass(ResourcePlugin.class);
plugin.onDetectIssue(new Issue(resultJson));
}

可以看到,由于原始 hprof 文件很大,因此 Matrix 先对它做了一个裁剪优化,接着再压缩裁剪后的文件,并删除旧文件,最后回调 onDetectIssue,上报文件位置、Activity 名称等信息。

分析结果

示例

检测到内存泄漏问题后,ActivityRefWatcher 会打印日志如下:

activity with key
[MATRIX_RESCANARY_REFKEY_sample.tencent.matrix.resource.TestLeakActivity_...] was suspected to be a leaked instance. mode[AUTO_DUMP]

如果模式为 AUTO_DUMP,且设置了 mDetectDebugger 为 true,那么,还会生成一个 hprof 文件:

hprof: heap dump
"/storage/emulated/0/Android/data/sample.tencent.matrix/cache/matrix_resource/du
mp_*.hprof" starting...

裁剪压缩后在 /sdcard/data/[package name]/matrix_resource 文件夹下会生成一个 zip 文件,比如:

/storage/emulated/0/Android/data/sample.tencent.matrix/cache/matrix_resource/dum p_result_*.zip

zip 文件里包括一个 dump*shinked.hprof 文件和一个 result.info 文件,其中 result.info 包含设备信息和关键 Activity 的信息,比如:

# Resource Canary Result Infomation. THIS FILE IS IMPORTANT FOR THE ANALYZER !!
sdkVersion=23
manufacturer=vivo hprofEntry=dump_323ff84d95424d35b0f62ef6a3f95838_shrink.hprof
leakedActivityKey=MATRIX_RESCANARY_REFKEY_sample.tencent.matrix.resource.TestLea
kActivity_8c5f3e9db8b54a199da6cb2abf68bd12

拿到这个 zip 文件,输入路径参数,执行 matrix-resource-canary-analyzer 中的 CLIMain 程序,即可得到一个 result.json 文件:

{
"activityLeakResult": {
"failure": "null",
"referenceChain": ["static
sample.tencent.matrix.resource.TestLeakActivity testLeaks", ...,
"sample.tencent.matrix.resource.TestLeakActivity instance"],
"leakFound": true,
"className": "sample.tencent.matrix.resource.TestLeakActivity",
"analysisDurationMs": 185,
"excludedLeak": false
},
"duplicatedBitmapResult": {
"duplicatedBitmapEntries": [],
"mFailure": "null",
"targetFound": false,
"analyzeDurationMs": 387
}
}

注意,CLIMain 在分析重复 Bitmap 时,需要反射 Bitmap 中的 “mBuffer”字段,而这个字段在 API 26已经被移除了,因此,对于 API 大于等于 26 的设备,CLIMain 只能分析 Activity 内存泄漏,无法分析重复 Bitmap。

分析过程

下面简单分析一下 CLIMain 的执行过程,它是基于 Square Haha 开发的,执行过程分为 5 步:

  1. 根据 result.info 文件拿到 hprof 文件、sdkVersion 等信息
  2. 分析 Activity 泄漏
  3. 分析重复 Bitmap
  4. 生成 result.json 文件并写入结果
  5. 输出重复的 Bitmap 图像到本地

public final class CLIMain {
public static void main(String[] args) {
doAnalyze();
}

private static void doAnalyze() throws IOException {
// 从 result.info 文件中拿到 hprof 文件、sdkVersion 等信息,接着开始分析
analyzeAndStoreResult(tempHprofFile, sdkVersion, manufacturer, leakedActivityKey, extraInfo);
}

private static void analyzeAndStoreResult(...) {
// 分析 Activity 内存泄漏
ActivityLeakResult activityLeakResult = new ActivityLeakAnalyzer(leakedActivityKey, ).analyze(heapSnapshot);
// 分析重复 Bitmap
DuplicatedBitmapResult duplicatedBmpResult = new DuplicatedBitmapAnalyzer(mMinBmpLeakSize, excludedBmps).analyze(heapSnapshot);
// 生成 result.json 文件并写入结果
final File resultJsonFile = new File(outputDir, resultJsonName);
resultJsonPW.println(resultJson.toString());

// 输出重复的 Bitmap 图像
for (int i = 0; i < duplicatedBmpEntryCount; ++i) {
final BufferedImage img = BitmapDecoder.getBitmap(...);
ImageIO.write(img, "png", os);
}
}
}

Activity 内存泄漏检测的关键是找到最短引用路径,原理是:

  1. 根据 result.info 中的 leakedActivityKey 字段获取 Activity 结点
  2. 使用一个集合,存储与该 Activity 存在强引用的所有结点
  3. 从这些结点出发,使用宽度优先搜索算法,找到最近的一个 GC Root,GC Root 可能是静态变量、栈帧中的本地变量、JNI 变量等重复 Bitmap 检测的原理在上一篇文章有介绍,这里跳过。

总结

Resource Canary 的实现原理

  1. 注册 ActivityLifeCycleCallbacks,监听 onActivityDestroyed 方法,通过弱引用判断是否出现了内存泄漏,使用后台线程(MatrixHandlerThread)周期性地检测
  2. 通过一个“哨兵”对象来确认系统是否进行了 GC
  3. 若发现某个 Activity 无法被回收,再重复判断 3 次(0.6.5 版本的代码默认是 10 次),且要求从该 Activity 被记录起有 2 个以上的 Activity 被创建才认为是泄漏(没发现对应的代码),以防在判断时该 Activity 被局部变量持有导致误判
  4. 不会重复报告同一个 Activity

Resource Canary的限制

  1. 只能在 Android 4.0 以上的设备运行,因为 ActivityLifeCycleCallbacks 是在 API 14 才加入进来的
  2. 无法分析 Android 8.0 及以上的设备的重复 Bitmap 情况,因为 Bitmap 的 mBuffer 字段在 API26 被移除了.

可配置的选项

  1. DumpMode。有 no_dump(报告 Activity 类名)、silence_dump(报告 Activity 类名,回调ActivityLeakCallback)、auto_dump(生成堆转储文件)、manual_dump(发送一个通知) 四 种
  2. debug 模式,只有在 debug 模式下,DumpMode 才会起作用,否则会持续打印日志
  3. ContentIntent,在 DumpMode 模式为 manual_dump 时,会生成一个通知,ContentIntent 可指定跳转的目标 Activity
  4. 应用可见/不可见时监测线程的轮询间隔,默认分别是 1min、20min
  5. MaxRedetectTimes,只有重复检测大于等于 MaxRedetectTimes 次之后,如果依然能获取到Activity,才认为出现了内存泄漏

修复内存泄漏

在监测的同时,Resource Canary 使用 ActivityLeakFixer 尝试修复内存泄漏问题,实现原理是切断InputMethodManager、View 和 Activity 的引用
hprof 文件处理

  1. 在 debug 状态下,且 DumpMode 为 audo_dump 时,Matrix 才会在监测到内存泄漏问题后,自动生成一个 hprof 文件
  2. 由于原文件很大,因此 Matrix 会对该文件进行裁剪优化,并将裁剪后的 hprof 文件和一个result.info 文件压缩到一个 zip 包中,result.info 包括 hprof 文件名、sdkVersion、设备厂商、出现内存泄漏的 Activity 类名等信息
  3. 拿到这个 zip 文件,输入路径参数,执行 matrix-resource-canary-analyzer 中的 CLIMain 程序,
    即可得到一个 result.json 文件,从这个文件能获取 Activity 的关键引用路径、重复 Bitmap 等信息

CLIMain的解析步骤

  1. 根据 result.info 文件拿到 hprof 文件、Activity 类名等关键信息
  2. 分析 Activity 泄漏
  3. 分析重复 Bitmap
  4. 生成 result.json 文件并写入结果
  5. 输出重复的 Bitmap 图像到本地

最短路径查找

Activity 内存泄漏检测的关键是找到最短引用路径,原理是:

  1. 根据result.info 中的 leakedActivityKey 字段获取 Activity 结点
  2. 使用一个集合,存储与该 Activity 存在强引用的所有结点
  3. 从这些结点出发,使用宽度优先搜索算法,找到最近的一个 GC Root,GC Root 可能是静态变量、栈帧中的本地变量、JNI 变量等

重复Bitmap的分析原理

把所有未被回收的 Bitmap 的数据 buffer 取出来,然后先对比所有长度为 1 的 buffer,找出相同的,记录所属的 Bitmap 对象;再对比所有长度为 2 的、长度为 3 的 buffer……直到把所有 buffer 都比对完,这样就记录了所有冗余的 Bitmap 对象.

文字太多,下篇再分析:

4.Hprof文件分析
5.卡顿监控
6.卡顿监控源码解析
7.插桩
8.资源优化
9.I/O监控及原理解析

完整完整版如下:(文档领取私信:Android)

以上就是关于gg修改器要用root吗_gg修改器一定要root吗的全部内容,感谢大家的浏览观看,如果你喜欢本站的文章可以CTRL+D收藏哦。

相关文章

热门下载

大家还在搜