参考资料:
https://www.cnblogs.com/samchen2009/p/3368158.html
《深入理解Android 卷三》邓凡平
http://gityuan.com/2017/01/01/input-anr/
输入系统主要作用:
读取设备节点原始的事件,然后派发给一个特定的窗口以及窗口中的控件。
核心类
1、InputManagerService:
是系统进程的一个服务,分为java层和native层,java层负责与WMS通信。Native层是InputReader和InputDispatcher的运行容器。
2、EventHub :
作为直接操作设备节点的输入系统组件,隐藏了INotify与Epoll以及设备节点读取等底层操作。通过一个简单的接口getEvents向使用者提供抽取设备事件与原始输入事件的功能。
INotify与Epoll:
INotify:Linux内核提供的一种文件系统变化通知机制。
Epoll:可扩展I/O事件通知机制,
这两套机制提供的事件监听机制以最小的开销解决了文件系统变化通知以及文件描述符可读可写状态变化的监听问题。
3、WindowManagerService:
创建新窗口时,将窗口信息,包括点击区域、焦点窗口等信息实时更新到IMS的InputDispatcher中。
4、InputReader
(native层)InputReader:
作用1:运行在独立线程,循环从EventHub中取事件。
作用2:由于原始输入事件结构简单,信息量少,可用性不好,而且所携带的事件信息往往与硬件实现有关,因此InputReader会对原始输入事件进行整合变换,比如对Keyboard类型或Touch类型分别加工。
5、InputDispatcher
(native层)InputDispatcher:也运行在独立线程,保管WMS的所有窗口信息,当收到InputReader输入事件后,会从中寻找合适的窗口进行分发。
mInboundQueue:会区分Key或者Motion等类型事件分别派发,或者是废弃
mOutboundQueue:Connection中的成员,Connection描述了从InputDispatcher到目标窗口的一个连接
// Queue of events that need to be published to the connection.
std::deque<DispatchEntry*> outboundQueue;
WaitQueue:Connection中的成员
// Queue of events that have been published to the connection but that have not
// yet received a "finished" response from the application.
std::deque<DispatchEntry*> waitQueue;
流程:
1、inputReader取出的事件加入派发队列mInboundQueue,有需要的话唤醒派发线程,过程发生在InputReader线程中。
2、派发线程的循环:
1)从mInboundQueue取事件;
2)寻找目标窗口,过程中会判断是否需要出发ANR
3)找到窗口则进行事件的分发,将事件加入mOutboundQueue队列,通过InputChannel向上层分发事件,然后将mOutboundQueue队列的事件转移到WaitQueue队列等待上层处理结果。
4)上层事件处理完成后通知底层从WaitQueue移除事件。
源码分析:
InputDispatcher.cpp
bool InputDispatcherThread::threadLoop() {
mDispatcher->dispatchOnce();
return true;
}
真正的一次事件分发流程
void InputDispatcher::dispatchOnce() {
nsecs_t nextWakeupTime = LONG_LONG_MAX;
{ // acquire lock
AutoMutex _l(mLock);
mDispatcherIsAliveCondition.broadcast();
// Run a dispatch loop if there are no pending commands.
// The dispatch loop might enqueue commands to run afterwards.
if (!haveCommandsLocked()) {
dispatchOnceInnerLocked(&nextWakeupTime);
}
// Run all pending commands if there are any.
// If any commands were run then force the next poll to wake up immediately.
if (runCommandsLockedInterruptible()) {
nextWakeupTime = LONG_LONG_MIN;
}
} // release lock
// Wait for callback or timeout or wake. (make sure we round up, not down)
nsecs_t currentTime = now();
int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
mLooper->pollOnce(timeoutMillis);
}
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
nsecs_t currentTime = now();
...
// Optimize latency of app switches.
// Essentially we start a short timeout when an app switch key (HOME / ENDCALL) has
// been pressed. When it expires, we preempt dispatch and drop all other pending events.
bool isAppSwitchDue = mAppSwitchDueTime <= currentTime;
if (mAppSwitchDueTime < *nextWakeupTime) {
*nextWakeupTime = mAppSwitchDueTime;
}
// Ready to start a new event.
// If we don't already have a pending event, go grab one.
if (! mPendingEvent) {
if (mInboundQueue.isEmpty()) {
...
// Nothing to do if there is no pending event.
if (!mPendingEvent) {
return;
}
} else {
// Inbound queue has at least one entry.
mPendingEvent = mInboundQueue.dequeueAtHead();
traceInboundQueueLengthLocked();
}
// Poke user activity for this event.
if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {
pokeUserActivityLocked(mPendingEvent);
}
// Get ready to dispatch the event.
resetANRTimeoutsLocked();
}
// Now we have an event to dispatch.
// All events are eventually dequeued and processed this way, even if we intend to drop them.
ALOG_ASSERT(mPendingEvent != NULL);
bool done = false;
DropReason dropReason = DROP_REASON_NOT_DROPPED;
if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) {
dropReason = DROP_REASON_POLICY;
} else if (!mDispatchEnabled) {
dropReason = DROP_REASON_DISABLED;
}
if (mNextUnblockedEvent == mPendingEvent) {
mNextUnblockedEvent = NULL;
}
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;
}
done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
break;
}
case EventEntry::TYPE_MOTION: {
MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);
if (dropReason == DROP_REASON_NOT_DROPPED && isAppSwitchDue) {
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;
}
done = dispatchMotionLocked(currentTime, typedEntry,
&dropReason, nextWakeupTime);
break;
}
..
}
if (done) {
if (dropReason != DROP_REASON_NOT_DROPPED) {
dropInboundEventLocked(mPendingEvent, dropReason);
}
releasePendingEventLocked();
*nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately
}
}
bool InputDispatcher::dispatchMotionLocked(
nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) {
// Preprocessing.
...
// Clean up if dropping the event.
if (*dropReason != DROP_REASON_NOT_DROPPED) {
setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY
? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED);
return true;
}
bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER;
// Identify targets.
Vector<InputTarget> inputTargets;
bool conflictingPointerActions = false;
int32_t injectionResult;
if (isPointerEvent) {
// Pointer event. (eg. touchscreen)
injectionResult = findTouchedWindowTargetsLocked(currentTime,
entry, inputTargets, nextWakeupTime, &conflictingPointerActions);
} else {
// Non touch event. (eg. trackball)
injectionResult = findFocusedWindowTargetsLocked(currentTime,
entry, inputTargets, nextWakeupTime);
}
...
dispatchEventLocked(currentTime, entry, inputTargets);
return true;
}
int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
const MotionEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime,
bool* outConflictingPointerActions) {
...
// Ensure all touched foreground windows are ready for new input.
for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
const TouchedWindow& touchedWindow = mTempTouchState.windows[i];
if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
// If the touched window is paused then keep waiting.
if (touchedWindow.windowHandle->getInfo()->paused) {
injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
NULL, touchedWindow.windowHandle, nextWakeupTime,
"Waiting because the touched window is paused.");
goto Unresponsive;
}
// If the touched window is still working on previous events then keep waiting.
if (!isWindowReadyForMoreInputLocked(currentTime, touchedWindow.windowHandle, entry)) {
injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
NULL, touchedWindow.windowHandle, nextWakeupTime,
"Waiting because the touched window has not finished "
"processing the input events that were previously delivered to it.");
goto Unresponsive;
}
}
}
...
return injectionResult;
}
判断窗口是否可以接收事件,否则就触发ANR
bool InputDispatcher::isWindowReadyForMoreInputLocked(nsecs_t currentTime,
const sp<InputWindowHandle>& windowHandle, const EventEntry* eventEntry) {
ssize_t connectionIndex = getConnectionIndexLocked(windowHandle->getInputChannel());
if (connectionIndex >= 0) {
...
if (eventEntry->type == EventEntry::TYPE_KEY) {
// If the event is a key event, then we must wait for all previous events to
// complete before delivering it because previous events may have the
// side-effect of transferring focus to a different window and we want to
// ensure that the following keys are sent to the new window.
//
// Suppose the user touches a button in a window then immediately presses "A".
// If the button causes a pop-up window to appear then we want to ensure that
// the "A" key is delivered to the new pop-up window. This is because users
// often anticipate pending UI changes when typing on a keyboard.
// To obtain this behavior, we must serialize key events with respect to all
// prior input events.
return connection->outboundQueue.isEmpty()
&& connection->waitQueue.isEmpty();
}
// Touch events can always be sent to a window immediately because the user intended
// to touch whatever was visible at the time. Even if focus changes or a new
// window appears moments later, the touch event was meant to be delivered to
// whatever window happened to be on screen at the time.
//
// Generic motion events, such as trackball or joystick events are a little trickier.
// Like key events, generic motion events are delivered to the focused window.
// Unlike key events, generic motion events don't tend to transfer focus to other
// windows and it is not important for them to be serialized. So we prefer to deliver
// generic motion events as soon as possible to improve efficiency and reduce lag
// through batching.
//
// The one case where we pause input event delivery is when the wait queue is piling
// up with lots of events because the application is not responding.
// This condition ensures that ANRs are detected reliably.
if (!connection->waitQueue.isEmpty()
&& currentTime >= connection->waitQueue.head->deliveryTime
+ STREAM_AHEAD_EVENT_TIMEOUT) {
return false;
}
}
return true;
}
6、ANR
findTouchedWindowTargetsLocked函数中调用handleTargetsNotReadyLocked方法会执行,主要分如下三类
if (windowState != null) {
Slog.i(TAG_WM, "Input event dispatching timed out "
+ "sending to " + windowState.mAttrs.getTitle()
+ ". Reason: " + reason);
// MIUI ADD:
reason = windowState.mAttrs.getTitle() + ", " + reason;
} else if (activity != null) {
Slog.i(TAG_WM, "Input event dispatching timed out "
+ "sending to application " + activity.stringName
+ ". Reason: " + reason);
// MIUI ADD:
reason = activity.stringName + ", " + reason;
} else {
Slog.i(TAG_WM, "Input event dispatching timed out "
+ ". Reason: " + reason);
}
ANR的原因有如下类型
/**
* Anr reason types:
* Input dispatching timed out (Waiting because the focused window has not finished processing the input events that were previously delivered to it.)
* Input dispatching timed out (Waiting because the touched window has not finished processing the input events that were previously delivered to it.)
* Input dispatching timed out (Waiting because no window has focus but there is a focused application that may eventually add a window when it finishes starting up.)
* Input dispatching timed out (Waiting to send non-key event because the touched window has not finished processing certain input events that were delivered to it over 500.0ms ago. Wait queue length: 6. Wait queue head age: 6152.1ms.)
* Input dispatching timed out (Waiting to send key event because the focused window has not finished processing all of the input events that were previously delivered to it. Outbound queue length: 0. Wait queue length: 6.)
* Input dispatching timed out (Waiting because the touched window's input channel is full. Outbound queue length: 1. Wait queue length: 52.)
* Input dispatching timed out (Waiting because the focused window's input channel is full. Outbound queue length: 1. Wait queue length: 52.)
* Input dispatching timed out (Waiting because the touched window's input channel is not registered with the input dispatcher. The window may be in the process of being removed.)
* Input dispatching timed out (Waiting because the focused window's input channel is not registered with the input dispatcher. The window may be in the process of being removed.)
* Input dispatching timed out (Waiting because the touched window is paused.)
* Input dispatching timed out (Waiting because the focused window is paused.)
* Input dispatching timed out (Waiting because the touched window's input connection is %s. The window may be in the process of being removed
* Input dispatching timed out (Waiting because the focused window's input connection is %s. The window may be in the process of being removed
* executing service
* Broadcast of Intent
* ContentProvider not responding
*/
ANR实例分析:
待续