稳定性优化:ANR产生原理

5,393 阅读20分钟

本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!

在性能优化的所有方向中,稳定性优化是最重要的一项,因为即使程序在其他方向的优化做的再好,但是如果在使用过程中,经常出现无响应或者崩溃,那么用户也是不能忍受的,很大概率会卸载程序或者减少使用时长,因此稳定性是一款程序的基石。

想要做好稳定性优化,就需要先掌握稳定性相关的底层知识和原理,常见的稳定性主要是 ANR,Crash 这两类,所以在接下来的文章中,我们会详细的去了解这两类问题的产生的原理,底层的机制等基础性知识。本章我先讲ANR的产生原理。

ANR(Application Not Responding)指的是应用长时间无法响应用户的操作,这种情况发生时,系统会出现弹窗,让我们选择是否强行关闭程序。如图所示是一个常见的 ANR 弹窗。

一 ANR 的类型

Android 系统中定义的 ANR 有下面这四类:

  • InputDispatching TimedOut(输入事件分发超时):应用在 5 秒内无法处理触摸,按键事件等输入事件时触发
  • BroadcastReceiver Timeout(广播接收超时):前台广播 10 秒、后台广播 60 秒内未执行完成 onReceiver 方法时触发
  • Service Timeout(服务启动超时):前台 Service 在 20 秒内、后台 Service 在 200 秒内未启动完成时触发
  • ContentProvider Timeout(内容提供者发布超时):10 秒内没有没有完成发布流程则触发

仅仅是简单了解这几种 ANR 的类型定义对进行 ANR 优化来说是远远不够的,而是需要深入到 Android 源码中去了解这些 ANR 产生的机制和原理。网上有很多对这四类 ANR 原理介绍的文章,但大都冗余且复杂,很容易让人迷失在代码的海洋中,所以笔者在这里尽量精简且聚焦的来讲解这几类 ANR 产生的机制和原理。

1. 输入事件分发超时

在了解输入事件分发超时之前,我们需要先了解什么是输入事件分发,它是指 Android 系统将用户触摸、按键等操作事件传递给对应程序的 Activity,Fragment 等组件的过程,这个过程主要在 InputFlinger 进程进行,涉及事件的捕获、传递、处理和响应等多个环节。该流程中的主要成员对象有 EventHub, InputReader,InputDispatcher,这几个对象详细的作用如下:

  • EventHub:EventHub 从底层的输入设备驱动程序接收原始的输入事件数据,并将原始事件数据转换为触摸事件(MotionEvent)、按键事件(KeyEvent)等输入事件对象,并传递给 InputReader 线程。
  • InputReader:InputReader 是一个线程,它会不断读取的 EventHub 中的输入事件,并根据设备类型和输入源,对输入事件进一步进行转换,加工,和分类。比如将多点触控事件转换为手势事件,将按键事件转换为字符事件等。
  • InputDispatcher:InputDispatcher 也是一个线程,从该对象的名称也能明白它的作用主要是进行事件的分发。它从 InputReader 那里接收加工后的输入事件后,会根据分发策略,将输入事件分发给合适的窗口或程序,在分发事件时,会根据输入事件的类型,设定一个超时时间,如果在超时时间内没有收到对应的窗口或程序的消费结果,InputDispatcher 便会认为窗口或应用无响应,触发 “InputDispatching TimedOut” 。

这三个对象处理事件分发的简化流程如图所示。

whiteboard_exported_image-14.png

事件分发的流程非常的长,我们主要聚焦在 ANR 的触发逻辑上。在分发的过程中会有多个原因触发 “InputDispatching TimedOut” 这个 ANR,这些原因都可以归于窗口未就绪和窗口处理超时这两类,分别如下:

  • 窗口未就绪:InputDispatcher 要分发的窗口没有准备好接收新的输入事件,例如窗口已暂停、窗口连接已死亡、窗口连接已满等情况,那么 InputDispatcher 会等待窗口就绪,如果等待时间超过 5 秒,就会触发 ANR
  • 窗口处理超时:InputDispatcher 将输入事件分发给窗口后,如果窗口在 5 秒内没有返回事件处理结果给 InputDispatcher, 则 InputDispatcher 会认为窗口处理超时,触发 ANR

