//2、消息处理前回调
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
…
//3、消息开始处理
msg.target.dispatchMessage(msg);// 分发处理消息
…
//4、消息处理完回调
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
}
…
}
注释2和注释4的logging.println
是谷歌提供给我们的一个接口,可以监听Handler处理消息耗时,我们只需要调用Looper.getMainLooper().setMessageLogging(printer)
,即可从回调中拿到Handler处理一个消息的前后时间。
需要注意的是,监听到发生卡顿之后,dispatchMessage
早已调用结束,已经出栈,此时再去获取主线程堆栈,堆栈中是不包含卡顿的代码的。
所以需要在后台开一个线程,定时获取主线程堆栈,将时间点作为key,堆栈信息作为value,保存到Map中,在发生卡顿的时候,取出卡顿时间段内的堆栈信息即可。
不过这种方案只适合线下使用,原因如下:
-
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
存在字符串拼接,频繁调用,会创建大量对象,造成内存抖动。 -
后台线程频繁获取主线程堆栈,对性能有一定影响,获取主线程堆栈,会暂停主线程的运行。
2.2.2 卡顿监控方案二
对于线上卡顿监控,需要了解字节码插桩技术。
通过Gradle Plugin+ASM,编译期在每个方法开始和结束位置分别插入一行代码,统计方法耗时,
伪代码如下
插桩前
fun method(){
run()
}
插桩后
fun method(){
input(1)
run()
output(1)
}
目前微信的Matrix 使用的卡顿监控方案就是字节码插桩,如下图所示
插桩需要注意的问题:
-
避免方法数暴增:在方法的入口和出口应该插入相同的函数,在编译时提前给代码中每个方法分配一个独立的 ID 作为参数。
-
过滤简单的函数:过滤一些类似直接 return、i++ 这样的简单函数,并且支持黑名单配置。对一些调用非常频繁的函数,需要添加到黑名单中来降低整个方案对性能的损耗。
微信Matrix做了大量优化,整体包体积增加1%-2%,帧率下降2帧以内,对性能影响整体可以接受,不过依然只会在灰度包使用。
再来说说ANR~
ANR 的类型和触发ANR的流程
3.1 哪些场景会造成ANR呢
-
Service Timeout:比如前台服务在20s内未执行完成,后台服务是10s;
-
BroadcastQueue Timeout:比如前台广播在10s内未执行完成,后台60s
-
ContentProvider Timeout:内容提供者,在publish过超时10s;
-
InputDispatching Timeout: 输入事件分发超时5s,包括按键和触摸事件。
相关超时定义可以参考ActivityManagerService
// How long we allow a receiver to run before giving up on it.
static final int BROADCAST_FG_TIMEOUT = 10*1000;
static final int BROADCAST_BG_TIMEOUT = 60*1000;
// How long we wait until we timeout on key dispatching.
static final int KEY_DISPATCHING_TIMEOUT = 5*1000;
3.2 ANR触发流程
来简单分析下源码,ANR触发流程其实可以比喻成埋炸弹和拆炸弹的过程,
以后台Service为例
3.2.1 埋炸弹
Context.startService
调用链如下:
AMS.startService
ActiveServices.startService
ActiveServices.realStartServiceLocked
ActiveServices.realStartServiceLocked
private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException {
…
//1、这里会发送delay消息(SERVICE_TIMEOUT_MSG)
bumpServiceExecutingLocked(r, execInFg, “create”);
try {
…
//2、通知AMS创建服务
app.thread.scheduleCreateService(r, r.serviceInfo,
mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
app.repProcState);
}
…
}
注释1的bumpServiceExecutingLocked内部调用scheduleServiceTimeoutLocked
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
…
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_TIMEOUT_MSG);
msg.obj = proc;
// 发送deley消息,前台服务是20s,后台服务是10s
mAm.mHandler.sendMessageDelayed(msg,
proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
}
注释2通知AMS启动服务之前,注释1处发送Handler延时消息,埋下炸弹,如果10s内(前台服务是20s)没人来拆炸弹,炸弹就会爆炸,即ActiveServices#serviceTimeout
方法会被调用
3.2.2 拆炸弹
启动一个Service,先要经过AMS管理,然后AMS会通知应用进程执行Service的生命周期, ActivityThread
的handleCreateService
方法会被调用
-> ActivityThread#handleCreateService
private void handleCreateService(CreateServiceData data) {
try {
…
Application app = packageInfo.makeApplication(false, mInstrumentation);
service.attach(context, this, data.info.name, data.token, app,
ActivityManager.getService());
//1、service onCreate调用
service.onCreate();
mServices.put(data.token, ser