ANR是如何产生之Service和Broadcast篇

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进程中执行。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,451评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,172评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,782评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,709评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,733评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,578评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,320评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,241评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,686评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,878评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,992评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,715评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,336评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,912评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,040评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,173评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,947评论 2 355

推荐阅读更多精彩内容