最近在做ANR优化,发现线上非常多的ANR(一半以上)原因都是 Input dispatching timed out。对于ActivityService生命周期的ANR产生原理,我想咱们应该都比较了解了,便是在AMS里埋炸弹、拆炸弹那一套机制,那Input Dispatching time out ANR是怎么产生的呢?这篇文章带咱们一同学习一下。

Android输入体系

Input Dispatching time out ANR是有Android点击事情超时所产生的,所以要了解它产生的原理,就要从Android的输入体系开端讲起。

Android输入体系,首要包括以下几个模块:

发送端:运行在system_server进程,首要运行在InputReaderThreadInputDispatcherThread

  • InputReader:这个模块首要负责从硬件获取输入,转换成事情Event,传给InputDispatcher
  • InputDispatcher:将InputReader传递过来的事情分发给相应的窗口,而且监控ANR。

接纳端:运行在使用程序进程,运行在UI线程

  • InputEventReceiver:在App端接纳按键,并进行分发。
  • ViewActivity:接纳按键并进行处理。

基础服务:

  • InputManagerService:负责InputReaderInputDispatcher的创立。
  • WindowManagerService:管理InputManagerWindowAMS之间的通讯。

通讯机制:

  • socket:发送端和接纳端跨进程,选用的是socket的通讯机制。

Android输入体系的原理比较复杂,这篇文章,咱们着重剖析ANR产生的原理,所以咱们只看InputDispatcher即可,由于关于ANR的判定是在这儿产生的。

后续学姐会再出专题,详细剖析整个Android输入体系的原理,感兴趣的能够点个重视❤️。

ANR原理剖析

咱们先来考虑一个问题,假如我在ActivitydispatchTouchEvent中,手动让线程sleep一段时刻。这种状况必定会报ANR么?

    var count = 0;
    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
       // 让第0个event,卡住9s
        if(count == 0){
            try {
                Thread.sleep(9000)
            } catch (e: Throwable) {
                e.printStackTrace()
            }
        }
        count++
        return super.dispatchTouchEvent(ev)
    }

我信任很多同学会答复必定,由于主线程 sleep 的时刻远远超过了 Input 事情报ANR的超时时刻,所以会报ANR。

但实在的状况是,在主线程sleep 大于 5s 不必定会报ANR。下面咱们就从InputDispatcher源码的视点来看看,Input ANR到底是怎么产生的吧。

InputDispatcher启动

InputDispatcher运行在InputDispatcherThread线程中,这个线程和使用UI线程相同,也是靠Looper机制运行起来的。

首要看下InputDispatcher方针的初始化进程:

InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy) :
    //创立Looper方针
    mLooper = new Looper(false);
    //获取分发超时参数
    policy->getDispatcherConfiguration(&mConfig);
}

首要便是创立了一个归于自己线程的Looper方针。当这个线程的Looper被启动之后,会不断重复调threadLoop办法,直到该办法回来false,退出循环,从而结束线程。

bool InputDispatcherThread::threadLoop() {
    mDispatcher->dispatchOnce(); 
    return true;
}

threadLoop里面,只做了一件事情,便是调用InputDispatcher的dispatchOnce办法:

void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;
    // 假如有commands需求处理,优先处理commands
    if (!haveCommandsLocked()) {
        //当commands处理完后,处理events
        dispatchOnceInnerLocked(&nextWakeupTime);
    }
    // 新增:假如还有event在处理中,需求提前唤醒查看是否产生anr
    const nsecs_t nextAnrCheck = processAnrsLocked(); 
    nextWakeupTime = std::min(nextWakeupTime, nextAnrCheck);
    nsecs_t currentTime = now();
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    mLooper->pollOnce(timeoutMillis); //进入epoll_wait
}

InputDispatcher处理的事情首要分为两种:一种是command,一种是input eventcommand首要是mPolicy处理的业务,和咱们点击事情的ANR没有关系,所以这儿不详细剖析。input event的处理逻辑首要是,找到对应的window方针,经过socketevent发送给使用进程。

当处理完一切commandinput event之后,会调用LooperpollOnce办法。从之前的epoll机制剖析文章中,咱们知道,在这个办法里,线程会进入epoll_wait等候。

唤醒epoll_wait的办法有:

  • 监听的fd有相关数据改动
  • timeout:抵达timeoutMillis的时刻
  • wake:主动调Looperwake()办法。

这儿会监听和使用程序通讯的socket fd,接纳使用程序处理完事情的音讯。

ps:关于epoll机制的原理,可参阅:从epoll机制看MessageQueue

