ANR如何产生之Service和Broadcast篇

833 阅读4分钟

ANR类型

类型超时时间是否有弹窗提示
Activity Timeout10s有提示
BroadcastReceiver Timeout10s, 60s无感知场景不会提示,如在后台
Service Timeout20s, 200s无感知场景不会提示
ContentProvider Timeout10s无感知场景不会提示
InputDispatching Timeout5s有提示

之前的文章,我们分析了Input ANR是如何产生的,具体可以参考:ANR如何产生之InputDispatching Timeout

今天这篇文章,我们来讲讲BroadcastReceiverService Timeout ANR分别是怎样产生的。

系统检测ANR的核心原理如下:

  • 在流程开始时,启动一个计时器,比如往MessageQueue加入一个定时执行的Message
  • 如果流程在规定的时间内结束,则关闭计时器,比如移除这个Message
  • 如果流程未按时结束,执行该Message,触发ANR

Service Timeout

1. 启动Service时,设置计时器

Service启动的流程,省略zygote fork进程的流程,最后会在system_server进程中调用到ActiveServices中的realStartServiceLocked方法。

private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException {
    // 发送delay消息(SERVICE_TIMEOUT_MSG)
    bumpServiceExecutingLocked(r, execInFg, "create");
    try {
        // 跨进程调用,调用Service所在进程的ActivityThread中的方法
        app.thread.scheduleCreateService(r, r.serviceInfo,
                mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
                app.repProcState);
    } catch (DeadObjectException e) {
    } 
}

在这个方法里,主要做了两件事情:

  • 发送SERVICE_TIMEOUT_MSGdelay时间为SERVICE_TIME_OUT
  • 跨进程调用,调用Service所在进程的ActivityThread中的Service初始化方法

bumpServiceExcutingLocked就是往MesageQueue里放一个定时执行的Message

void scheduleServiceTimeoutLocked(ProcessRecord proc) {
    // 当超时后仍没有remove该SERVICE_TIMEOUT_MSG消息,则执行service Timeout流程
    mAm.mHandler.sendMessageAtTime(msg,
        proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT));
}

这个定时执行的Message的执行时间是,当前时间加上SERVICE_TIME_OUT

SERVICE_TIME_OUT有两种情况:

  • 如果是前台ServiceSERVICE_TIMEOUT = 20s
  • 如果是后台ServiceSERVICE_BACKGROUND_TIMEOUT = 200s

2. Service执行完,移除计时器

Service所在进程的ActivityThread中,执行Service的初始化方法。

private void handleCreateService(CreateServiceData data) {
        java.lang.ClassLoader cl = packageInfo.getClassLoader();
        Service service = (Service) cl.loadClass(data.info.name).newInstance();
        try {
            //创建ContextImpl对象
            ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
            context.setOuterContext(service);
            //创建Application对象
            Application app = packageInfo.makeApplication(false, mInstrumentation);
            service.attach(context, this, data.info.name, data.token, app,
                    ActivityManagerNative.getDefault());
            //调用服务onCreate()方法 
            service.onCreate();
            // 通知system_server,service启动完成
            ActivityManagerNative.getDefault().serviceDoneExecuting(
                    data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
        } catch (Exception e) {
            ...
        }
    }

从上面的方法可以看到,当Service在进程中初始化完成后,会通过跨进程调用,通知system_server

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

3. 如果未按时执行完,执行Message

final class MainHandler extends Handler {
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case SERVICE_TIMEOUT_MSG: {
                mServices.serviceTimeout((ProcessRecord)msg.obj);
            } break;
        }
    }
}

AMSMainHandler中,会处理SERVICE_TIMEOUT_MSG,调用ActiveServiceserviceTimeout方法。

