Android ANR触发机制及日志分析

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在指定时间内启动完成后&#x

你可能感兴趣的:(android)