1.ANR
Android系统要求一些事件在一定时间内完成,如果超过预定时间未得到有效响应或响应时间过长,就会造成ANR(Application Not Responding应用程序未响应)。
Android中有4种ANR发生场景:
①点击事件(按键和触摸事件):点击事件在5s内未被处理,日志描述为Input event dispatching timed out。
②服务Service: 前台服务20s内、后台服务200s内未完成启动,日志描述为Timeout executing service。
③广播BroadcastReceiver:前台广播10s内、后台广播60s内,onReceive()在规定时间内没处理完,日志描述为Timeout of broadcast Broadcast Record。
注意,前台广播的ANR时间是10s内onReceive()没有执行完就提示,这是在没有点击触摸事件导致ANR的前提下才是10s,否则会先触发点击事件的ANR,onReceive()有可能执行不到10s就发生 ANR,所以不要在onReceive()处理耗时操作。
④内容提供者ContentProvider:publish在10s内没处理完,日志描述为Timeout publishing content providers。
ANR的触发方式分为两种:通过Handler的延迟机制触发ANR和Input事件触发ANR。Service、BroadcastReceiver和ContentProvider都是通过Hander机制触发ANR。
ANR产生需要同时满足三个条件:
①主线程:只有应用程序的主线程响应超时才会产生ANR;
②超时时间:产生ANR的上下文不同,超时时间也不同,但只要在这个时间上限内没有响应就会ANR;
③输入事件/特定操作:输入事件指按键、触屏等设备输入事件,特定操作指BroadcastReceiver和Service的生命周期中的各个函数,产生ANR的上下文不同,导致ANR的原因也会不同。
在实际项目中,大多数ANR都是点击触摸事件超时导致,超时的原因主要有三个:
①数据导致的ANR:频繁GC导致线程暂停,处理事件时间被拉长;
②线程阻塞或死锁导致ANR;
③Binder导致ANR:Binder通信数据量过大;
注为了避免ANR,耗时操作要放在子线程中,防止耗时操作阻塞主线程。为了降低因网络访问导致的ANR,在Android4.0之后强制规定访问网络必须在子线程,否则将会抛出NetworkOnMainThreadException。
2.ANR触发原理
触发ANR的过程可分为三个步骤: 埋炸弹、拆炸弹、引爆炸弹。埋炸弹可以理解为发送一个延迟触发的消息;拆炸弹就是将这个延迟消息取消了,也就不会触发了;引爆炸弹就是延迟时间已到,开始处理延迟消息。
其实说到本质上,系统内部对于ANR的触发流程也是建立在主线程Looper机制上的,就是先发送一个延时消息,然后在特定位置移除这个消息,如果指定时间内消息没有被移除则证明整个流程出现问题,就会执行ANR处理。
以Service触发ANR为例看一下触发流程。
Service超时是位于ActivityManager线程中的AMS.MainHandler收到SERVICE_TIMEOUT_MSG消息时触发的。
1)第一步:埋炸弹
在Service进程attach到system_server进程时会调用realStartServiceLocked()方法,该方法开始真正执行Service的生命周期方法,并开始装炸弹。
ActiveServices.java:
private final void realStartServiceLocked( ServiceRecord r, ProcessRecord app, boolean execInFg) {
//handler发送延迟消息
bumpServiceExecutingLocked(r, execInFg, "create");
...
//调用Service的onCreate()方法
app.thread.scheduleCreateService(r, r.serviceInfo, mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo), app.repProcState);
}
bumpServiceExecutingLocked方法通过Handler发送延迟消息:
private void bumpServiceExecutingLocked( ServiceRecord r, boolean fg, String why) {
scheduleServiceTimeoutLocked(r.app);
}
调用scheduleServiceTimeoutLocked方法来发送延迟消息:
void scheduleServiceTimeoutLocked( ProcessRecord proc) {
Message msg = mAm.mHandler.obtainMessage(ActivityManagerService.SERVICE_TIMEOUT_MSG);
msg.obj = proc;
//发送延迟消息,当超时后仍没有remove,则执行service Timeout流程
mAm.mHandler.sendMessageDelayed(msg, proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
}
int SERVICE_TIMEOUT = 20*1000;
int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;
在执行Service的生命周期方法前会通过bumpServiceExecutingLocked()方法进行装炸弹,即通过Handler机制发送一个标志为SERVICE_TIMEOUT_MSG的延迟消息(炸弹),如果是前台则延迟20s,后台则延迟200s执行。
2)第二步:拆炸弹
在执行完Service的生命周期方法后就会执行拆炸弹,比如onCreate()方法在handleCreateService()执行完毕:
ActivityThread java:
private void handleCreateService( CreateServiceData data) { //创建对应的Service,并执行onCreate方法
java.lang.ClassLoader cl = packageInfo.getClassLoader();
Service service = (Service) cl.loadClass(data.info.name).newInstance();
service.attach(context, this, data.info.name, data.token, app, ActivityManagerNative.getDefault()); //通过attach方法绑定资源文件
service.onCreate(); //调用onCreate()方法
//onCreate()执行完成后,会调用AMS的serviceDoneExecuting方法
ActivityManager.getService().serviceDon eExecuting(data.token,SERVICE_DONE_EXECUTING_ANON, 0, 0);
}
首先创建目标Service对象并回调onCreate()方法,onCreate()成功后会调用到system_server来执行serviceDoneExecuting()方法:
// AMS
public void serviceDoneExecuting(IBinder token, int type, int startId, int res) {
synchronized (this) {
mServices.serviceDoneExecutingLocked( (ServiceRecord) token, type, startId, res);
}
}
最终调用ActiveServices的serviceDoneExecutingLocked方法开始拆弹过程:
ActiveServices.java:
private void serviceDoneExecutingLocked( ServiceRecord r, boolean inDestroying, boolean finishing) {
//取消SERVICE_TIMEOUT_MSG消息拆炸弹
mAm.mHandler.removeMessages( ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
...
}
如果Service在指定时间内启动完成后