在实际场景中,大部分的输入事件分发超时都是因为窗口处理超时导致,而窗口处理超时中最常见的,又是主线程处理任务超时导致的。所以我们接着了解窗口处理超时导致 ANR 的机制流程。这一流程主要有这四步:

  1. 对 InputDispatcher 来说,每一个窗口都以一个 Connection 对象来维护, 在事件分发时,InputDispatcher 会先寻找到正确的窗口 Connection 对象。
  2. 当 InputDispatcher 找到对应的 Connection 后,就会通过 Socket 通信将事件分发给程序窗口,程序窗口会通过 InputChannel 对象来接收事件,InputDispatcher 分发完该事件后,会接着将事件放入到 waitQueue 队列中,
  3. 如果程序的窗口的主线程消费了这个事件,InputChannel 会通过 Socket 通信来通知 InputDispatcher 从 waitQueue 队列移除这个事件
  4. 当 InputDispatcher 在下一次事件分发时,会判断 waitQueue 队列中是否有事件在五秒内没被移除,如果有就会认为发生了 ANR。

这一流程如图所示。

whiteboard_exported_image-15.png

我们接着通过源码来更深入的了解 InputDispatcher 分发事件,waitqueue 队列添加和移除事件,以及 InputDispatcher 通过 waitqueue 队列中的事件判断 ANR 发生这三个关键步骤的实现。

1)InputDispatcher 分发事件

InputDispatcher 是一个在不断运行的线程,该线程会循环的执行 dispatchOnce 方法来进行事件的分发。该方法的代码如下

void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;
    { 
        std::scoped_lock _l(mLock);
        mDispatcherIsAlive.notify_all();
        // 1. 分发输入事件
        if (!haveCommandsLocked()) {
            dispatchOnceInnerLocked(&nextWakeupTime);
        }
        // 2. 处理输入命令
        if (runCommandsLockedInterruptable()) {
            nextWakeupTime = LLONG_MIN;
        }
        // 3. 处理 ANR ,并返回下一次线程唤醒的时间。
        const nsecs_t nextAnrCheck = processAnrsLocked();
        nextWakeupTime = std::min(nextWakeupTime, nextAnrCheck);

        if (nextWakeupTime == LONG_LONG_MAX) {
            mDispatcherEnteredIdle.notify_all();
        }
    } 
    nsecs_t currentTime = now();
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    // 4. 线程休眠 timeoutMillis 毫秒
    mLooper->pollOnce(timeoutMillis);
}

该方法主要做的事情有这四件:

  1. 调用 dispatchOnceInnerLocked 方法来分发事件
  2. 调用 runCommandsLockedInterruptable 方法处理输入命令,比如横竖屏切换,音量的调节等都是输入命令,该方法中会对输入命令封装,然后分发给目标对象处理
  3. 调用 processAnrsLocked 来判断是否发生了 ANR,该方法会去 waitQueue 寻找是否有超过 5S 未被处理输入事件,如果有则抛出 ANR
  4. 基于性能考虑将当前线程休眠一定时间,直到休眠结束或者有新的输入事件要分发而被唤醒

dispatchOnceInnerLocked 即为事件分发的方法,但由于这个方法内部逻辑和路径非常多,我们在这里先通过时序图,如下图所示,来了解该方法的主路径。

在时序图中,序列 3 findToucheWindowTargetsLocked 方法会根据焦点寻找目标窗口,序列 7 startDispatchCyclelocked 便会通过对应 Connection 将事件分发给程序的 InputChannel 中,我们主要看看这个方法的实现。该方法的简化代码如下所示。

