高级原理解析binder面试

[ANR] Input ANR是怎么产生的

2023-02-18  本文已影响0人  尹学姐

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

Android输入系统

Input Dispatching time outANR是有Android点击事件超时所产生的,所以要了解它产生的原理,就要从Android的输入系统开始讲起。

Android输入系统,主要包含以下几个模块:

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

接收端:运行在应用程序进程,运行在UI线程。

基础服务:

通信机制:

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);
    }
    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的方法有:

这里会监听和应用程序通信的socket fd,接收应用程序处理完事件的消息。

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

分发事件

InputDispatcher线程中,主要包含三个事件队列:

还有一个单独的变量:

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; //强制立刻执行轮询
    }
}

这个方法主要的逻辑是:

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的逻辑如下:

  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呢?

因为ANR的检查逻辑,是在下个事件的分发流程中进行的。如果在这个9s中,没有后续事件,或者后续事件的等待时间不超过5s,则不会触发ANR。

image.png

整体流程如上图所示。

系统这样做的好处是,如果这个事件后续没有事件要处理,那其实不需要报ANR。只有当后续真的有事件需要处理,且事件被卡住的时候,才会触发ANR。

拓展阅读

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

上一篇下一篇

猜你喜欢

热点阅读