ANR系列之五:Service类型ANR原理讲解(已废弃)

声明:

后来发现这篇文章写的有些疏漏,所以索性就重新写了,新文章地址:

ANR系列之五:Service类型ANR原理讲解_失落夏天的博客-CSDN博客

是对这一个版本的修正补充,推荐直接阅读新文章即可。

新文章应该是目前网络上对Service类型ANR讲解最细致的一篇文章了。

前言:

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

遵循这样的一个顺序:

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

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

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

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

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

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

本文主要讲解内容如下:

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

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

3.什么场景下,可以触发service类型的ANR。

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

https://blog.csdn.net/rzleilei/article/details/128452528

一.Service类型ANR如何触发?

1.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);
    }
}

1.2 Service类型ANR的触发流程

serviceTimeout的调用流程其实也是很简单的,其核心就是一个延时消息机制。

Active注册一个延时消息,然后继续后面的流程,通知APP去执行对应的流程,APP执行完成后通知回系统,进行对应的延时消息的取消。

如果APP侧因为各种原因超时,没有按照的通知回APP,则就会发生service类型的ANR。

 这里超市时间有两个配置,区分前台service还是后台service。

void scheduleServiceTimeoutLocked(ProcessRecord proc) {
        if (proc.mServices.numberOfExecutingServices() == 0 || proc.getThread() == null) {
            return;
        }
        Message msg = mAm.mHandler.obtainMessage(
                ActivityManagerService.SERVICE_TIMEOUT_MSG);
        msg.obj = proc;
        mAm.mHandler.sendMessageDelayed(msg, proc.mServices.shouldExecServicesFg()
                ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
    }


// 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;

前台的超时时间为20S,后台为200S。

1.3 何时触发超时检查流程

整个Service启动流程中,在APP侧其实有多个生命周期的回调,每一个都有可能会超时,所以自然的,整个启动流程中,超时检查的机制并不止一次。

首先,在通知APP侧去创建Service时,会触发第一次的超时检查。

具体方法在realStartServiceLocked中,如下:

private void realStartServiceLocked(...) throws RemoteException {
    //第一次超时检查
     bumpServiceExecutingLocked(r, execInFg, "create", null /* oomAdjReason */);
    //通知APP去创建,创建完成后APP会通知回系统侧取消注册超时消息
    thread.scheduleCreateService(r, r.serviceInfo,
                    mAm.compatibilityInfoForPackage(r.serviceInfo.applicationInfo),
                    app.mState.getReportedProcState());
    //第二次超时检查
     sendServiceArgsLocked(r, execInFg, true);
}

其次,通知APP侧执行onStartCommand流程时,也会触发一次超时检查。

private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg,
            boolean oomAdjusted) throws TransactionTooLargeException {
    //第二次超时检查
    bumpServiceExecutingLocked(r, execInFg, "start", null /* oomAdjReason */);
    //通知到APP一侧
    r.app.getThread().scheduleServiceArgs(r, slice);
}

也就是说,系统侧在通知APP侧之前,会提前发送一个延时消息。如果APP正常完成了流程,则会通知回系统侧结束掉这个延时的消息,就不会触发ANR的流程了。

我们接下来,就看下APP侧收到通知后的流程。

二.APP侧如何处理的

2.1 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方法通知回系统侧。serviceDoneExecuting方法具体如何执行取消注册流程的,我们2.3来讲。

2.2 service中onStartCommand生命周期的回调

APP侧最终处理scheduleServiceArgs的方法是ActivityThread中的handleServiceArgs方法:

private void handleServiceArgs(ServiceArgsData data) {
        CreateServiceData createData = mServicesData.get(data.token);
        Service s = mServices.get(data.token);
        if (s != null) {
            try {
                ...
                int res;
                if (!data.taskRemoved) {
                    res = s.onStartCommand(data.args, data.flags, data.startId);
                } else {
                    s.onTaskRemoved(data.args);
                    res = Service.START_TASK_REMOVED_COMPLETE;
                }

                ...
                try {
                    ActivityManager.getService().serviceDoneExecuting(
                            data.token, SERVICE_DONE_EXECUTING_START, data.startId, res);
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            } catch (Exception e) {
                if (!mInstrumentation.onException(s, e)) {
                    throw new RuntimeException(
                            "Unable to start service " + s
                            + " with " + data.args + ": " + e.toString(), e);
                }
            }
        }
    }

我们可以看到,调用了service的onStartCommand方法之后,也调用了serviceDoneExecuting方法进行了回调通知。

2.3 serviceDoneExecuting回调通知

我们看下ActivityManagerService中的serviceDoneExecuting方法:

public void serviceDoneExecuting(IBinder token, int type, int startId, int res) {
        synchronized(this) {
            if (!(token instanceof ServiceRecord)) {
                Slog.e(TAG, "serviceDoneExecuting: Invalid service token=" + token);
                throw new IllegalArgumentException("Invalid service token");
            }
            mServices.serviceDoneExecutingLocked((ServiceRecord) token, type, startId, res, false);
        }
    }

完全委托给ActiveService来处理的。

serviceDoneExecutingLocked中的逻辑有些复杂,我这精简一下,只保留最核心的。

就是取消超时类型消息注册的代码,如下:

private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
            boolean finishing, boolean enqueueOomAdj) {
       ...
        mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
       ...
    }

三.Service类型ANR实例

按照上面的分析,我们知道有两种场景都会导致service类型的ANR触发。

分别是:

1.service创建流程

2.service执行onStartCommand生命周期流程。

当然这两步只是流程,具体导致的原因又可能会有很多了。如果service是首次创建的话,那么还会创建Application,Application创建耗时一样会导致Service类型的ANR事件。

我们这里做两个例子,验证下我们的猜测:

例子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)
 startService(intent)

这里使用的是startService方法,而不是startForegroundService,如果后台启动的话,就需要休眠200S以上了。

最终实验结果,果然产生了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);
    }
}

同样启动service,果然也发现出现了Service类型的ANR。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

失落夏天

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

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

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

打赏作者

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

抵扣说明:

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

余额充值