想做这个组件很久了,企业是一个盈利机构,业务的稳定快速发展才能支撑一个公司正常运营。但很多时候这个理由被滥用,被拿来当做搪塞的借口。忽略流程、质量,以致很多时候绝大部分人都疲于奔命,而且产出效益一点都不高。只有为数不多的企业真正做到科学管控、各司其职。
作为一个技术人员,核心职责是通过技术手段,交付质量高、稳定性好、扩展性强的功能,满足企业的业务需求。那怎样才能衡量交付的内容满足以上条件?看谁的声音大?还是看谁的资历老?【摊手】。显然不是,通过各种各样的监控手段,获得整个系统的运行数据,量化统计出各项运行指标,制定合适的标准,才能判断交付的内容是否符合标准。
出于这个目的,我在公司内部提出了设计并实现一个APP监控组件,可以方便地集成到公司的各个大大小小Android应用程序中,时时刻刻监控用户运行时的程序各项数据,并将获取到的数据上传至公司数据库,用于分析运行时的程序性能,并得出后续迭代的依据。
目前组件已经上线运行一段时间,目测数据比较正常,有需要可以拿去研究学习,已去除公司域名,哈哈哈哈哈哈
https://github.com/andyliu900/ideacode-apm-lib
以下内容高能,非专业人士选择性阅读
想法很美好,那么问题来了
问题一:一个手机APP,应该监控什么?
首先,这里实现的是Android原生程序性能监控方案,讨论的只是Android原生的范围。
一个Android程序由一个又一个界面组成,每一个界面都有各自的画面,界面的打开、显示、渲染等等都需要时间进行处理,所以监控这个过程的耗时数据是个不错的idea。
每个人用手机程序的时候都有过卡成狗的体验,所以监控为什么会卡成狗又是一个不错的idea。
现在的手机程序界面为了吸引用户,多多少少都会用到各种动画,计算机是通过绘制一帧又一帧的画面来实现画面动起来的效果,玩过走马灯的都知道这个原理,人眼对于绘制速度超过每秒60帧的画面认为流畅不卡顿,又是一个监控的point。
计算机都需要内存来进行缓冲,程序跑起来都会占用一定的内存空间,内存越少运行程序越卡,所以监控一定时间内的内存使用情况,也是一个很好的idea。
现在想找一个不用联网的单机程序真的难,网络通信本质上市IO操作,有上行和下行的数据,那么获取上行、下行数据,并统计整个IO操作的耗时,不失为一个聪明的idea。
另外现代操作系统是支持多进程的,一个程序允许开启多个进程来处理任务,可以监控各个进程的信息,判断这个程序的进程设计是否科学......
问题二:好吧,你说的都对。但我不知道去哪里拿这些内容
以上可以统称为监控【APP启动】、【Activity生命周期】、【卡顿时线程堆栈】、【绘制帧率】、【运行时内存使用情况】、【网络通信】、【进程信息】。接下来会分别从以上7个方面获取我们想要的信息,并记录在案。
以下内容更高能,非战斗人员尽早离场
监控【APP启动】、【Activity生命周期】
这两个本质上都是监控Activity的相关信息,这里要做一个很重要的事情,就是“hook”,中文译名是“钩子”。实际上是用一个假的东西替换掉系统原来的东西,让系统傻傻地拿着这个假的内容去完成任务,而这个假的内容已经被我们所接管,我们可以监控系统的整个任务过程。
说到Activity,大家都知道Activity和Activity之间是可以跳转的,真正控制跳转的是Instrumentation这个对象。那我们可以用我们自己指定的假的Instrumentation来替换系统原来的Instrumentation。
/**
* 插桩实现
*
* @throws ClassNotFoundException
* @throws NoSuchMethodError
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws NoSuchFileException
*/
private static void hookInstrumentation() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
IllegalAccessException, NoSuchFieldException {
Class<?> c = Class.forName("android.app.ActivityThread");
Method currentActivityThread = c.getDeclaredMethod("currentActivityThread");
boolean acc = currentActivityThread.isAccessible();
if (!acc) {
currentActivityThread.setAccessible(true);
}
Object o = currentActivityThread.invoke(null);
if (!acc) {
currentActivityThread.setAccessible(acc);
}
Field f = c.getDeclaredField("mInstrumentation");
acc = f.isAccessible();
if (!acc) {
f.setAccessible(true);
}
Instrumentation currentInstrumentation = (Instrumentation)f.get(o);
Instrumentation ins = new ApmInstrumentation(currentInstrumentation);
f.set(o, ins);
if (!acc) {
f.setAccessible(acc);
}
}
ApmInstrumentation就是我说的假的Instrumentation。通过重写callApplicationOnCreate、callActivityOnXXX等方法,达到拦截生命周期事件的目的,并在整个过程中记录时间节点,统计出耗时信息。
详见代码:
https://github.com/andyliu900/ideacode-apm-lib/blob/master/apmlibrary/src/main/java/com/ideacode/apm/library/core/job/activity/ApmInstrumentation.java
注意点:监控FirstFrame耗时
一个Activity页面的最外层实际是一个DecorView,可以指定发送一个Runnable对象,并在其中计算出FirstFrame的渲染耗时。
activity.getWindow().getDecorView().post(new FirstFrameRunnable(activity, startType, startTime));
.
. 省略代码
.
static class FirstFrameRunnable implements Runnable {
private Activity activity;
private int startType;
private long startTime;
public FirstFrameRunnable(Activity activity, int startType, long startTime) {
this.activity = activity;
this.startType = startType;
this.startTime = startTime;
}
@Override
public void run() {
long firstFrameTime = System.currentTimeMillis() - startTime;
if (Manager.isDebug()) {
ApmLogX.d(APM_TAG, SUB_TAG, "FirstFrameRunnable time:" + firstFrameTime);
}
if (firstFrameTime >= TaskConfig.DEFAULT_ACTIVITY_FIRST_MIN_TIME) {
saveActivityInfo(activity, startType, System.currentTimeMillis() - startTime, ActivityInfo.TYPE_FIRST_FRAME);
}
if (Manager.isDebug()) {
ApmLogX.d(APM_TAG, SUB_TAG, "FirstFrameRunnable time:" + String.format("[%s, %s]", ActivityCore.isFirst,
TimeUtils.getFormatTime(ActivityCore.appAttachTime, TimeUtils.DATETIMESECOND_SPLIT)));
}
// 保存应用冷启动时间
if (ActivityCore.isFirst) {
ActivityCore.isFirst = false;
if (ActivityCore.appAttachTime <= 0) {
return;
}
int t = (int)(System.currentTimeMillis() - ActivityCore.appAttachTime);
if (Manager.isDebug()) {
ApmLogX.d(APM_TAG, SUB_TAG, "AppStartTime time: " + t);
}
AppStartInfo info = new AppStartInfo(t);
ITask task = Manager.getInstance().getTaskManager().getTask(ApmTask.TASK_APP_START);
if (task != null) {
if (Manager.isDebug()) {
ApmLogX.d(APM_TAG, SUB_TAG, "AppStartInfo saveAppStartInfo");
}
task.save(info);
} else {
if (Manager.isDebug()) {
ApmLogX.d(APM_TAG, SUB_TAG, "AppStartInfo task == null");
}
}
}
}
}
网友评论