ANR系列之五:Service类型ANR原理讲解

前言:

之前写过一篇service类型ANR原理讲解的文章,但是最近回头看,发现有漏了很多东西,而且讲解的不清楚,直接在原文上改改动太大,所以直接就索性重新写了。

ANR系列文章一共有有若干篇,

遵循这样的一个顺序依次讲解:

1.先讲ANR的基本概念以及ANR发生后的流程;

2.四种类型的ANR是如何发生的;

3.该如何排查和解决ANR类型问题。

想看整个系列的文章,可以参考该系列文章第一篇,里面会有明确的清单:

ANR系列之一:ANR显示和日志生成原理讲解

本篇是ANR系列文章的第五篇,本文主要讲解service类型的ANR类型是如何发生的。

本文主要讲解内容如下:

1.Service类型的ANR介绍;

2.两种Service类型下不同reason的ANR原理介绍;

3.六种Service类型的ANR举例说明,覆盖全部场景。

PS:阅读本文前,建议阅读下面的文章,做好知识储备,方便本文的理解。

android四大组件之二-service实现原理分析

一.Service类型ANR介绍

在安卓中,service类型的ANR有两种,分别为service启动超时和前台服务超时,我们依次介绍下。

1.1 service启动超时

目标service未能在期望的时间内完成启动流程,从而产生“executing service ...”类型的ANR。这里的service并不仅仅只针对后台服务,对于前台服务也是生效的(具体场景和解释请看第五章)。

对应的AMS中的Message类型声明如下:

ActivityManagerService.SERVICE_TIMEOUT_MSG

1.2 前台服务超时

指的是启动前台service时,此时service需要在规定规定时间内完成setForeground的操作,否则就有可能会提示“Context.startForegroundService() did not then call Service.startForeground():..”类型的ANR(具体场景和解释请看第五章)。

对应的AMS中的Message类型声明如下:

ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG

下面我们依次介绍两种类型的ANR,第二章介绍service启动超时类型,第三章介绍前台服务超时。

二.Service启动超时类型

2.1 Service启动超时类型ANR的触发点

首先,我们看一下service类型的ANR触发点在哪里。

之前讲过,所有类型的ANR,最终都会通知到ANRHelper这个类的appNotResponding方法,service类型的自然也不例外。

所以最终的触发点在ActiveService类的serviceTimeout方法中:

void serviceTimeout(ProcessRecord proc) {
    ...
    anrMessage = "executing service " + timeout.shortInstanceName;
    ...
    if (anrMessage != null) {
            mAm.mAnrHelper.appNotResponding(proc, anrMessage);
    }
}

而这个方法,是在ActivityManagerService中被调用的,对应的Message的消息类型为ActivityManagerService.SERVICE_TIMEOUT_MSG。

相关代码如下:

