卡顿、死锁、ANR原理,腾讯T3手把手教你

本文深入探讨了Android应用程序无响应(ANR)的原因和触发流程,从卡顿监控到ANR的分析方法。重点讲解了Service、Broadcast、ContentProvider的ANR原理,以及输入事件分发超时的情况。通过分析源码揭示了ANR触发时的炸弹埋设与拆除过程,包括ServiceTimeout、BroadcastQueue Timeout等场景。此外,文章介绍了如何模拟和分析死锁导致的ANR,以及利用ANRWatchDog和ANRMonitor进行线上ANR监控的实践。最后,讨论了死锁监控技术,涉及获取线程和锁信息的方法,以帮助开发者更好地定位和解决ANR问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

//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中,在发生卡顿的时候,取出卡顿时间段内的堆栈信息即可。

不过这种方案只适合线下使用,原因如下:

  1. logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);存在字符串拼接,频繁调用,会创建大量对象,造成内存抖动。

  2. 后台线程频繁获取主线程堆栈,对性能有一定影响,获取主线程堆栈,会暂停主线程的运行

2.2.2 卡顿监控方案二

对于线上卡顿监控,需要了解字节码插桩技术。

通过Gradle Plugin+ASM,编译期在每个方法开始和结束位置分别插入一行代码,统计方法耗时,

伪代码如下

插桩前

fun method(){

run()

}

插桩后

fun method(){

input(1)

run()

output(1)

}

目前微信的Matrix 使用的卡顿监控方案就是字节码插桩,如下图所示

插桩需要注意的问题:

  1. 避免方法数暴增:在方法的入口和出口应该插入相同的函数,在编译时提前给代码中每个方法分配一个独立的 ID 作为参数。

  2. 过滤简单的函数:过滤一些类似直接 return、i++ 这样的简单函数,并且支持黑名单配置。对一些调用非常频繁的函数,需要添加到黑名单中来降低整个方案对性能的损耗。

微信Matrix做了大量优化,整体包体积增加1%-2%,帧率下降2帧以内,对性能影响整体可以接受,不过依然只会在灰度包使用。

再来说说ANR~

三、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的生命周期, ActivityThreadhandleCreateService方法会被调用

-> 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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值