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

前言:

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

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

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

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

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

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

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

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

本文主要讲解内容如下:

1.service类型的ANR在系统侧是如何触发的;

2.service类型的ANR在APP侧的执行流程;

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所对应的APP进程都未存在的情况下,被第三方应用唤起,此时service需要在10S内完成setForeground的操作,否则就会提示“Context.startForegroundService() did not then call Service.startForeground():..”类型的ANR。这里的service只针对前台服务。

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

ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG

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

二.Service启动超时类型

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

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

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

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完成了相关操作。

再来看一下何时取消?取消的代码在serviceDoneExecutingLocked方法中:

private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,boolean finishing, boolean enqueueOomAdj) {
    ...
    mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.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启动时,当第一个生命周期的任务执行时,或者是前台应用启动的,都会开启超时检测。接下来,我们就看下哪里调用到bumpServiceExecutingLocked方法,之前在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方法并不代表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.如果通知失败,则结束刚才创建的那些超时检测。

所以这里我们做一个总结,通知应用去创建或者去执行生命周期方法的之前,都会分别开启一个超时检测。如果通知APP的过程中发生异常,则会主动结束超时检测。

2.3 关闭超时检测

刚才是发送了SERVICE_TIMEOUT_MSG类型消息,如果到了执行时间并且没有取消,则就会提示ANR。所以,自然会有一个移除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启动流程结束了。

接下来,我们看一下,到底有哪些场景会调用serviceDoneExecutingLocked方法移除SERVICE_TIMEOUT_MSG消息。

首先,上面有介绍过,启动异常时,会结束。包含realStartServiceLocked/sendServiceArgsLocked中的调用;

其次,进程已出现异常时,也会结束。包含serviceProcessGoneLocked中的调用;

最后,就是来自AMS的通知了,这个通知最终是来自于APP一侧,对应AMS中的serviceDoneExecuting方法。这也是我们主流程的调用,我们来看下其流程。

无论是创建还是走生命周期,完成后都会通知到AMS的serviceDoneExecuting方法。所以,当Service的attach/onCreate/onStartCommads方法中如果有耗时操作的话,就会导致ANR的问题。

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。

当然,如果在Application.attach方法中就阻塞的话,就不会提示Service类型的ANR了。

2.6 小结

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

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

总体流程图如下:

三.前台服务超时类型

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

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

前台服务型ANR和启动超时型ANR有一些区别,前台服务型ANR中会有两种类型的消息。

static final int SERVICE_FOREGROUND_TIMEOUT_MSG = 66;
static final int SERVICE_FOREGROUND_TIMEOUT_ANR_MSG = 67;

SERVICE_FOREGROUND_TIMEOUT_MSG:前台服务类型的Service,执行sendServiceArgsLocked通知APP的时候,就会启动一个SERVICE_FOREGROUND_TIMEOUT_MSG类型的延时消息。

延时时间为10S(9.0版本)和30S(12+版本),到了时间并且未移除该消息,就会执行serviceForegroundTimeout方法。

SERVICE_FOREGROUND_TIMEOUT_ANR_MSG:serviceForegroundTimeout中注册的一个延时消息。

延时时间为10S。到了时间并且未移除该消息,就会执行serviceForegroundTimeoutANR方法走ANR触发流程。

PS:9.0版本无该功能,会直接走ANR触发流程。

3.2 何时开启和关闭前台服务类型超时检测

3.2.1 何时开启

首先来看下何时开启检测。我们来来看下何时调用这个方法,只有一处被调用的场景,就是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要求前台显示,而且还没有开始超时等待流程,并且未处于前台的时候,就会开始超时检测。

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

3.2.2 何时结束

移除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的流程,这时候自然也要移除超时消息的注册。

第三处:

public final class ActiveServices {
    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如何通知到系统去执行这个方法的。

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

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

相关调用流程如下:

3.4 前台服务超时后的流程

走到了serviceForegroundTimeout中的超时逻辑后,就一定会ANR吗?当然不一定,这个我们最开始那个小节就介绍过,还有一个SERVICE_FOREGROUND_TIMEOUT_ANR_MSG类型的消息。

9.0以下版本:

这里首先区分一下版本,9.0的版本,走到了serviceForegroundTimeout流程后,是一定会触发ANR流程的。相关代码如下:

void serviceForegroundTimeout(ServiceRecord r) {
    ProcessRecord app;
    synchronized (mAm) {
        if (!r.fgRequired || r.destroying) {
            return;
        }
        app = r.app;
        if (app != null && app.debugging) {
            // The app's being debugged; let it ride
            return;
        }
        if (DEBUG_BACKGROUND_CHECK) {
            Slog.i(TAG, "Service foreground-required timeout for " + r);
        }
        r.fgWaiting = false;
        stopServiceLocked(r);
    }

    if (app != null) {
        mAm.mAppErrors.appNotResponding(app, null, null, false,
                "Context.startForegroundService() did not then call Service.startForeground(): "
                    + r);
    }
}

可以看到,走到了serviceForegroundTimeout方法后,直接走appNotResponding流程,收集ANR日志,并且弹出ANR提示框。

13.0以上版本:

我们看一下12+的版本中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启动后的10S(看版本有区别)内,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

五.总结

所以我们总结一下servic出现ANR的情况

第一种:executing service ...类型

application.onCreate时间+Service.onCreate时间+Service.onStartCommand时间大于超时时间(前台服务20S,后台服务200S),就会触发启动超时类型ANR。

为什么application的onCreate时间也被计算在内?因为application.attach的时候就会通知系统去启动service,哪怕此时主线程阻塞了,系统的启动任务仍然会派发给APP,并且开始超时检测。

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

首先有一个前提,就是启动的是前台服务。

另外要区分一下版本。

9.0及以下版本,并且application.onCreate时间+Service.onCreate时间+Service.onStartCommand时间大于10S,则就会触发前台服务类型ANR。

13及以后的版本,则分为几种情况:

1.如果application.onCreate时间+Service.onCreate时间+Service.onStartCommand时间大于40S,不过这种情况下会优先触发executing service ...类型的ANR,所以这种情况不存在。

2.Service.onStartCommand中主线程阻塞30S以上。和上面同样的原因,会优先触发executing service ...类型的ANR,所以这种情况也不存在。

3.前面的周期都没有超时,但是没有进行前台视图绑定。在onStartCommand之后主线程阻塞10S以上,此时无法接受系统侧的异常通知,就会发生ANR。

  • 4
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

失落夏天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值