case SERVICE_TIMEOUT_MSG: {
    mServices.serviceTimeout((ProcessRecord)msg.obj);

我们再来看一下SERVICE_TIMEOUT_MSG类型的消息何时被发送的:

// How long we wait for a service to finish executing.
static final int SERVICE_TIMEOUT = 20 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;

// How long we wait for a service to finish executing.
static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;

void scheduleServiceTimeoutLocked(ProcessRecord proc) {
    ...
    Message msg = mAm.mHandler.obtainMessage(
            ActivityManagerService.SERVICE_TIMEOUT_MSG);
    msg.obj = proc;
    mAm.mHandler.sendMessageDelayed(msg, proc.mServices.shouldExecServicesFg()
            ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
}

很显然,发送了一个延时的消息,也就是说,service启动流程中会发送一个延时的SERVICE_TIMEOUT_MSG类型消息,如果时间到了并且该类型的消息没有取消掉,则ANR就发生了。

这里配置的延时时间有两种,区分属于前台service还是后台service。前台的超时时间为20S,后台为200S。

而取消的类型有两种,通知APP出现异常,或者APP完成了相关操作。

2.2 何时开启和关闭超时检测

知道了系统是通过注册SERVICE_TIMEOUT_MSG类型的延时消息来实现的超时检查后,我们就再来看下何时注册和移除这个SERVICE_TIMEOUT_MSG类型的消息。

注册SERVICE_TIMEOUT_MSG类型消息

注册SERVICE_TIMEOUT_MSG是在scheduleServiceTimeoutLocked方法中,那我们就来看下何时调用这个方法。

这个方法的调用都在bumpServiceExecutingLocked中,我们看下代码:

/**
 * r:service的记录
 * fg:是否由前台应用启动
 * why:启动描述
 */
private boolean bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why,String oomAdjReason) {
    ...
    ProcessServiceRecord psr;
    boolean timeoutNeeded = true;
    if ((mAm.mBootPhase < SystemService.PHASE_THIRD_PARTY_APPS_CAN_START)
        && (r.app != null) && (r.app.getPid() == ActivityManagerService.MY_PID)) {
        timeoutNeeded = false;
    }    
    if (r.executeNesting == 0) {
        r.executeFg = fg;
        if (r.app != null) {
            if (timeoutNeeded && psr.numberOfExecutingServices() == 1) {
                scheduleServiceTimeoutLocked(r.app);
            }
        }
    } else if (r.app != null && fg) {
        if (!psr.shouldExecServicesFg()) {
            if (timeoutNeeded) {
                scheduleServiceTimeoutLocked(r.app);
            }
        }
    }
    ...
    r.executeNesting++;
    r.executingStart = SystemClock.uptimeMillis();
}

解释如下:

1.如果是SystemService发起的启动,如果时机如果过早,则没必要做超时检测,这时候系统还没出初始化好,所以很容易因为系统侧的原因造成ANR。

2.如果没有执行中的任务,并且前台正在启动的service只有一个,则调用scheduleServiceTimeoutLocked开启超时检测。

3.如果service是由前台应用启动的,也需要开启超时检测。

这里总结一下,就是说service启动时,当第一个生命周期的任务执行时,或者是前台应用启动的,都会开启超时检测。

移除SERVICE_TIMEOUT_MSG类型消息

移除SERVICE_TIMEOUT_MSG类型消息是在serviceDoneExecutingLocked方法中,相关代码如下:

private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
        boolean finishing, boolean enqueueOomAdj) {
    r.executeNesting--;
    if (r.executeNesting <= 0) {
        if (r.app != null) {
            if (psr.numberOfExecutingServices() == 0) {
                mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
            }
            ..
        }
        ...
        r.executeFg = false;
    }
}

解释如下:

1.每次执行完任务的时候,都会调用serviceDoneExecutingLocked方法,则生命周期方法次数-1。

2.当生命周期方法全部执行结束时,并且没有还未执行的service启动任务,则移除SERVICE_TIMEOUT_MSG类型消息。也就是说此时才认为整个service启动流程结束了。

2.3 AMS开启超时检测

之前service启动流程原理的文章中介绍过,service的启动流程中,分别有两个流程:

1.通知APP去创建Service,对应的方法主要是:realStartServiceLocked。

2.通知APP去执行Service生命周期,对应的方法主要是:sendServiceArgsLocked。

我们先看第一个realStartServiceLocked方法:

private final void realStartServiceLocked(...) throws RemoteException {
    //启动超时检测
    bumpServiceExecutingLocked(r, execInFg, "create");
    ...
    //通知APP去创建service
    try {
        app.thread.scheduleCreateService(..);
        created = true;
    }catch(Exception e){
    
    }finally{
        if (!created) {
            ...
            serviceDoneExecutingLocked(r, inDestroying, inDestroying);
        }
    }
    ...
    //通知APP进行service的生命周期
    sendServiceArgsLocked(r, execInFg, true);
}

方法的核心逻辑梳理如下:

1.首先方法之初,会通过bumpServiceExecutingLocked方法开启超时检测。

2.然后通知APP去创建service。

