美文网首页
ANR是如何产生之Service和Broadcast篇

ANR是如何产生之Service和Broadcast篇

作者: 尹学姐 | 来源:发表于2023-03-14 22:55 被阅读0次

    ANR类型

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

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

    今天这篇文章,我们来讲讲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的处理和采集信息流程,可以参考这篇文章:[ANR] 发生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.receiversnull,所以会执行cancelBroadcastTimeoutLocked方法。

    总结

    Service Timeout ANR发生原理:

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

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

    相关文章

      网友评论

          本文标题:ANR是如何产生之Service和Broadcast篇

          本文链接:https://www.haomeiwen.com/subject/oytsldtx.html