Input类型的ANR相对于Service
、Broadcast
、Provider
实现的方式不一样,Input的ANR发生原因是输入事件分发超时,包括按键和屏幕的触摸事件。对于输入系统而言
Input的事件是需要从屏幕(输入设备)点击开始,从硬件设备驱动到应用层,涉及到的底层的交互,其中重要的角色有
InputReader
:负责从输入设备获取事件,并将该事件通知给队列,并最终通知到InputDispatcher
InputDispatcher
:负责将输入事件分发到正确的窗口上,并会处理ANR问题InputManager
:是InputReader
和InputDispatcher
的枢纽,类似MVC中到Controller作用InputDispatcher
的实现主要涉及3个Queue
InboundQueue
: 这个队列里面存储的是从InputReader送来到输入事件OutboundQueue
:这个队列里面存储的是即将要发送给应用的输入事件WaitQueue
:这个队列里面存储的是已经发给应用的事件,但应用还未处理完成它们之间的关系如下
InputDispatcher
内部维护了一个线程,负责不断从InboundQueue
读取事件
OutboundQueue
,并分发给应用后放入WaitQueue
。WaitQueue
将会等待应用处理完成后,将事件从WaitQueue
中移除,再循环处理下一个事件WaitQueue
是否存在未处理的事件,如果存在,则判断上一次的事件的记录时间是否大于超时时间(5s),如果是则会响应ANRInput的ANR发生在事件的分发流程上,其逻辑都在InputDispatcher
中,事件分发最终执行到dispatchOnceInnerLocked()
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
//1、ANR计算的起点时间
nsecs_t currentTime = now();
......
if (!mPendingEvent) {
if (mInboundQueue.isEmpty()) {
if (!mPendingEvent) {
return;
}
} else {
//2、从mInboundQueue取出头部的事件
mPendingEvent = mInboundQueue.dequeueAtHead();
}
......
resetANRTimeoutsLocked();
}
switch (mPendingEvent->type) {
case EventEntry::TYPE_KEY: {
KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
if (isAppSwitchDue) {
if (isAppSwitchKeyEventLocked(typedEntry)) {
resetPendingAppSwitchLocked(true);
isAppSwitchDue = false;
} else if (dropReason == DROP_REASON_NOT_DROPPED) {
dropReason = DROP_REASON_APP_SWITCH;
}
}
if (dropReason == DROP_REASON_NOT_DROPPED
&& isStaleEventLocked(currentTime, typedEntry)) {
dropReason = DROP_REASON_STALE;
}
if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
dropReason = DROP_REASON_BLOCKED;
}
//3、针对不同的事件进行分发
done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
break;
}
......
}
......
}
针对不同的事件进行分发,分发的逻辑在dispatchKeyLocked()
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) {
......
//1、通过上图可以知道,事件先要寻找焦点窗口
nt32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,
entry, inputTargets, nextWakeupTime);
......
//2、当窗口就绪后,才会真正去分发事件
dispatchEventLocked(currentTime, entry, inputTargets);
}
在分发事件之前要确保可以焦点窗口就绪,具体在findFocusedWindowTargetsLocked()
int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,
const EventEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime) {
......
//1、检测窗口是否为更多的输入操作而准备就绪
reason = checkWindowReadyForMoreInputLocked(currentTime,
mFocusedWindowHandle, entry, "focused");
if (!reason.isEmpty()) {
//2、如果未准备就绪则触发handleTargetsNotReadyLocked
injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime, reason.string());
goto Unresponsive;
}
......
}
窗口是否检查就绪的条件如下
String8 InputDispatcher::checkWindowReadyForMoreInputLocked(nsecs_t currentTime,
const sp<InputWindowHandle>& windowHandle, const EventEntry* eventEntry,
const char* targetType) {
//当窗口暂停的情况,则保持等待
if (windowHandle->getInfo()->paused) {
return String8::format("Waiting because the %s window is paused.", targetType);
}
//当窗口连接未注册,则保持等待
ssize_t connectionIndex = getConnectionIndexLocked(windowHandle->getInputChannel());
if (connectionIndex < 0) {
return String8::format("Waiting because the %s window's input channel is not "
"registered with the input dispatcher. The window may be in the process "
"of being removed.", targetType);
}
//当窗口连接已死亡,则保持等待
sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
if (connection->status != Connection::STATUS_NORMAL) {
return String8::format("Waiting because the %s window's input connection is %s."
"The window may be in the process of being removed.", targetType,
connection->getStatusLabel());
}
// 当窗口连接已满,则保持等待
if (connection->inputPublisherBlocked) {
return String8::format("Waiting because the %s window's input channel is full. "
"Outbound queue length: %d. Wait queue length: %d.",
targetType, connection->outboundQueue.count(), connection->waitQueue.count());
}
if (eventEntry->type == EventEntry::TYPE_KEY) {
// 按键事件,输出队列或事件等待队列不为空
if (!connection->outboundQueue.isEmpty() || !connection->waitQueue.isEmpty()) {
return String8::format("Waiting to send key event because the %s window has not "
"finished processing all of the input events that were previously "
"delivered to it. Outbound queue length: %d. Wait queue length: %d.",
targetType, connection->outboundQueue.count(), connection->waitQueue.count());
}
} else {
// 非按键事件,事件等待队列不为空且头事件分发超时500ms
if (!connection->waitQueue.isEmpty()
&& currentTime >= connection->waitQueue.head->deliveryTime
+ STREAM_AHEAD_EVENT_TIMEOUT) {
return String8::format("Waiting to send non-key event because the %s window has not "
"finished processing certain input events that were delivered to it over "
"%0.1fms ago. Wait queue length: %d. Wait queue head age: %0.1fms.",
targetType, STREAM_AHEAD_EVENT_TIMEOUT * 0.000001f,
connection->waitQueue.count(),
(currentTime - connection->waitQueue.head->deliveryTime) * 0.000001f);
}
}
return String8::empty();
}
在检查窗口是否准备就绪时,当输出队列或事件等待队列不为空的时候,就表示有事件在等待,说明窗口未准备就绪,当未就绪时,就要处理handleTargetsNotReadyLocked()
int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime,
const EventEntry* entry,
const sp<InputApplicationHandle>& applicationHandle,
const sp<InputWindowHandle>& windowHandle,
nsecs_t* nextWakeupTime, const char* reason) {
......
//1、当超时5s,则发生ANR
if (currentTime >= mInputTargetWaitTimeoutTime) {
onANRLocked(currentTime, applicationHandle, windowHandle,
entry->eventTime, mInputTargetWaitStartTime, reason);
*nextWakeupTime = LONG_LONG_MIN;
return INPUT_EVENT_INJECTION_PENDING;
} else {
if (mInputTargetWaitTimeoutTime < *nextWakeupTime) {
*nextWakeupTime = mInputTargetWaitTimeoutTime;
}
return INPUT_EVENT_INJECTION_PENDING;
}
}
可以借助adb的命令dumpsys input
来分析一些问题。执行dumpsys input
后,可以看到InboundQueue
,OutboundQueue
,WaitQueue
3个Queue的状态,以及PendingEvent
的值,正常情况下都为
......
Input Dispatcher State:
DispatchEnabled: 1
DispatchFrozen: 0
FocusedApplication: name='AppWindowToken{3f0c92ab token=Token{1ea37cfa ActivityRecord{235b7325 u0 com.android.launcher3/.Launcher t607}}}', dispatchingTimeout=5000.000ms
FocusedWindow: name='Window{37886f95 u0 com.android.launcher3/com.android.launcher3.Launcher}'
TouchStatesByDisplay:
0: down=false, split=false, deviceId=6, source=0x00002002
Windows: <none>
Windows:
......
PendingEvent: <none>
InboundQueue: <empty>
ReplacedKeys: <empty>
Connections:
......
8: channelName='37886f95 com.android.launcher3/com.android.launcher3.Launcher (server)', windowName='Window{37886f95 u0 com.android.launcher3/com.android.launcher3.Launcher}', status=NORMAL, monitor=false, inputPublisherBlocked=false
OutboundQueue: <empty>
WaitQueue: <empty>
......
通过重载dispatchTouchEvent()
函数,阻塞事件的处理,来模拟ANR
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
Log.i(TAG, "handle event: "+ev)
Thread.sleep(100000)
return super.dispatchTouchEvent(ev)
}
程序运行后触屏点击一次Activity,执行adb shell dumpsys input
,输出如下
PendingEvent: <none>
InboundQueue: <empty>
Connections:
......
11: channelName='14a0d447 com.cy.anr/com.cy.anr.MainActivity (server)', windowName='Window{14a0d447 u0 com.cy.anr/com.cy.anr.MainActivity}', status=NORMAL, monitor=false, inputPublisherBlocked=false
OutboundQueue: <empty>
WaitQueue: length=3
MotionEvent(deviceId=5, source=0x00001002, action=0, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (740.0, 75.0)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=0, age=1411.8ms, wait=1411.3ms
MotionEvent(deviceId=5, source=0x00001002, action=2, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (741.0, 80.0)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=2, age=1363.3ms, wait=1363.2ms
MotionEvent(deviceId=5, source=0x00001002, action=1, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (741.0, 80.0)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=1, age=1347.2ms, wait=1347.0ms
PendingEvent
, InboundQueue
和OutboundQueue
都是空,而WaitQueue
中有3个MotionEvent
,分别对应action0,2,1
,也就是Down,Move,Up
。产生这样输出的原理是,在点击前,窗口没有在处理任何输入,因此对于down可以添加到WaitQueue
。而对于Move和Up,由于是几乎同时产生和处理的,因此在判断窗口是否就绪时(当前时间是否大于WaitQueue队头500ms),发现窗口就绪,因此一并加入到了WaitQueue
WaitQueue
有元素,那么(500ms后)再次点击,往InqueueBound
加入事件后,由于WaitQueue
仍在处理,窗口未就绪,因此PendingEvent
无法加入到WaitQueue
。如果5s后再次尝试添加PendingEvent
时窗口仍未就绪,则会触发ANR再点击一次,5s后便发生ANR,看看dump输出
PendingEvent:
MotionEvent(deviceId=5, source=0x00001002, action=0, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (716.0, 75.0)]), policyFlags=0x62000000, age=2067.0ms
InboundQueue: length=1
MotionEvent(deviceId=5, source=0x00001002, action=1, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (716.0, 75.0)]), policyFlags=0x62000000, age=1985.6ms
Connections:
......
11: channelName='14a0d447 com.cy.anr/com.cy.anr.MainActivity (server)', windowName='Window{14a0d447 u0 com.cy.anr/com.cy.anr.MainActivity}', status=NORMAL, monitor=false, inputPublisherBlocked=false
OutboundQueue: <empty>
WaitQueue: length=2
MotionEvent(deviceId=5, source=0x00001002, action=0, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (691.0, 62.0)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=0, age=3362.6ms, wait=3362.0ms
MotionEvent(deviceId=5, source=0x00001002, action=1, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (691.0, 62.0)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=1, age=3265.9ms, wait=3265.8ms