3.如果通知创建的流程出现异常,则通过bumpServiceExecutingLocked方法结束超时检测。

4.通知创建完成后,自然继续通知APP去执行service生命周期流程。

我们再来看一下,sendServiceArgsLocked方法:

private final void sendServiceArgsLocked(...){
    while (r.pendingStarts.size() > 0) {
        //获取启动周期所对应的事务对象
        ServiceRecord.StartItem si = r.pendingStarts.remove(0);
        //加入集合
        r.deliveredStarts.add(si);
        ...
        //开启生命周期超时检测
        bumpServiceExecutingLocked(r, execInFg, "start", null /* oomAdjReason */);
        ...
        //构造启动参数
        args.add(new ServiceStartArgs(si.taskRemoved, si.id, flags, si.intent));
    }
    ...
    ParceledListSlice<ServiceStartArgs> slice = new ParceledListSlice<>(args);
    try {
        //通知APP执行对应生命周期
        r.app.getThread().scheduleServiceArgs(r, slice);
    } catch(Exception e){
        //通知流程出现异常
        caughtException = e;
    }
    //出现异常,则结束流程
    if(caughtException != null){
        for (int i = 0, size = args.size(); i < size; i++) {
            serviceDoneExecutingLocked(r, inDestroying, inDestroying, true);
        }
    }
}

方法的核心逻辑梳理如下:

1.首先方法之初,获取ServiceRecord对象中所包含的生命周期事物,加入deliveredStarts集合中。

2.有多少个生命周期的启动函数,则就构建多少个超时检测。

3.通知APP去执行对应的生命周期方法。

4.如果通知失败,则结束刚才创建的那些超时检测。

2.4 APP处理流程

我们仍然分成两种场景,去看待APP的处理流程。

第一种:service创建;

第二种:service执行生命周期。

APP侧执行service创建流程流程

service实现原理的文章介绍过,上面2.3中通知APP去执行创建service的流程,在APP侧接受创建通知的是ApplicationThread对象中的scheduleCreateService方法,最终交给ActivityThread中的handleCreateService方法来处理,我们看下相关代码。