void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection) {
    // 遍历 Connection 的 outboundQueue 队列,直到队列为空或者 Connection 状态异常
    while (connection->status == Connection::STATUS_NORMAL && 
            !connection->outboundQueue.isEmpty()) {
        // 取出队列头部的 DispatchEntry 对象
        DispatchEntry* dispatchEntry = connection->outboundQueue.head;
        // 设置分发时间为当前时间
        dispatchEntry->deliveryTime = currentTime;
        status_t status;
        // 获取对应的 EventEntry 对象
        EventEntry* eventEntry = dispatchEntry->eventEntry;
        // 1. 根据事件类型,调用不同的 publish 函数,将输入事件发送给目标窗口或应用
        switch (eventEntry->type) {
            case EventEntry::TYPE_KEY: {
                status = connection->inputPublisher.publishKeyEvent(……);
                break;
            }
            case EventEntry::TYPE_MOTION: {
                status = connection->inputPublisher.publishMotionEvent(……);
                break;
            }
            default:               
                return;
        }
        // 2. 判断发送状态是否正常
        if (status) {   
            if (status == WOULD_BLOCK) {
                 /*如果发送状态为 WOULD_BLOCK,
                   表示目标窗口或应用的 InputChannel 缓存区已满,无法接收新的输入事件
                    此时会直接中断分发循环,触发 ANR*/
               ……
            } else {
                /* 如果发送状态为其他错误,表示发送过程出现了异常,
                也直接中断分发循环,触发 ANR*/
                abortBrokenDispatchCycleLocked(currentTime, connection, true);
            }
            return;
        }
        // 3. 如果发送状态正常,就将 DispatchEntry 对象从 outboundQueue 队列中移除
        connection->outboundQueue.dequeue(dispatchEntry);
        // 将 DispatchEntry 对象添加到 waitQueue 队列的尾部,等待目标窗口或应用的反馈
        connection->waitQueue.enqueueAtTail(dispatchEntry);
        // 将 DispatchEntry 对象添加到 mAnrTracker 中,用于追踪输入事件的超时情况
        mAnrTracker.insert(dispatchEntry);
    }
}

该方法中主要有三个流程,分别如下:

  1. 根据输入事件的类型(eventEntry->type)选择目标窗口(connection)对应的事件分发函数(publishMotionEvent 或 publishKeyEvent)来进行事件分发
  2. 如果事件分发失败,此时会中断分发并触发 ANR,此时的 ANR 属于窗口未就绪导致的 ANR
  3. 最后将该输入事件放到 waitQueue 中,用于后续进行 ANR 超时判断

到这里我们就了解了 InputDispatcher 是如何进行事件分发的,在最后一步中,InputDispatcher 将事件放入到了 waitQueue 中,如果该队列中的事件超过 5 秒没被移除就会触发 ANR,所以我们接着看一下 waitQueue 中的事件是如何被移除的。

2)InputDispatcher 移除 waitQueue 队列事件

当目标窗口的主线程处理完输入事件后,便会通过 Socket 来通知 InputDispatcher 事件已经消费,InputDispatcher 会在 handleReceiveCallback 方法中处理这一流程,该方法的简化代码如下所示。

int InputDispatcher::handleReceiveCallback(int events, sp<IBinder> connectionToken) {
    std::scoped_lock _l(mLock);
    std::shared_ptr<Connection> connection = getConnectionLocked(connectionToken);
    ……
    if (!(events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP))) {
        ……
        for (;;) {
            //1. 接收窗口返回的事件消费反馈
            Result<InputPublisher::ConsumerResponse> result =
                    connection->inputPublisher.receiveConsumerResponse();
            if (!result.ok()) {
                status = result.error().code();
                break;
            }

            if (std::holds_alternative<InputPublisher::Finished>(*result)) {
                const InputPublisher::Finished& finish =
                        std::get<InputPublisher::Finished>(*result);
                //2. 处理窗口返回的事件消费反馈
                finishDispatchCycleLocked(currentTime, connection, finish.seq, 
                                            finish.handled,
                                            finish.consumeTime);
            } else if (std::holds_alternative<InputPublisher::Timeline>(*result)) {
               ……
            }
            gotOne = true;
        }
        if (gotOne) {
            //3. 执行command命令
            runCommandsLockedInterruptable();
            if (status == WOULD_BLOCK) {
                return 1;
            }
        }

    } else {
        ……
    }

    removeInputChannelLocked(connection->inputChannel->getConnectionToken(), notify);
    return 0;
}