pps:新版别添加逻辑:假如还有event在处理中,需求提前唤醒查看是否产生anr.

  • 经过AnrTracker记载一切待处理的events的超时时刻
  • 获取最小的时刻,作为下次唤醒的timeout时刻

分发事情

InputDispatcher线程中,首要包括三个事情行列:

  • mInBoundQueue:InputReader线程负责经过EventHub读取输入事情,一旦监听到输入事情就放入这个行列。
  • outBoundQueue:记载行将分发给方针使用窗口的输入事情。
  • waitQueue:记载已分发给方针使用,且使用尚未处理完成的输入事情。

还有一个单独的变量

  • mPendingEvent:记载当时正在发送中的event,发送成功后会清空
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    // 假如没有正在发送中的event,才去取新的event
    if (!mPendingEvent) {
        // mInboundQueue为待处理的事情行列
        if (mInboundQueue.isEmpty()) {
            if (!mPendingEvent) {
                return; //没有事情需求处理,则直接回来
            }
        } else {
            //从mInboundQueue取出头部的事情
            mPendingEvent = mInboundQueue.dequeueAtHead();
        }
        // 新的分发开端了,重置ANR超时时刻
        resetANRTimeoutsLocked(); 
    }
    switch (mPendingEvent->type) {
          // 尝试分发按键事情
          done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
          break;
      }
    }
    //分发操作完成,则进入该分支
    if (done) {
        // 开释pendingEvent事情,将这个标志位设置为空
        releasePendingEventLocked();
        *nextWakeupTime = LONG_LONG_MIN; //强制马上履行轮询
    }
}

这个办法首要的逻辑是:

  • 假如有正在发送的event(pendingEvent),则什么都不做,假如没有,则取mInboundQueue头部的事情,用于发送。
  • 调用dispatchKeyLocked办法发送事情。
  • 当发送成功后,开释pendingEvent标志位。
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
        DropReason* dropReason, nsecs_t* nextWakeupTime) {
    Vector<InputTarget> inputTargets;
    // 寻觅焦点 
    int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,
            entry, inputTargets, nextWakeupTime);
    if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
        return false; //直接回来
    }
    //只要injectionResult是成功,才有时机履行分发事情
    dispatchEventLocked(currentTime, entry, inputTargets);
    return true;
}

履行到这一步的事情,不必定能够走到发送的逻辑。由于还需求寻觅可履行的焦点,只要当找到了可履行焦点后,事情才会被真实分发。

int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,
        const EventEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime) {
    //检测窗口是否为更多的输入操作而准备安排妥当
    reason = checkWindowReadyForMoreInputLocked(currentTime,
            mFocusedWindowHandle, entry, "focused");
    if (!reason.isEmpty()) {
        // 假如窗口没有安排妥当,判别是否产生了ANR
        injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
                mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime, reason.string());
    }
}

这个办法会先检测窗口是否安排妥当,假如未安排妥当,会判别是否超过5s,即判别是否产生ANR。

String8 InputDispatcher::checkWindowReadyForMoreInputLocked(nsecs_t currentTime,
        const sp<InputWindowHandle>& windowHandle, const EventEntry* eventEntry,
        const char* targetType) {
    // 处理一些窗口暂停、窗口衔接已逝世、窗口衔接已满的问题
    if (eventEntry->type == EventEntry::TYPE_KEY) {
        // 按键事情,输出行列或事情等候行列不为空
        if (!connection->outboundQueue.isEmpty() || !connection->waitQueue.isEmpty()) {
            return String8::format();
        }
    } else {
        // 非按键事情,事情等候行列不为空且头事情分发超时500ms
        if (!connection->waitQueue.isEmpty()
                && currentTime >= connection->waitQueue.head->deliveryTime
                        + STREAM_AHEAD_EVENT_TIMEOUT) {
            return String8::format();
        }
    }
    return String8::empty();
}

到这儿就很清楚了,假如outboundQueue不为空,或waitQueue不为空,此刻表示WindowReady。则新的事情无法走到正常分发的逻辑。

  1. Window不ready的逻辑