private void handleCreateService(CreateServiceData data) {
    
    //完成创建service
    service = packageInfo.getAppFactory()
                    .instantiateService(cl, data.info.name, data.intent);

    //调用service的attach方法
    service.attach(context, this, data.info.name, data.token, app,
                    ActivityManager.getService());

    //调用service的onCreate方法
    service.onCreate();

    //通知回系统侧
    ActivityManager.getService().serviceDoneExecuting(
                        data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
}

我们可以看到,通过serviceDoneExecuting方法通知到AMS中的serviceDoneExecuting方法,接下来我们就看下AMS中的serviceDoneExecuting方法以及ActiveServices中的serviceDoneExecutingLocked方法。

public void serviceDoneExecuting(IBinder token, int type, int startId, int res) {
    synchronized(this) {
        ...
        mServices.serviceDoneExecutingLocked((ServiceRecord) token, type, startId, res, false);
    }
}
//ActiveServices
void serviceDoneExecutingLocked(ServiceRecord r, int type, int startId, int res,
        boolean enqueueOomAdj) {
    if (r != null) {
        ...
        //serviceDoneExecutingLocked方法就是2.2种介绍的关闭超时检测的方法
        serviceDoneExecutingLocked(r, inDestroying, inDestroying, enqueueOomAdj);
    }
}

可以看到,最终APP侧通过binder方法serviceDoneExecuting,通知系统任务已完成。当service所有启动任务都完成时,系统就会停止超时检测流程。

APP侧执行service执行生命周期流程

service实现原理的文章介绍过,上面2.3中通知APP去执行service的生命周期流程,在APP侧接受创建通知的是ApplicationThread对象中的scheduleServiceArgs方法,最终交给ActivityThread中的handleServiceArgs方法来处理,我们看下相关代码:

private void handleServiceArgs(ServiceArgsData data) {
    Service s = mServices.get(data.token);
    if(s != null ){
        if (!data.taskRemoved) {
            res = s.onStartCommand(data.args, data.flags, data.startId);
        } else {
            s.onTaskRemoved(data.args);
            res = Service.START_TASK_REMOVED_COMPLETE;
        }
        ... 
        ActivityManager.getService().serviceDoneExecuting(
            data.token, SERVICE_DONE_EXECUTING_START, data.startId, res);
        ...
    }
}

可以看到,首先会执行Service的onStartCommand方法,完成后也是通过binder方法serviceDoneExecuting通知系统相关任务已完成。

2.5 APP进程未存在时场景

上面介绍的两种场景,都是进程存在时的,如果Service的进程未存在呢?整个流程可以参考开头介绍的Service原理讲解这篇文章。

这里画一个流程图,方便讲解。

进程创建后通知AMS去绑定,attachApplication中会先后调用thread.bindApplication和realStartServiceLocked,分别通知APP去初始化进程以及通知APP去启动Service。

而在APP侧,相关流程都是在主线程的,所以如果bindApplication耗时严重的话,是会导致handleCreateService和handleServiceArgs未能按时执行,从而导致最终ANR发生的。

实际上,Application.onCreate(),Service.onCreate(),Service.onStartCommand()三个方法合计时间如果超过规定的超时时间,则就会产生ANR。

2.6 小结

这里我们做一个小小的总结,总结一下Service启动超时导致ANR产生的情况。

ActiveServices中有两个会触发注册超时检查的场景,注册之后通知APP去完成相关的操作,如果APP按照完成所有任务,则超时检查的任务会被取消。如果到了超时时间后仍然有未完成的任务,则就会发生ANR。

总体流程图如下:

三.前台服务超时类型

和第二章中类似的流程,我们先看下何时会注册前台服务类型的延时消息。

3.1 前台服务超时类型ANR介绍

首先,我们看下ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG类型的消息何时注册,相关方法在scheduleServiceForegroundTransitionTimeoutLocked中,如下:

private static final int DEFAULT_SERVICE_START_FOREGROUND_TIMEOUT_MS = 30 * 1000;
volatile int mServiceStartForegroundTimeoutMs = DEFAULT_SERVICE_START_FOREGROUND_TIMEOUT_MS;

void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) {
    Message msg = mAm.mHandler.obtainMessage(ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG);
    mAm.mHandler.sendMessageDelayed(msg, mAm.mConstants.mServiceStartForegroundTimeoutMs);
}

默认的超时时间是30S,可以通过配置修改。

3.2 何时开启和关闭超时检测

何时开启

首先来看下何时开启检测。我们来来看下何时调用这个方法,只有一处被调用的场景,就是sendServiceArgsLocked方法中。

private final void sendServiceArgsLocked(...){
    ...
    while (r.pendingStarts.size() > 0) {
        ...
        if (r.fgRequired && !r.fgWaiting) {
            if (!r.isForeground) {
                scheduleServiceForegroundTransitionTimeoutLocked(r);
            }else{
                r.fgRequired = false;
            }
        }
    }
}

之前介绍过,sendServiceArgsLocked是在执行生命周期的时候被调用的。

再来看判断条件:r.fgRequired && !r.fgWaiting代表Service要求前台显示,并且还没有开始设置超时等待流程。

接下来是判断条件:!r.isForeground,代表当前是否处于前台状态。

总结一下,就是Service要求前台显示,而且还没有开始超时等待流程,并且未处于前台的时候,就会开始超时检测。

何时结束

移除ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG类型消息有3处调用,我们依次来看。

第一处:

private void bringDownServiceLocked(ServiceRecord r, boolean enqueueOomAdj) {
    if (r.fgRequired) {
        mAm.mAppOpsService.finishOperation(AppOpsManager.getToken(mAm.mAppOpsService),AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null);
        mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
        if (r.app != null) {
            Message msg = mAm.mHandler.obtainMessage(ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG);
            ...
            mAm.mHandler.sendMessage(msg);
        }
    }
}