该方法中的主要流程解释如下:

  1. 首先通过 receiveConsumerResponse 方法来接收窗口返回的事件消费通知
  2. 接着在 finishDispatchCycleLocked 方法中将处理接收的事件的任务封装成 Command 命令
  3. 最后调用 runCommandsLockedInterruptable 方法执行上面封装的处理事件消费 Command 任务

我们接着主要看一下 finishDispatchCycleLocked 中做的事情,该函数代码实现如下:

void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime,
                                                const std::shared_ptr<Connection>& connection,
                                                uint32_t seq, bool handled, nsecs_t consumeTime) {

    if (connection->status == Connection::Status::BROKEN ||
        connection->status == Connection::Status::ZOMBIE) {
        return;
    }

    //将对窗口回调处理任务封装成Command
    auto command = [this, currentTime, connection, seq, handled, consumeTime]() REQUIRES(mLock) {
        doDispatchCycleFinishedCommand(currentTime, connection, seq, handled, consumeTime);
    };
    //将 Command 添加到队列中
    postCommandLocked(std::move(command));
}

//输入事件移除逻辑
void InputDispatcher::doDispatchCycleFinishedCommand(nsecs_t finishTime,
                                                     const std::shared_ptr<Connection>& connection,
                                                     uint32_t seq, bool handled,
                                                     nsecs_t consumeTime) {
    ……

    //循环遍历waitQueue队列
    dispatchEntryIt = connection->findWaitQueueEntry(seq);
    if (dispatchEntryIt != connection->waitQueue.end()) {
        dispatchEntry = *dispatchEntryIt;
        //移除输入事件
        connection->waitQueue.erase(dispatchEntryIt);
        const sp<IBinder>& connectionToken = connection->inputChannel->getConnectionToken();
        mAnrTracker.erase(dispatchEntry->timeoutTime, connectionToken);
        ……
    }
    startDispatchCycleLocked(now(), connection);
}

可以看到,真正的事件处理函数是 doDispatchCycleFinishedCommand 方法,该方法中会遍历 waitQueue 寻找对应的事件,找到后会将该事件从 waitQueue 中移除,该处理函数被添加到 Command 队列中,直到 runCommandsLockedInterruptable 函数调用时才会从 Command 队列取出该命令并执行。

3)InputDispatcher 对 ANR 的判定

我们接着看 dispatchOnce 这个循环执行的方法中的最后一个步骤,调用 processAnrsLocked 函数来判断 ANR,该函数的代码实现如下。

void InputDispatcher::processAnrsLocked(nsecs_t currentTime) {
    // 遍历所有的 Connection 对象
    for (size_t i = 0; i < mConnectionsByFd.size(); i++) {
        const sp<Connection>& connection = mConnectionsByFd.valueAt(i);
        // 获取 waitQueue 队列
        Queue<DispatchEntry>* waitQueue = &connection->waitQueue;
        if (waitQueue->isEmpty()) {
            continue;
        }
        // 获取目标应用和窗口对象
        sp<InputApplicationHandle> applicationHandle = connection->inputApplicationHandle;
        sp<InputWindowHandle> windowHandle = connection->inputWindowHandle;
        // 遍历 waitQueue 队列中的 DispatchEntry 对象
        for (DispatchEntry* dispatchEntry = waitQueue->head; dispatchEntry; dispatchEntry = dispatchEntry->next) {
            // 获取 EventEntry 对象
            EventEntry* eventEntry = dispatchEntry->eventEntry;
            // 获取超时时间
            nsecs_t timeout = getDispatchingTimeoutLocked(applicationHandle, windowHandle);
            nsecs_t startTime = dispatchEntry->deliveryTime;
            nsecs_t waitTime = currentTime - startTime;
            // 判断是否超时
            if (waitTime >= timeout) {
                // 调用 onANRLocked 函数,触发 ANR
                onANRLocked(currentTime, applicationHandle, windowHandle, eventEntry->eventTime, startTime, "input dispatching timed out");
                // 跳出循环,继续下一个 Connection 对象
                break;
            }
        }
    }
}