int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime){
     // 假如失利的原因是由于上一个使命未处理完,则不需求给超时时刻从头赋值
     if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
            // 设置InputTargetWaitCause
            mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
            //这儿的currentTime是指履行dispatchOnceInnerLocked办法体的起点
            mInputTargetWaitStartTime = currentTime; 
            // timeout为5s
            mInputTargetWaitTimeoutTime = currentTime + timeout;
            mInputTargetWaitTimeoutExpired = false;
            mInputTargetWaitApplicationHandle.clear();
      }
    //当超时5s,则进入ANR流程
    if (currentTime >= mInputTargetWaitTimeoutTime) {
        onANRLocked(currentTime, applicationHandle, windowHandle,
                entry->eventTime, mInputTargetWaitStartTime, reason);
        *nextWakeupTime = LONG_LONG_MIN; //强制马上履行轮询来履行ANR策略
        return INPUT_EVENT_INJECTION_PENDING;
    }
}

这段代码,判别ANR的逻辑如下:

  • 在首次进入handleTargetsNotReadyLocked()办法的时候,mInputTargetWaitCause的值不为INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY,因此会去获取一个超时时刻,并记载等候的开端的时刻、等候超时时刻,等候的原由于INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY
  • 当下一个输入事情调用handleTargetsNotReadyLocked()办法时,假如mInputTargetWaitCause的值还没有被改动,仍然为INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY,则直接进入(currentTime >= mInputTargetWaitTimeoutTime)的判别。假如超时等候时刻大于5s,则满意该条件,进入onANRLocked()办法,发送ANR通知。
  1. 正常分发逻辑

假如当时没有正在分发的Event,会走到真实的分发逻辑:

void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,
        const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) {
    // 将事情加入到 outboundQueue 队尾
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
            InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT);
    if (wasEmpty && !connection->outboundQueue.isEmpty()) {
        // 开端dispatch事情
        startDispatchCycleLocked(currentTime, connection);
    }
}

首要将事情加到outboundQueue的队尾,然后开端分发事情。

void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
        const sp<Connection>& connection) {
    //当Connection状况正常,且outboundQueue不为空
    while (connection->status == Connection::STATUS_NORMAL
            && !connection->outboundQueue.isEmpty()) {
        EventEntry* eventEntry = dispatchEntry->eventEntry;
        switch (eventEntry->type) {
          case EventEntry::TYPE_KEY: {
              KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
              //发布Key事情
              status = connection->inputPublisher.publishKeyEvent(dispatchEntry->seq,
                      keyEntry->deviceId, keyEntry->source,
                      dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags,
                      keyEntry->keyCode, keyEntry->scanCode,
                      keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime,
                      keyEntry->eventTime);
              break;
          }
        }
        // 发布事情成功后,从outboundQueue中取出事情,从头放入waitQueue行列
        connection->outboundQueue.dequeue(dispatchEntry);
        connection->waitQueue.enqueueAtTail(dispatchEntry);
    }
}

调用inputPublisher.publishKeyEvent将事情真实发送了出去,然后将事情从outboundQueue中取出,加入到waitQueue中。到这儿,事情真实发送了出去了。

假如使用端及时处理完事情回来,会将事情从waitQueue中删除。

总结

答复文章开端时的问题,为什么在主线程中sleep 9s不必定会形成ANR呢?

由于在老版别的Android体系上,ANR的查看逻辑,是在下个事情的分发流程中进行的。假如在这个9s中,没有后续事情,或者后续事情的等候时刻不超过5s,则不会触发ANR。

ANR如何产生之InputDispatching Timeout篇

全体流程如上图所示。

  • InputDispatcher在发送事情之前,会查看Window是否Ready,这个判别条件便是waitQueue是否为空。
  • 假定第一个音讯被卡住了,则waitQueue不为空。
  • 第二个音讯来的时候,Window不Ready,会进入handleTargetsNotReadyLocked办法,将mInputTargetWaitCause设置为INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY,并设置mInputTargetWaitTimeoutTime为当时时刻+5s。
  • 之后再循环进来,假如还是卡顿状况,持续走进handleTargetsNotReadyLocked,当时时刻假如大于mInputTargetWaitTimeoutTime,则会触发ANR。

不过,上面的流程和源码是用的比较老的Android版别,新版别能够一起发多个事情,而且修正且严厉了ANR的校验逻辑。

新版别的逻辑有以下几点不同:

  • 添加AnrTracker记载事情的超时时刻。
  • 每次回调dispatchOnce都会查看Tracker中记载的事情是否有超时,假如超时则触发ANR。
  • 能够一起发多个Event,但只要有一个超时,则判定为ANR。
  • 当时Event也会触发ANR,由于也会走到查看Tracker中是否有超时Event的逻辑。

下篇文章,我再来详细共享新版别的不同之处。

拓宽阅览

深入理解Android ANR触发原理以及信息采集进程