TracePlugin,它继承自plugin,里面包括四个维度FrameTracer
、FPSTracer
、 EvilMethodTracer
、StartUpTracer
来分析app.初始方法如下:
@Override
public void init(Application app, PluginListener listener) {
super.init(app, listener);
MatrixLog.i(TAG, "trace plugin init, trace config: %s", mTraceConfig.toString());
//低版本不支持
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
MatrixLog.e(TAG, "[FrameBeat] API is low Build.VERSION_CODES.JELLY_BEAN(16), TracePlugin is not supported");
unSupportPlugin();
return;
}
ApplicationLifeObserver.init(app);
mFrameTracer = new FrameTracer(this);
//开关,可以选择不开
if (mTraceConfig.isMethodTraceEnable()) {
mStartUpTracer = new StartUpTracer(this, mTraceConfig);
}
if (mTraceConfig.isFPSEnable()) {
mFPSTracer = new FPSTracer(this, mTraceConfig);
}
if (mTraceConfig.isMethodTraceEnable()) {
mEvilMethodTracer = new EvilMethodTracer(this, mTraceConfig);
}
}
TracePlugin分成四个部分mStartUpTracer、mFPSTracer、mFrameTracer和mEvilMethodTracer,它们都继承了BaseTracer; BaseTracer里监听了ApplicationLifeObserver,即每个activity的生命周期和前后台状态的监听;BaseTracer监听着FrameBeat的每一帧刷新前后的时间即doFrame(long lastFrameNanos, long frameNanos); (1)mFrameTracer(屏幕刷新相关)
在doFrame里根据界面绘制的时间差计算,如果超过了正常绘制16.67秒就会在监听里把数据回调出去,这个有两个回调方法doFrameSync和doFrameAsync,对应的是同步调用和异步调用,异步的实现方式利用handler机制,其中getScene是当前的activity或fragment的类名。
@Override
public void doFrame(final long lastFrameNanos, final long frameNanos) {
if (!isDrawing) {
return;
}
isDrawing = false;
final int droppedCount = (int) ((frameNanos - lastFrameNanos) / REFRESH_RATE_MS);
if (droppedCount > 1) {
for (final IDoFrameListener listener : mDoFrameListenerList) {
listener.doFrameSync(lastFrameNanos, frameNanos, getScene(), droppedCount);
if (null != listener.getHandler()) {
listener.getHandler().post(new Runnable() {
@Override
public void run() {
listener.getHandler().post(new AsyncDoFrameTask(listener,
lastFrameNanos, frameNanos, getScene(), droppedCount));
}
});
}
}
}
}
(2)mEvilMethodTracer
EvilMethodTracer顾名思义就是找到那些邪恶的方法,也就是耗时多的方法。数据大概是这样:
content[{"machine":"HIGH"
,"cpu_app":0
,"mem":7908851712
,"mem_free":4317912
,"detail":"NORMAL"
,"cost":1752
,"usage":"1.26%"
,"scene":"xxx.xxx.xx.xx.MainActivity"
,"stack":""
,"stackKey":""
,"tag":"Trace_EvilMethod"
,"process":"xxx.xxx.xxx"
,"time":1647848753331}]
(3)mStartUpTracer
StartUpTracer是用于分析activity的启动时间的,application启动耗时、SplashActivity(欢迎页)耗时、应用和activity之间的耗时。
(4mFPSTracer
FPSTracer统计的是帧率,统计对应的activity、fragment的掉帧水平。
content[{"machine":"HIGH",
"cpu_app":0,
"mem":7908851712,
"mem_free":4042724,
"scene":"xxx.xxx.xxx.xx.MainActivity",
"dropLevel":{"DROPPED_FROZEN":5,
"DROPPED_HIGH":0,
"DROPPED_MIDDLE":4,
"DROPPED_NORMAL":6,
"DROPPED_BEST":187},
"dropSum":{"DROPPED_FROZEN":485,
"DROPPED_HIGH":0,
"DROPPED_MIDDLE":63,
"DROPPED_NORMAL":21,
"DROPPED_BEST":23},
"fps":15.220011711120605,
"tag":"Trace_FPS","process":"xxx.xxx.xxx",
"time":1647851378435}]
tracePlugin总结
主要分析得失TracePlugin的实现,其中包括了ANR记录、超时函数记录、帧数统计和启动记录。
// matrix
DynamicConfigImplDemo dynamicConfig = new DynamicConfigImplDemo();
boolean fpsEnable = dynamicConfig.isFPSEnable();
boolean traceEnable = dynamicConfig.isTraceEnable();
Matrix.Builder maxBuilder = new Matrix.Builder(sApplication);
maxBuilder.pluginListener(new VenuciaappPluginListener(sContext));
//trace
TraceConfig traceConfig = new TraceConfig.Builder()
.dynamicConfig(dynamicConfig)
//按照自己需求开启以下监控任务
.enableFPS(fpsEnable)
.enableEvilMethodTrace(traceEnable)
.enableAnrTrace(traceEnable)
.enableStartup(traceEnable)
.splashActivities("xxx.xxx.xxx.xx.SplashActivity")
//debug模式
.isDebug(true)
//dev环境
.isDevEnv(false)
.build();
TracePlugin tracePlugin = new TracePlugin(traceConfig);
maxBuilder.plugin(tracePlugin);
Matrix.init(maxBuilder.build());
tracePlugin.start();
2、IOCanary
检测主线程 I/O
主要包括文件 I/O 监控和 Closeable Leak 监控两部分。通过使用 IOCanary ,可以快速发现常见的 I/O 问题,提高开发质量
使用
//io
IOConfig ioConfig = new IOConfig.Builder()
.dynamicConfig(dynamicConfig)
.build();
IOCanaryPlugin ioCanaryPlugin = new IOCanaryPlugin(ioConfig);
maxBuilder.plugin(ioCanaryPlugin);
Application);
(1)write超过阈值检测
//这里write8000次 512个字节数组
private fun writeLongSth() {
try {
val f = File("/sdcard/a_long.txt")
if (f.exists()) {
f.delete()
}
val data = ByteArray(512)
for (i in data.indices) {
data[i] = 'a'.toByte()
}
val fos = FileOutputStream(f)
for (i in 0 until 10000 * 8) {
fos.write(data)
}
fos.flush()
fos.close()
} catch (e: FileNotFoundException) {
e.printStackTrace()
} catch (e: IOException) {
e.printStackTrace()
}
}
//检测日志
tag[io]type[2];
key[null];
content[{"path":"\/sdcard\/a_long.txt",
"size":40960000,
"op":80000,
"buffer":512,
"cost":586,
"opType":2,
"opSize":40960000,
"thread":"main"
,"stack":"android.app.ActivityThread$AndroidOs
.open(ActivityThread.java:7463)
\ncom.szlanyou.iov.tencent.io
.TestIOActivity.writeLongSth(TestIOActivity.kt:173)
xxx.xxx.xxx.tencent.io.TestIOActivity
.onClick(TestIOActivity.kt:79)\n
java.lang.reflect.Method.invoke(Native Method)
androidx.appcompat.app
.AppCompatViewInflater$DeclaredOnClickListener
.onClick(AppCompatViewInflater.java:409)
android.view.View.performClick(View.java:7187)
android.view.View.performClickInternal(View.java:7160)
android.view.View.access$3500(View.java:824)
android.view.View$PerformClick.run(View.java:27664)
android.app.ActivityThread.main(ActivityThread.java:7566)\n",
"repeat":0,"tag":"io"
,"type":2
,"process":"xxx.xxx.xxx"
,"time":1647934014624}]
(2)file leak检测
//这里读写数据流时未关导致的内存泄漏
private fun leakSth() {
writeSth()
try {
val f = File("/sdcard/a.txt")
val buf = ByteArray(400)
val fis = FileInputStream(f)
val count = 0
while (fis.read(buf) != -1) {
// MatrixLog.i(TAG, "read %d", ++count);
}
} catch (e: FileNotFoundException) {
e.printStackTrace()
} catch (e: IOException) {
e.printStackTrace()
}
//need to trigger gc to detect leak
//need to trigger gc to detect leak
Thread {
Runtime.getRuntime().gc()
Runtime.getRuntime().runFinalization()
Runtime.getRuntime().gc()
}.start()
}
private fun writeSth() {
try {
val f = File("/sdcard/a.txt")
if (f.exists()) {
f.delete()
}
val data = ByteArray(4096)
for (i in data.indices) {
data[i] = 'a'.toByte()
}
val fos = FileOutputStream(f)
for (i in 0..9) {
fos.write(data)
}
fos.flush()
fos.close()
} catch (e: FileNotFoundException) {
e.printStackTrace()
} catch (e: IOException) {
e.printStackTrace()
}
}
//日志检测
ag[io]type[4];key[java.util.zip.ZipFile.(ZipFile.java:273)
java.util.zip.ZipFile.(ZipFile.java:187)
java.util.zip.ZipFile.(ZipFile.java:201)
miui.content.res.ThemeZipFile$MyZipFile.(ThemeZipFile.java:355)
miui.content.res.ThemeZipFile.checkUpdate(ThemeZipFile.java:92)
miui.content.res.ThemeResources.checkUpdate(ThemeResources.java:221)
miui.content.res.ThemeResources.(ThemeResources.java:216)
miui.content.res.ThemeResourcesPackage.(ThemeResourcesPackage.java:79)
miui.content.res.ThemeResourcesPackage.getTopLevelThemeResources(ThemeResourcesPackage.java:69)
miui.content.res.ThemeResourcesPackage.getThemeResources(ThemeResourcesPackage.java:40)
(3)buffer过小
// read的时候给定一个小的buffer
private void smallBuffer() {
readSth();
}
private void readSth() {
try {
File f = new File("/sdcard/a_long.txt");
byte[] buf = new byte[400];
FileInputStream fis = new FileInputStream(f);
int count = 0;
while (fis.read(buf) != -1) {
// MatrixLog.i(TAG, "read %d", ++count);
}
fis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
//检测日志
tag[io]type[2];
key[null];
content[{"path":"\/sdcard\/a_long.txt"
,"size":40960000
,"op":102401
,"buffer":400
,"cost":274
,"opType":1
,"opSize":40960400
,"thread":"main"
,"stack":"android.app.ActivityThread$AndroidOs
.open(ActivityThread.java:7463)
com.szlanyou.iov.tencent.io.
TestIOActivity.readSth(TestIOActivity.kt:102)
com.szlanyou.iov.tencent.io.
TestIOActivity.smallBuffer(TestIOActivity.kt:95)
com.szlanyou.iov.tencent.io.
TestIOActivity.onClick(TestIOActivity.kt:88)
java.lang.reflect.Method.invoke(Native Method)
androidx.appcompat.app
.AppCompatViewInflater$DeclaredOnClickListener
.onClick(AppCompatViewInflater.java:409)
android.view.View.performClick(View.java:7187)
android.view.View.performClickInternal(View.java:7160)
android.view.View.access$3500(View.java:824)
android.view.View$PerformClick.run(View.java:27664)
","repeat":0,"tag":"io","type":2,"process":"com.szlanyou.iov"
,"time":1647936449740}]
3、ResourceCanary
ResourceCanary后能实现下面的目标:
使用
//resource
ResourceConfig resourceConfig = new ResourceConfig.Builder()
.dynamicConfig(dynamicConfig)
.setAutoDumpHprofMode(ResourceConfig.DumpMode.MANUAL_DUMP)
.setManufacture(Build.MANUFACTURER)
.setManualDumpTargetActivity("xxx.xxx.xxx.ccm.CCMActivity")
//只有官方demo中才会为true
.setDetectDebuger(true)
.build();
ResourcePlugin resourcePlugin = new ResourcePlugin(resourceConfig);
maxBuilder.plugin(resourcePlugin);
// 当activity destroy之后,自动断开从引用的view到gc root之间的路径
ResourcePlugin.activityLeakFixer(s
DumpMode:
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
}
MANUAL_DUMP一定要传activity,不然会报空指针。
内存泄露
content[{"dump_mode":"MANUAL_DUMP",
"activity":"xxx.xxx.xxx.ccm.CCMActivity",
"ref_key":"MATRIX_RESCANARY_REFKEY_com.szlanyou.iov.ccm.
CCMActivity_16f442b6fb7e4f039d0c54bb5307207b"
,"leak_detail":"Leak Reference:android.os.MessageQueue mMessages;
android.os.Message callback;com.analysys.visual.bind
.VisualBindManager$a a;
com.analysys.visual.bind.VisualBindManager mListRootView;
java.util.ArrayList elementData;array java.lang.Object[] [3];
com.analysys.ui.RootView view;com.android.internal.policy.DecorView mContentRoot;android.widget.LinearLayout mContext;xxx.xxx.xxx.ccm.CCMActivity instance
;"
,"cost_millis":"9500"
,"tag":"memory"
,"process":"xxx.xxx.xxx","time":1647913981637}]
4、SqliteCanary
SQLiteLint在APP运行时进行检测,而且大部分检测算法与数据量无关即不依赖线上的数据状态。只要你触发了某条sql语句的执行,SQLiteLint就会帮助你review这条语句是否写得有问题。而这在开发、测试或者灰度阶段就可以进行。
使用
//sqlite
SQLiteLintConfig config = new SQLiteLintConfig(SQLiteLint.SqlExecutionCallbackMode.CUSTOM_NOTIFY);
SQLiteLintPlugin sqLiteLintPlugin = new SQLiteLintPlugin(config);
maxBuilder.plugin(sqLiteLintPlugin);
sqLiteLintPlugin.start();