从源码实现可以看到,processAnrsLocked 函数会遍历所有的窗口 Connection 对象的 waitQueue 队列,比较输入事件的超时时间和当前时间,如果超过了超时时间,就调用 onANRLocked 函数来触发 ANR。onAnrLocked 函数的代码如下。

void InputDispatcher::onAnrLocked(const std::shared_ptr<Connection>& connection) {
    if (connection == nullptr) {
        LOG_ALWAYS_FATAL("Caller must check for nullness");
    }
    if (connection->waitQueue.empty()) {
        ALOGI("Not raising ANR because the connection %s has recovered",
              connection->inputChannel->getName().c_str());
        return;
    }

    DispatchEntry* oldestEntry = *connection->waitQueue.begin();
    const nsecs_t currentWait = now() - oldestEntry->deliveryTime;
    //打印 ANR 信息
    std::string reason =
            android::base::StringPrintf("%s is not responding. Waited %" PRId64 "ms for %s",
                                        connection->inputChannel->getName().c_str(),
                                        ns2ms(currentWait),
                                        oldestEntry->eventEntry->getDescription().c_str());
    sp<IBinder> connectionToken = connection->inputChannel->getConnectionToken();
    //将 ANR 信息发送给窗口
    updateLastAnrStateLocked(getWindowHandleLocked(connectionToken), reason);
    //发送给 WindowManagerService 处理
    processConnectionUnresponsiveLocked(*connection, std::move(reason));

    cancelEventsForAnrLocked(connection);
}

onAnrLocked 函数中会将 ANR 的超时信息打印出来,并通过 Binder 发送 ANR 信号给目标窗口,并通过 processConnectionUnresponsiveLocked 方法将 ANR 发送给 WindowManagerService 做进一步处理 。

2. 广播接收超时

广播作为 Android 的四大组件之一,在实际的场景中使用是比较频繁的,但是这里不对广播组件做过多的介绍,主要还是聚焦在广播产生 ANR 的相关流程上。我们首先需要先了解一下广播的类型,主要有三类广播,分别如下:

  • 普通广播(Normal Broadcast):普通广播是异步的,发送者不关心接收者的处理结果,也不会等待接收者的响应。通过 sendBroadcast 方法发送的广播即为普通广播。
  • 有序广播(Ordered Broadcast):有序广播是同步的,发送者需要等待接收者的处理结果,通过 sendOrderedBroadcast 方法即可发送有序广播。
  • 粘性广播(Sticky Broadcast):这种广播是一种特殊的普通广播,它可以在发送后一直保留在系统中,这样后来注册的接收者也可以收到之前发送的广播,通过 sendStickyBroadcast 方法可发送粘性广播。

上面的三种类型的广播中,只有有序广播才会导致 ANR 的,因为该广播是同步的,如果接收者在广播接收函数 onReceive 函数中执行耗时超过 10 秒,系统便会触发 "BroadcastReceiver Timeout" 类型的 ANR。该 ANR 触发流程主要有这几步:

1)ActivityManagerService 通过 processNextBroadcast 方法启动广播

2)启动流程中,如果是有序广播,则会启动触发 “BroadcastReceiver Timeout” 这一 ANR 的延时任务,

3)阻塞执行 performReceiverLocked 方法,该方法会触发应用中的 Receiver 回调 onReceive 函数,当 onReciver 函数执行完成后,ActivityManagerService 才会继续执行后续流程,移除前面启动的 ANR 触发的延时任务。

通过流程可以知道,如果在规定的时间内,ANR 触发的延时任务没被移除,便会触发 ANR,上述流程如图所示,我们接着通过代码实现来更深入的了解该 ANR 的触发原理。

whiteboard_exported_image-16.png

  1. 广播启动入口