bringDownServiceLocked代表结束启动Service的流程,结束流程时自然要移除超时消息的注册。

第二处:

void performScheduleRestartLocked(ServiceRecord r, ...) {
    if (r.fgRequired && r.fgWaiting) {
        mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
        r.fgWaiting = false;
    }
}

performScheduleRestartLocked代表重新开启启动Service的流程,这时候自然也要移除超时消息的注册。

第三处:

private void setServiceForegroundInnerLocked(final ServiceRecord r, int id,
        Notification notification, int flags, int foregroundServiceType) {
    if (id != 0) {
        if (r.fgRequired) {
            mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
        }
    }
}

第三处才是我们需要的主流程调用。当调用setServiceForegroundInnerLocked方法时,会停止SERVICE_FOREGROUND_TIMEOUT_MSG类型的超时检测。

所以接下来,我就看下APP如何通知到系统去执行这个方法的。

3.3 APP侧处理流程

前台Service启动时,会分别调用onCreate和onStartCommand方法。而上面开启检测的时机,就是调用生命周期方法onStartCommand方法。

所以在onStartCommand方法之后的20S内,我们需要需要调用startForeground方法绑定前台通知,这时候就会通过binder方法通知到AMS的setServiceForeground()方法,从而结束前台服务类型的超时流程,避免产生ANR。

相关调用流程如下:

3.4 前台服务超时后的流程

这里和service启动超时类型有所不同,哪怕SERVICE_FOREGROUND_TIMEOUT_MSG类型的消息最终超时导致此条消息被执行,也不会立马弹出ANR框。我们看一下超时后执行的方法serviceForegroundTimeout:

//ActivityManagerConstants类
final class ActivityManagerConstants{
    private static final int DEFAULT_SERVICE_START_FOREGROUND_ANR_DELAY_MS = 10 * 1000;
    volatile int mServiceStartForegroundAnrDelayMs = DEFAULT_SERVICE_START_FOREGROUND_ANR_DELAY_MS;
}
//ActiveServices类
public final class ActiveServices{
    //SERVICE_FOREGROUND_TIMEOUT_MSG类型消息触发后执行该方法
    void serviceForegroundTimeout(ServiceRecord r) {
        ProcessRecord app;
        synchronized (mAm) {
            if (!r.fgRequired || !r.fgWaiting || r.destroying) {
                return;
            }
            app = r.app;
            ...
            r.fgWaiting = false;
            stopServiceLocked(r, false);
        }
        if (app != null) {
            final String annotation = "Context.startForegroundService() did not then call "+ "Service.startForeground(): " + r;
            Message msg = mAm.mHandler.obtainMessage(ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_ANR_MSG);
            SomeArgs args = SomeArgs.obtain();
            args.arg1 = app;
            args.arg2 = annotation;
            msg.obj = args;
            mAm.mHandler.sendMessageDelayed(msg,mAm.mConstants.mServiceStartForegroundAnrDelayMs);
        }
    }
    
    //SERVICE_FOREGROUND_TIMEOUT_ANR_MSG类型消息触发后执行该方法
    void serviceForegroundTimeoutANR(ProcessRecord app, String annotation) {
        mAm.mAnrHelper.appNotResponding(app, annotation);
    }
}

我们可以看到主要分成两个流程:

1.执行stopServiceLocked相关的流程;

2.延时10S后去执行SERVICE_FOREGROUND_TIMEOUT_ANR_MSG类型的消息。而这个消息被触发时才是真正的执行ANR流程。

stopServiceLocked之后的流程很复杂,为了方便读者理解,我整理成了如下面所示的流程图:

相关解释:

  1. stopServiceLocked被执行后,会通过一系列的方法调用,最终通过binder方法向应用侧发送消息,通知其发生了超时异常。

  2. 应用的binder线程收到通知后,这时候会分两种场景,主线程有阻塞(3)和主线程没有阻塞(4-7)。

  3. 如果主线程有阻塞,并且达到了10S以上,则应用就不会通知系统发生了崩溃。因此10S之后,ANR流程就会正常执行,弹出ANR框。

  4. 如果主线程没有阻塞,则最终应用会走到Crash流程,通知系统应用发生了Crash。Crash流程可以参考另外一篇文章:android源码学习-android异常处理机制

  5. 系统收到APP发生异常的消息后,则会执行异常流程,记录相关异常日志,最终通过ShellCommand命令去结束掉APP的进程

  6. APP进程被杀死后,系统会收到相关的通知,然后对该进程在系统中的绑定对象ProcessRecord中的相关成员变量进行状态同步。其中,就会做如下设置:

    mPid = 0
    mKilled = true;
  7. 10S之后,执行serviceForegroundTimeoutANR流程,进入到ANR流程的时候,会判断pid否为0,此时pid如果等于0,则就不会执行后面的ANR流程了。

    void appNotResponding(...){
        final int incomingPid = anrProcess.mPid;
        if (incomingPid == 0) {
            Slog.i(TAG, "Skip zero pid ANR, process=" + anrProcess.processName);
            return;
        }
        ...
    }

因此,如果应用进程不阻塞的话,系统会记录一条异常崩溃日志。

3.5 小结

这里也做一个总结,也就是说当启动前台服务类型的Service时,在Service启动后的30S内,APP需要主动调用startForeground()方法进行相关的前台通知绑定,否则一旦到了超时时间,就会进入超时流程。

进入到超时流程后,看应用进程是否阻塞,来决定到底是走ANR无响应流程还是Crash异常流程。

相关流程图如下:

四.Service类型ANR实例说明

按照上面的讲解,我们知道Service类型的ANR有2种类型,分别是service启动超时和前台服务未绑定超时。

我们接下来就举几个例子,依次说明各种场景下的Service超时。

例子1:service的onCreate中进行耗时操作

代码如下:

