为什么会产生ANR
ANR 是英文Application Not Response 的缩写,也就是当前的应用未响应。“临床”表现为当前的应用滑动无响应,点击无响应,输入无响应。等待一会后有些手机会直接闪退(比如oppo),有些手机会有弹框“当前应用未响应,是否结束”。然后你可以选择结束应用或继续等待。是否有弹框选择对于我们开发人员来说可以去开发者设置里面进行设置,但是对于普通用户来说,闪退了就是闪退了,跟crash的用户体感是一样的
产生anr情况
首先为什么会产生ANR?其实以下四种场景总结起来一句话:UI线程没有办法在规定的时间内做出本应当做的事情。
1InputDispatching Timeout
这个Timeout 引发的anr表示5s内无法响应屏幕的点击事件或者输入事件。inputDispatching 的超时事件是一个native层的引发的anr,我们这里由于篇幅所限制不做扩展
2 广播的onReceiver()在规定的事件内无法被处理完
Android 系统定义了如果广播的onReceiver()事件在规定的时间内无法被执行完,那么系统就会抛出ANR异常,其中:
前台广播:10s
后台广播:60s
整个流程是如何判断的呢?我们接下来就进入源码的世界中
BroadcastQueue
我们看一下BroadcastQueue里面的重要方法
public void scheduleBroadcastsLocked() {
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Schedule broadcasts ["
+ mQueueName + "]: current="
+ mBroadcastsScheduled);
if (mBroadcastsScheduled) {
return;
}
mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
mBroadcastsScheduled = true;
}
这里我们看到是通过handler的方法发送了一个广播的intent Message。我们再往后追,handler的回调中处理这个消息后调用了processNextBroadcastLocked()方法。我们看一下源码,这里篇幅较长,我们只截取了核心的调用方法
final void processNextBroadcast(boolean fromMsg) {
broadcastTimeoutLocked(false);
setBroadcastTimeoutLocked(timeoutTime);
cancelBroadcastTimeoutLocked();
}
我们看一下
final void setBroadcastTimeoutLocked(long timeoutTime) {
if (! mPendingBroadcastTimeoutMessage) {
Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
mHandler.sendMessageAtTime(msg, timeoutTime);
mPendingBroadcastTimeoutMessage = true;
}
}
整个的核心思路就是发送一个延时的message,当时间到达的时候,触发handler的回调,这个时候回去检查当前的超时广播事件监听有没有被remove掉,如果没有被remove掉,就表明当前的广播执行超时了。我们看一下超时的处理
scheduleBroadcastsLocked(){
if (anrMessage != null) {
// Post the ANR to the handler since we do not want to process ANRs while
// potentially holding our lock.
mHandler.post(new AppNotResponding(app, anrMessage));
}
}
有注释也可以看出,这个handler会抛往ActivityManagerService,执行anr处理的流程。这个流程我们在下面进行分析
3 Service 组件引发的ANR
我们都知道,service里面是不可以做耗时操作的,我们先抛出结果:
前台service:20s
后台service:200s
超过这两个时间,我们的应用程序也会抛出ANR。
接下来我们进入源码分析。我们知道应用冷启动的时候,ActvityManagerService的attachApplicationLocked方法会通知ActivityThread 把当前的application 创建起来。attachApplicationLocked里面有一个重要的逻辑,就是会判断当前有没有需要执行的Service
// Find any services that should be running in this process...
if (!badApp) {
try {
didSomething |= mServices.attachApplicationLocked(app, processName);
checkTime(startTime, "attachApplicationLocked: after mServices.attachApplicationLocked");
} catch (Exception e) {
Slog.wtf(TAG, "Exception thrown starting services in " + app, e);
badApp = true;
}
}
进入ActiveService.java的类后,核心的“挖坑”的地方就是这个方法
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
if (proc.executingServices.size() == 0 || proc.thread == null) {
return;
}
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_TIMEOUT_MSG);
msg.obj = proc;
mAm.mHandler.sendMessageDelayed(msg,
proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
}
这个流程就和广播的ANR超时处理机制很像了。一样就是开启一个定时消息。如果这个定时消息没有被取消,那么就会走ANR的处理逻辑,抛出异常
void serviceTimeout(ProcessRecord proc) {
...
if (anrMessage != null) {
mAm.mAppErrors.appNotResponding(proc, null, null, false, anrMessage);
}
}
4 Contentprovider publish 引发的异常
还是回到万能的ActivityManagerService的attachApplicationLocket()方法中,查看关键的contentprovider 流程处理相关
List<ProviderInfo> providers = normalMode ? generateApplicationProvidersLocked(app) : null;
if (providers != null && checkAppInLaunchingProvidersLocked(app)) {
Message msg = mHandler.obtainMessage(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG);
msg.obj = app;
mHandler.sendMessageDelayed(msg, CONTENT_PROVIDER_PUBLISH_TIMEOUT);
}
还是这个熟悉的流程,发送定时Message,如果能收到这个,说明当前CP的发布10s超时,从而引发ANR
四大组件产生的原因小结
其实总结上面的几个流程,简单来说就是一个挖坑,掉坑和填坑的过程
挖坑:发送定时消息
填坑:正常处理流程,成功后remove本条message
掉坑:无法正常处理流程,定时消息成功接受,触发anr,掉进坑里去
发生anr后,系统做了什么
我们从上文的分析中可以看到,所有的ANR错误最后都会走到AppErrors里面的appNotResponding里面。下面我们用流程图进行分析
anr 监控的原理和方法
1 监听Looper Log的时间
思路
在Looper的loop()方法中,我们可以看到在调用msg的dispatchMessage方法的前后有两个logger的日志打印。那么我们就可以采用这个方法,定义一个Printer类,去计算这两个Logger的打印时间,这样就可以准确的判断出是否发生了ANR
优点
灵活配置,可以准备的得到发生ANR的时间和调用栈;
缺点:
1 不能覆盖所有的场景,有些anr并不通过dispatchMessage方法调用,比如input事件的ANR
2 Logger有可能被第三方所接管
3 dispatchMessage方法如果执行时间过长,同样也无法触发计算
2 定时往主线程发一个时间,如果5s后没有响应,那么就是发生anr了
思路
启用一个线程向主线程定时发送一个消息a,然后线程进行休眠,等到时间结束后去检查判断这个值是否变成了a+1,如果没有,说明发生了anr
优点
1 无侵入,对工程项目比较友好
2 代码简单,流程比较容易理解
3 没有兼容问题,通杀
缺点
1 不能保证所有场景都可以触发,另外这个休眠的时间阀值不好控制
3 监听fileobserve的ANR路径的读写
思路
监听data/anr 文件路径下的文件读写的变化,因为一旦发生了anr,系统就会往这个路径创建anr文件
缺点
随着Android对文件读写权限的严格把控,这个思路现在已经不可用了~
4 监听广播
思路
监听“android.intent.action.ANR“广播
“缺点”
所有发生ANR的应用都会发出这个广播,因此就容易发生“误报”。所以需要在接收到广播的时候进行过滤
日常开发如何尽量避免anr
1 小心使用SharedPreferences
2 多线程情况下选用线程池
3 不要在主线程做耗时操作
4 跨进程也有可能引发anr(cpu抢占)