void serviceTimeout(ProcessRecord proc) {
    String anrMessage = null;
    synchronized(mAm) {
        if (timeout != null && mAm.mLruProcesses.contains(proc)) {
            // 拼接anr信息
            anrMessage = "executing service " + timeout.shortName;
        }
    }
    if (anrMessage != null) {
        //当存在timeout的service,执行appNotResponding
        mAm.appNotResponding(proc, null, null, false, anrMessage);
    }
}

在这个方法里,主要完成拼接ANR信息,以"executing service"开头,同时调用AMSappNotResponding处理后续的dump和弹窗逻辑。

appNotResponding的处理和采集信息流程,可以参考这篇文章:Android发生ANR后的信息采集过程

Broadcast Receiver Timeout

广播分为有序广播和无序广播。

对于无序广播,遍历所有的广播接收者并发送,不关心接收者是否处理完广播消息。

有序广播,需要等上一个接收者处理完之后,才能发送给后续的接收者,所以会发生ANR。

1. 发送广播时,设置计时器

发送广播的流程,最后会调用processNextBroadcastLocked来处理广播。这个方法特别长,我们只截取其中需要的部分。

final void processNextBroadcast(boolean fromMsg) {
    synchronized(mService) {
        ...
        // 处理当前有序广播
        do {
            r = mOrderedBroadcasts.get(0);
            // 获取该广播所有的接收者
            int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
            if (mService.mProcessesReady && r.dispatchTime > 0) {
                long now = SystemClock.uptimeMillis();
                if ((numReceivers > 0) &&
                        (now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))) {
                    //当广播处理时间超时,则强制结束这条广播
                    broadcastTimeoutLocked(false);
                }
            }
            // 当广播分发结束,调用了finishReceiverLocked后,r.receivers为null
            if (r.receivers == null || r.nextReceiver >= numReceivers
                    || r.resultAbort || forceReceive) {
                if (r.resultTo != null) {
                    //处理广播消息消息
                    performReceiveLocked(r.callerApp, r.resultTo,
                        new Intent(r.intent), r.resultCode,
                        r.resultData, r.resultExtras, false, false, r.userId);
                    r.resultTo = null;
                }
                // 移除MSG
                cancelBroadcastTimeoutLocked();
            }
        } while (r == null);

        r.receiverTime = SystemClock.uptimeMillis();
        if (!mPendingBroadcastTimeoutMessage) {
            long timeoutTime = r.receiverTime + mTimeoutPeriod;
            // 设置广播的超时MSG
            setBroadcastTimeoutLocked(timeoutTime);
        }

        // 处理动态广播
        if (nextReceiver instanceof BroadcastFilter) {
            deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);
            return;
        }

        // 省略处理静态广播的流程
    }
}

有两种情况会引发BroadcastReceiver的超时:

  • 某个广播的总处理时长 > 2 * receiver总个数 + mTimeoutPeriod
  • 某个receiver的处理市场超过mTimeoutPeriod

setBroadcastTimeoutLocked的原理就是往Handler里发送一个BROADCAST_TIMEOUT_MSG。

    final void setBroadcastTimeoutLocked(long timeoutTime) {
        if (! mPendingBroadcastTimeoutMessage) {
            Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
            mHandler.sendMessageAtTime(msg, timeoutTime);
            mPendingBroadcastTimeoutMessage = true;
        }
    }

2. 移除计时器

cancelBroadcastTimeoutLocked的调用时机是,广播结束后,调用AMSfinishReceiver方法,然后调用nextReceiver,最后调用到processNextBroadcastLocked方法中。

因为这个时候,r.receivers null,所以会执行cancelBroadcastTimeoutLocked方法。

总结

Service Timeout ANR发生原理:

  • 启动Service时,发送一个延时执行的SERVICE_TIMEOUT_MSG
  • 如果Service在规定的时间内启动,则移除SERVICE_TIMEOUT_MSG
  • 如果Service没有在规定的时间内启动,则执行SERVICE_TIMEOUT_MSG

注意:以上判断ANR的逻辑,都在system_server进程中执行。