public class ThreadService extends IntentService {
    @Override
    public void onCreate() {
        super.onCreate();
        Log.i("ThreadService", "ThreadService onCreate");
        try {
            Thread.sleep(60_000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

然后通过代码启动:

 val intent = Intent(this, ThreadService::class.java)
 startForegroundService(intent)

这里使用的是startService方法,而不是startService,如果后台启动的话,就需要休眠200S以上了。最终实验结果产生了“executing service ...”类型的ANR。

例子2:service的onStartCommand中进行耗时操作

代码如下:

public class ThreadService extends IntentService {
    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        Log.i("ThreadService", "onStartCommand");
        try {
            Thread.sleep(60_000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return super.onStartCommand(intent, flags, startId);
    }
}

和例子1中使用同样的方式启动service,最终实验结果产生了“executing service ...”类型的ANR。

例子3:application中进行耗时操作

Application中增加耗时操作,去掉Service中的耗时操作。

public class DemoApplication extends Application {

    public void onCreate() {
        super.onCreate();
        try {
            Thread.sleep(25_000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

和例子1中使用同样的方式启动service,最终实验结果产生了“executing service ...”类型的ANR。

例子4:Application.onCreate/Service.onCreate和Service.onStartCommand都有耗时操作

相关代码如下:

public class DemoApplication extends Application {

    public void onCreate() {
        super.onCreate();
        try {
            Thread.sleep(8_000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ThreadService extends IntentService {
    @Override
    public void onCreate() {
        super.onCreate();
        Log.i("ThreadService", "ThreadService onCreate");
        try {
            Thread.sleep(8_000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        Log.i("ThreadService", "onStartCommand");
        try {
            Thread.sleep(8_000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return super.onStartCommand(intent, flags, startId);
    }
}

共计有三处进行了sleep操作,每次8S。前台服务的超时时间为20S,只有三次时间累积,才会发生ANR。

和例子1中使用同样的方式启动service,最终实验结果产生了“executing service ...”类型的ANR。

所以说明三次累积时间超过设置的超时时间,就会产生ANR。

当然这个结论并不绝对,因为系统中是判断是否移除超时检查的判断条件为当前未执行的任务是否为0,所以如果application和onCreate中执行速度较快,而system_server中发生了卡顿,这时候会产生onCreate任务已经执行完成,但是生命周期方法还未注册的场景,此时未执行的任务为0,所以会移除超时任务,从0开始计算。

例子5:前台服务未绑定,并且主线程阻塞

因为前台超时服务的时间是30S,所以我们延时29秒阻塞主线程,这样系统传递过来的异常信息,就会被阻塞得不到处理,我们阻塞15S就好,因为上面说到,系统对于前台服务类型的等待时间只有20S。

代码如下:

public class ThreadService extends IntentService {
    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        Log.i("ThreadService", "onStartCommand");
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(15_000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, 29_000);
        return super.onStartCommand(intent, flags, startId);
    }
}

和例子1中使用同样的方式启动service,最终产生了ANR,相关日志如下:

2023-03-31 11:35:30.459  1736-1968  ActivityManager         pid-1736                             W  Bringing down service while still waiting for start foreground: ServiceRecord{99501ef u0 com.xt.client/.service.ThreadService}
...
2023-03-31 11:35:40.593  1736-26437 ActivityManager         pid-1736                             E  ANR in com.xt.client
                                                                                                    PID: 26405
                                                                                                    Reason: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{99501ef u0 com.xt.client/.service.ThreadService}
                                                                                                    ErrorId: 3b070281-4717-4b64-83a3-ec41a48f5e9a

例子6:前台服务未绑定,并且主线程未阻塞

代码如下:

public class ThreadService extends IntentService {
    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        Log.i("ThreadService", "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }
}

和例子1中使用同样的方式启动service,最后没有弹出ANR框,而是发生了Crash,相关日志如下,这个流程和我们3.4中分析一模一样。

//进程创建
2023-03-30 17:31:59.002 26981-26981 lxltest                 com.xt.client                        I  DemoApplication init
//Service完成创建
2023-03-30 17:31:59.026 26981-26981 ThreadService           com.xt.client                        I  ThreadService onCreate
2023-03-30 17:31:59.027 26981-26981 ThreadService           com.xt.client                        I  onStartCommand
//APP发生异常,VM虚拟机关闭,并且通知系统发生异常
2023-03-30 17:32:28.717 26981-26981 AndroidRuntime          com.xt.client                        D  Shutting down VM
2023-03-30 17:32:28.720 26981-26981 AndroidRuntime          com.xt.client                        E  FATAL EXCEPTION: main
                                                                                                    Process: com.xt.client, PID: 26981
//发出进程退出信号
2023-03-30 17:45:25.142 27448-27448 Process                 com.xt.client                        I  Sending signal. PID: 27448 SIG: 9
//进程被杀死,收到回调通知,进行清理操作
2023-03-30 17:45:25.172  1736-1981  ActivityManager         pid-1736                             I  Process com.xt.client (pid 27448) has died: cch+5 CEM
2023-03-30 17:45:25.178  1736-2014  libprocessgroup         pid-1736                             I  Successfully killed process cgroup uid 10235 pid 27448 in 5ms
//因为pid为0,所以跳过ANR流程,没有弹出ANR的提示框。
2023-03-30 17:45:35.128  1736-1968  ActivityManager         pid-1736                             I  Skip zero pid ANR, process=com.xt.client

五.总结

所以我们总结一下service的ANR类型,有两种:

第一种:executing service ...类型。

application创建时间+Service.onCreate时间+Service.onStartCommand时间大于超时时间(前台服务20S,后台服务200S)。

第二种:Context.startForegroundService() ..类型

这种类型只针对前台服务才会生效。启动前台服务后,需要在30S内完成notification的绑定,如果时间到了未能绑定,并且此时应用主线程阻塞10S以上,就会出现这种类型的ANR。

猜你喜欢

转载自blog.csdn.net/AA5279AA/article/details/129878691
ANR