ActivityManagerService(AMS) 中的 BroadcastQueueImpl 成员对象是专门专门用来处理和广播相关的对象,当 AMS 需要启动一个广播时,会通知 BroadcastQueueImpl 中的 Handler 触发 processNextBroadcast 方法来启动广播,代码如下:

// BroadcastQueueImpl中的 Handler
private final class BroadcastHandler extends Handler {
    public BroadcastHandler(Looper looper) {
        super(looper, null);
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case BROADCAST_INTENT_MSG: {
                //1. 广播启动入口
                processNextBroadcast(true);
            } break;
            case BROADCAST_TIMEOUT_MSG: {
                //2. 广播超时,会触发广播类型的ANR
                synchronized (mService) {
                    broadcastTimeoutLocked(true);
                }
            } break;
        }
    }
}

该 Handler 中只处理两个任务,一个是 BROADCAST_INTENT_MSG 消息用于广播启动,一个是 BROADCAST_TIMEOUT_MSG 消息用于触发广播超时的 ANR 。我们先看看 processNextBroadcast 这个广播启动入口函数,该函数代码如下:

public void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
    // 1. 处理并行广播
    while (mParallelBroadcasts.size() > 0) {
        ……
    }
    ……    
    boolean looped = false;
    //2. 处理有序广播
    do {
        final long now = SystemClock.uptimeMillis();
        //3. 获取mOrderedBroadcasts中的第一个广播
        r = mDispatcher.getNextBroadcastLocked(now);
        ……
        int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
        if (mService.mProcessesReady && !r.timeoutExempt && r.dispatchTime > 0) {
            if ((numReceivers > 0) &&
                    (now > r.dispatchTime + (2 * mConstants.TIMEOUT * numReceivers))) {
                //4. 启动广播超时延时任务
                broadcastTimeoutLocked(false); 
            }
        }
        ……
        if (r.receivers == null || r.nextReceiver >= numReceivers
                || r.resultAbort || forceReceive) {
            if (r.resultTo != null) {
                ……
                try {
                    //5. 同步调用接收者的 onReceive 方法
                    performReceiveLocked(r, r.resultToApp, r.resultTo,
                            new Intent(r.intent), r.resultCode,
                            r.resultData, r.resultExtras, false, false, r.shareIdentity,
                            r.userId, r.callingUid, r.callingUid, r.callerPackage,
                            r.dispatchTime - r.enqueueTime,
                            now - r.dispatchTime, 0,
                            r.resultToApp != null
                                    ? r.resultToApp.mState.getCurProcState()
                                    : ActivityManager.PROCESS_STATE_UNKNOWN);
                } catch (RemoteException e) {
                    r.resultTo = null;
                }
                ……
            }
            //6. 取消 ANR 判断任务
            cancelBroadcastTimeoutLocked();
            ……
            continue;
        }
        ……
    } while (r == null);

    // 预处理下一个广播对应的receiver
    ……
}

该方法中主要流程说明如下:

1)首先循环处理并行广播,即普通广播,普通广播在启动时,都是先放入到 mParallelBroadcasts 队列中。

2)接着开始循环处理有序广播

3)处理有序广播时,首先会通过在 mOrderdBroadcasts 队列中获取位于队头的有序广播

4)接着调用 broadcastTimeoutLoced 来启动广播启动超时的延时任务

5)然后调用 performReceiverLocked 方法,而该函数会同步调用接受者的 onReceive 方法

6)最后调用 cancelBroadcastTimeoutLoced 方法来取消 ANR 判定的延时任务

通过这段代码,我们也就能清晰的明白广播启动超时这一 ANR 的触发机制,如果广播接收者在 onReceive 方法中耗时太久,那么就来不及调用 cancelBroadcastTimeoutLoced 方法来移除延时的 ANR 任务,所以该 ANR 任务就会被触发。

  1. ANR 触发任务

我们接着看 broadcastTimeoutLocked 这个会触发 ANR 的延时任务,该函数的简化代码实现如下:

final void broadcastTimeoutLocked(boolean fromMsg) {
    ……
    try {
        long now = SystemClock.uptimeMillis();
        BroadcastRecord r = mDispatcher.getActiveBroadcastLocked();
        if (fromMsg) {
            ……
            long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
            if (timeoutTime > now) {
                //启动触发 ANR 的延时任务
                setBroadcastTimeoutLocked(timeoutTime);
                return;
            }
        }

        ……

    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    }

}

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

通过代码可以看到,setBroadcastTimeoutLocked 方法会往 mHander 发送延时为 timeoutTime ,消息类型为 BROADCAST_TIMEOUT_MSG 的 ANR 触发任务,如果在 timeoutTime 的时间内,这个任务没移除,便会执行 BROADCAST_TIMEOUT_MSG 消息对应的 broadcastTimeoutLocked 函数来触发 ANR,该方法的精简代码实现如下:

final void broadcastTimeoutLocked(boolean fromMsg) {
    ……
    try {
        ……

        //  通过AMS触发ANR
        if (!debugging && app != null) {
            mService.appNotResponding(app, timeoutRecord);
        }

    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    }

}

在上面的代码中 mService.appNotResponding 这一方法便会调用 AMS 的 appNotResponding 方法来触发目标进程的 ANR。

3. 服务启动超时

前面我们已经通过代码深入的了解了广播接收超时的原理,而服务启动超时和内容提供者发布超时这两类 ANR 在原理上是大同小异的,所以笔者接下来仅做些简单的介绍。服务启动超时这一 ANR 触发流程如图所示。

whiteboard_exported_image-17.png

Service 的相关逻辑都封装在 AMS 的成员对象 ActiveServices 里,我们只需要了解 Service 启动的入口函数 realStartServiceLocked 方法,便能了解 “Service Timeout ” 触发的流程,该方法的简化代码如下:

private void realStartServiceLocked(ServiceRecord r, ProcessRecord app,
        IApplicationThread thread, int pid, UidRecord uidRecord, boolean execInFg,
        boolean enqueueOomAdj) throws RemoteException {
    ……
    //1. 往handler发送SERVICE_TIMEOUT_MSG延时任务,即ANR触发任务
    bumpServiceExecutingLocked(r, execInFg, "create",
            OOM_ADJ_REASON_NONE);

    boolean created = false;
    try {
        ……
        //2. 同步执行目标service的onCreate方法
        thread.scheduleCreateService(r, r.serviceInfo,
                null
                app.mState.getReportedProcState());
        r.postNotification(false);
        created = true;
    } catch (DeadObjectException e) {
        ……
        throw e;
    } finally {
        if (!created) {
            ……
            //3. 移除SERVICE_TIMEOUT_MSG延时任务
            serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
                    OOM_ADJ_REASON_STOP_SERVICE);
            ……
        }
    }

    ……
}

该方法中主要流程的解释如下:

1)首先调用 bumpServiceExecutinLoced 方法,该方法会通过 Handler 发送一个 SERVICE_TIMEOUT_MSG 消息,也就是 ANR 触发的消息,该消息的延时时间取决于服务是前台还是后台,前台服务的超时时间是 20 秒,后台服务的超时时间是 200 秒。

2)接着通过 Binder 机制通知目标服务所在的进程,让其执行服务的创建或绑定操作,目标服务所在的进程会通过 ActivityThread 类的 scheduleCreateService 来调度服务的 onCreate 生命周期

3)最后,当服务完成创建或绑定操作后,执行 serviceDoneExecutingLocked 方法,该方法会移除之前发送的 SERVICE_TIMEOUT_MSG 消息,表示服务已经正常启动或绑定,不会触发 ANR 。

如果 SERVICE_TIMEOUT_MSG 这个消息没有在规定的时间内被移除则会执行 serviceTimeout 方法来触发 ANR,ANR 的触发也是调用 AMS 的 appNotResponding 方法来进行的。

4. 内容提供者发布超时

ContentProvider 即内容提供者,是随着程序首次启动而一起被创建的。程序首次启动过程中,AMS 会执行 attachApplicationLocked 方法,该方法会通过 binder 调用程序的 bindApplication 方法,该方法会触发程序启动的一系列流程,比如将 mainfest 中配置的 ContentProvider 进行发布,触发 Application 的 onAttach 等生命周期。该方法简化的代码如下:

private void attachApplicationLocked(@NonNull IApplicationThread thread,
        int pid, int callingUid, long startSeq) {

    ……
    //获取程序中配置的provider列表
    List<ProviderInfo> providers = normalMode
                                        ? mCpHelper.generateApplicationProvidersLocked(app)
                                        : null;

    //1. 发送CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG任务,该任务触发ContentProvider Timeout
    if (providers != null && mCpHelper.checkAppInLaunchingProvidersLocked(app)) {
        Message msg = mHandler.obtainMessage(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG);
        msg.obj = app;
        mHandler.sendMessageDelayed(msg,
                ContentResolver.CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS);

    ……

    //2. 通过binder调用目标程序的bindApplication方法
    thread.bindApplication(processName, appInfo,
            app.sdkSandboxClientAppVolumeUuid, app.sdkSandboxClientAppPackage,
            providerList,
            instr2.mClass,
            profilerInfo, instr2.mArguments,
            instr2.mWatcher,
            instr2.mUiAutomationConnection, testMode,
            mBinderTransactionTrackingEnabled, enableTrackAllocation,
            isRestrictedBackupMode || !normalMode, app.isPersistent(),
            new Configuration(app.getWindowProcessController().getConfiguration()),
            app.getCompat(), getCommonServicesLocked(app.isolated),
            mCoreSettingsObserver.getCoreSettingsLocked(),
            buildSerial, autofillOptions, contentCaptureOptions,
            app.getDisabledCompatChanges(), serializedSystemFontMap,
            app.getStartElapsedTime(), app.getStartUptime());
    ……
}

通过上面代码可以知道, AMS 在调用目标程序的 bindApplication 方法之前,会先向 Handler 发送 CONTENT_PROVIDER_PUBLISH_TIMEOUT 的延时任务。该任务便会触发内容提供者发布超时这一 ANR。当目标程序在 bindApplication 方法中发布完 ContentProvider 后,便会通知 AMS 移除该任务消息。从这里我们也能发现,内容提供者的发布是在程序启动的过程中进行的,所以当内容提供者发布耗时太久,触发了 ANR,会直接影响到程序的正常启动。

二 常见的 ANR 归因

从这四类 ANR 的产生机制来看,本质原因都是因为主线程没法在规定时间内完成任务的执行。会导致主线程无法按时完成任务的因素却非常多,总结起来都可以归于这三类:

  1. 主线程方法耗时高

主线程中某个方法执行耗时特别久并超过了 ANR 触发的阈值,比如某个主线程的 IO 任务,等待某个锁等。对于这类问题只需要定位出明确的异常方法逻辑后,采用异步处理,方法粒度细化等方式,都会比较容易修复。

  1. 主线程消息堆积

这一类问题是比较难定位问题的,因为我们会发现主线程中的任务执行耗时都很短,但是依然发生了 ANR,原因是因为主线的消息堆积过多了,比如每个任务执行耗时为 100 ms,但如果主线程消息队列中有 50 个这样的任务,那么要将所有这些消息处理完毕就要耗时 5 秒,这样就会到输入事件没法在规定时间内被响应而触发,改流程如图 6-6 所示。对于这类问题,我们往往要找到往主线程频繁发送任务的业务和代码逻辑,然后来针对性的优化,在一些更复杂的场景中,有时候并不是某一个业务在大量往主线程发送任务,而是大部分业务都在这样做,这个时候就需更好的架构设计来避免主线程的拥塞了。

whiteboard_exported_image-18.png

  1. 性能问题

当程序出现 CPU 占用率高、内存占用高、GC 频繁等性能异常的情况时,主线程可能会因为无法获得 CPU 时间片而导致任务的执行速度发生劣化,这个时候也很容易产生 ANR,所以当性能问题导致 ANR 产生时,我们需要通过对 CPU,内存等方向的性能优化来进行 ANR 的治理。