ANR问题类型及产生原理
ANR(Application Not Responding):即应用无响应. 在日常使用安卓手机的过程中, 对最anr最直接的印象就是手机弹框显示应用未响应. 选择继续等待或者关闭.
如果应用程序的主线程在规定的时间内, 没有完成特定操作和事件, 就会发生ANR.
四种ANR类型
- KeyDispatchTimeout : input事件在5S内没有处理完成发生ANR
-
ServiceTimeout : bind,create,start,unbind等操作,前台Service在20s内,后台
Service在200s内没有处理完成发生ANR -
BroadcastTimeout : BroadcastReceiver onReceiver处理事务时前台广播在10S内,后台广播在60s内 (应用程序应该避免在BroadcastReceiver里做耗时的操作或计算。如果响应Intent广播需要执行一个耗时的动作的话,应用程序应该启动一个 Service).
没有处理完成发生ANR -
ProcessContentProviderPublishTimedOutLocked : ContentProvider publish在10s内没有处理完成发
生ANR
其中第四种ANR发生的概率最小.
ANR产生的常见原因
- 主线程耗时操作,如复杂的layout,庞大的for循环,IO等. (实际APP开发时开发者会避开这种, 没有见到过这种问题产生ANR);
- 主线程被子线程同步锁block. (当子线程先拿着锁, 主线程等待这把锁的时候, 子线程太耗时. 导致主线程一直被阻塞, 从而ANR)
- 主线程被Binder对端阻塞
- Binder被占满导致主线程无法和SystemServer通信
- 得不到系统资源(CPU/RAM/IO) (耗时的动画需要大量的计算工作,可能导致CPU负载过重.)
ANR触发机制
ANR有四种类型, 所以可以从这四种类型去了解ANR触发机制.
1 Service发生ANR的机制
Service Timeout是AMS中MainHandler收到SERVICE_TIMEOUT_MSG消息时触发。
由ServiceTimeout原因发生的ANR有两种, 前台服务未响应(20s) 和 后台服务未响应(200s).
1.1 startService()触发ANR流程:
- 在用户进程A中, 当发起startService的时候, 通过binder通信, 通过IActivityManager#startService调用AMS中
startService()
方法. (这一步从用户进程跨越到system_server进程, 调用AMS的方法)
备注:IActivityManager对象保存在进程A的单例Singleton<IActivityManager>
中,进程启动时查询ServiceManager#getService获得,具有缓存作用
-
在AMS中, 当realStartServiceLocked()启动服务的时候, 其内部会操作AMS的MainHandler调用
在这里插入图片描述sendMessageAtTime()
方法发送SERVICE_TIMEOUT_MSG
消息埋下炸弹, 并通过参数指定消息发送的时间. (当AMS埋好炸弹之后, 通过binder把目标服务创建所需要的信息传递给目标服务进程B, 进行服务的创建)
-
在服务进程B中, 服务进程将AMS传递的过来的服务创建信息进行打包, 然后ApplicationThread内部类使用消息传递机制让ActivityThread外部类异步执行service.onCreate回调. 当service创建完毕之后, 服务进程B会再次binder通信, 告诉AMS调用
serviceDoneExecuting()
, 该方法最后会执行Handler.removeMessages()
把之前埋下的炸弹拆除.
所以, 回调完Service的onCreate()
方法之后便会移除启动服务超消息SERVICE_TIMEOUT_MSG
.
Service启动过程出现ANR,”executing service [发送超时serviceRecord信息]”
, 这往往是service的onCreate()回调方法执行时间过长。 -
当炸弹没有及时被拆除. 当
在这里插入图片描述SERVICE_TIMEOUT_MSG
爆炸消息被AMS的MainHandler收到的时候, 就会发生ANR.
注: 1: 当启动进程A发起startService
的时候, AMS如果发现目标服务的进程还没有启动, 会启动一个进程. 但是启动这个进程的时间不算在ANR的爆炸时间内. 因为只有当AMS真正调用realStartServiceLocked()
之后, 才会埋下炸弹.
注2: 在ActivityManagerService实例化的时候, AMS会新开的一条HandlerThread线程,在这个线程里会准备好一个Looper. 然后使用有参构造指定之前线程准备好的Looper,实例化一个Handler.
该Handler就是AMS中的mHandler.
所有埋炸弹的Message都会在这个Looper中的, MessageQueue中进行入队. 最终回调MainHandler.handleMessage()
方法.
注3: AMS和ActivityService
在ActivityManagerService实例化的时候, 会实例化一个ActiveService成员变量.
在实例化ActiveService的时候, AMS会把this传给ActiveService, 然后ActiveService也有一个AMS的成员变量.
AMS.mServices和AS.mAm.
注4: ApplicationThread是ActivityThread的普通内部类, 当system_server进程通过binder通信调用ApplicationThread中的scheduleCreateService()
方法时, 在ApplicationThread该方法内部往外部ActivityThread发送消息, 当ActivityThread进行handle调用的时候, 创建服务.
1.2 产生ANR的源码中与"埋炸弹", "拆炸弹"有关的代码
1) 埋炸弹
[ActiveSErvices.java]
private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException {
...
//发送delay消息(SERVICE_TIMEOUT_MSG)
bumpServiceExecutingLocked(r, execInFg, "create");
...
}
private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {
...
scheduleServiceTimeoutLocked(r.app);
}
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
...
//当超时后仍没有remove该SERVICE_TIMEOUT_MSG消息,则执行service Timeout流程
mAm.mHandler.sendMessageAtTime(msg,
proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT));
}
2) 拆炸弹
在目标进程的服务创建之后, 会remove掉AMS, MessageQueue之中的超时消息, 当这个消息被remove掉之后, 自然不会再被MainHandler处理, 也就不会发生ANR.
[ActivityThread.java]
private void handleCreateService(CreateServiceData data) {
...
java.lang.ClassLoader cl = packageInfo.getClassLoader();
Service service = (Service) cl.loadClass(data.info.name).newInstance();
...
try {
//创建ContextImpl对象
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
context.setOuterContext(service);
//创建Application对象
Application app = packageInfo.makeApplication(false, mInstrumentation);
service.attach(context, this, data.info.name, data.token, app,
ActivityManagerNative.getDefault());
//调用服务onCreate()方法
service.onCreate();
//拆除炸弹引线
ActivityManagerNative.getDefault().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
} catch (Exception e) {
...
}
}
[ActivityThread.java]
private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying, boolean finishing) {
...
if (r.executeNesting <= 0) {
if (r.app != null) {
r.app.execServicesFg = false;
r.app.executingServices.remove(r);
if (r.app.executingServices.size() == 0) {
//当前服务所在进程中没有正在执行的service
mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
...
}
...
}
2 BroadcastReceiver发生ANR的机制
BroadcastReceiver Timeout是AMS中的BroadcastQueue.BroadcastHandler
收到BROADCAST_TIMEOUT_MSG
消息时触发.
其中, 前台广播超时mTimeoutPeriod
为10s, 后台广播超时mTimeoutPeriod
为 60s.
广播发送的时候有两种方式, 一种是 "默认广播" (并行广播), 第二种是 "有序广播" (串行广播).
只有串行广播才需要考虑超时,因为接收者是串行处理的,前一个receiver处理慢,会影响后一个receiver;并行广播 通过一个循环一次性向所有的receiver分发广播事件,所以不存在彼此影响的问题,则没有广播超时;
串行广播超时情况1:某个广播总处理时间 > 2* receiver总个数 * mTimeoutPeriod
, 其中mTimeoutPeriod
串行广播超时情况2:某个receiver的执行时间超过mTimeoutPeriod
Broadcast的ANR流程如下图所示:
BBABA.png
-
图中1,2,3 步从用户进程通过binder通信调用AMS的
broadcastIntentLocked()
方法.
在该方法中, 首先进行了一系列的广播验证.
然后先并行处理动态注册的广播, 然后合并动态注册广播和静态注册的广播receivers进行串行处理. (原因: 让静态注册的广播串行化,能防止出现瞬间启动大量进程的喷井效应。)
由于我们ANR只会在处理串行广播的时候发生, 接下来我们只关注串行广播处理的流程. -
图中第4,5步, 在第4步中AMS按照并行广播和串行广播两种, 分别要发送的广播入相应的队列, 然后再通过
scheduleBroadcastLocked()
方法 内部使用BroadcastHandler实例 sendMessage(BroadcastHandler是BroadcastQueue类的一个普通内部类)
.
备注:这里的BroadcastHandler实例, 是在AMS启动的时候, AMS构造方法中进行实例化的, 在new这个handler的时候, 把AMS构造器之前的准备好Looper给了该Handler. -
图中第6, 7步, 当系统回调
handlerMessage()
方法的时候, 其内部进行广播的处理. 当然我们关注的是处理串行广播的流程. -
第9步, 当前广播超时, 强制结束广播.
-
10,11 通过binder通信告诉ActivityThread, 让他去sendMessage(Receiver), ActivityThread处理这个消息的时候回调BroadcastReceiver的
onReceiver()
方法, 完成广播接受处理. -
BroadcastReceiver完成
onReceiver()
回调. 而BroadcastQueue向ActivityThread发起Binder通信之后, 在BroadcastQueue的processNextBroadcast()
方法中执行cancelBroadcastTimeoutLocked()
, handler会发送removeMessages的消息, 把之前埋下的炸弹拆除.
综上所诉: 当分析ANR的问题时, 如果log显示是BroadcastReceiver的ANR, 可以首先怀疑BroadCastReceiver.onRecieve()
的问题; 如果是Service的ANR, 可以首先怀疑是Service.onCreate()方法耗时的问题.
网友评论