简介
在input服务中,InputReader负责读取数据,InputDispatcher负责分发数据,InputReader和InputDispatcher相互关联的重要纽带为队列mInboundQueue。ANR机制就是在InputDispatcher分发中充当监控。
分析 -- android 10.0
1.启动过程
void InputDispatcher::dispatchOnce() {
···
nsecs_t currentTime = now();
int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
mLooper->pollOnce(timeoutMillis);
}
因为InputDispatcher跑在一个循环执行的线程中,同时采用了Looper机制,唤醒线程的方式有两种:wake和timeout。通过wake唤醒的方式,一般通过InputReader调用实现,而对于InputDispatcher自身来说,此时自身的timeout机制就发挥很大的作用。ANR就是使用timeout唤醒做的监听
2.核心函数分析
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
···
// Ready to start a new event.
// If we don't already have a pending event, go grab one.
if (! mPendingEvent) {
if (mInboundQueue.isEmpty()) {
···
} else {
// Inbound queue has at least one entry.
mPendingEvent = mInboundQueue.dequeueAtHead();
traceInboundQueueLengthLocked();
}
···
//重置ANR
resetANRTimeoutsLocked();
}
···
//以下是触发ANR监听业务
switch (mPendingEvent->type) {
···
case EventEntry::TYPE_KEY: {
···
done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
break;
}
case EventEntry::TYPE_MOTION: {
···
done = dispatchMotionLocked(currentTime, typedEntry,
&dropReason, nextWakeupTime);
break;
}
···
//如果分发正常即done等于true,则重置ANR
if (done) {
if (dropReason != DROP_REASON_NOT_DROPPED) {
dropInboundEventLocked(mPendingEvent, dropReason);
}
mLastDropReason = dropReason;
//重置ANR
releasePendingEventLocked();
*nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately
}
}
ANR出现的情况,自然我们关注的是交互,进一步说是window:按键事件关注findFocusedWindowTargetsLocked,触摸事件关注findTouchedWindowTargetsLocked。因ANR是通过timeout方式触发线程执行循环,所以需要关注nextWakeupTime
3.重置ANR函数
void InputDispatcher::resetANRTimeoutsLocked() {
// Reset input target wait timeout.
mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE;
mInputTargetWaitApplicationToken.clear();
}
mInputTargetWaitCause为目标input等待的原因,具体有三种 INPUT_TARGET_WAIT_CAUSE_NONE/INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY/INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY
mInputTargetWaitApplicationToken为目标input的令牌token
4.分析nextWakeupTime的变化,以触摸事件为例
bool InputDispatcher::dispatchMotionLocked(
nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) {
···
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);
}
//如果触摸事件异常,则返回false,这样loop马上重启启动循环
if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
return false;
}
···
dispatchEventLocked(currentTime, entry, inputTargets);
return true;
}
以nextWakeupTime为线索跟踪分析
int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
const MotionEntry* entry, std::vector& inputTargets, nsecs_t* nextWakeupTime,
bool* outConflictingPointerActions) {
···
// Ensure all touched foreground windows are ready for new input.
for (const TouchedWindow& touchedWindow : mTempTouchState.windows) {
if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
// 如果有异常,则走进handleTargetsNotReadyLocked
std::string reason = checkWindowReadyForMoreInputLocked(currentTime,
touchedWindow.windowHandle, entry, "touched");
if (!reason.empty()) {
injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
nullptr, touchedWindow.windowHandle, nextWakeupTime, reason.c_str());
goto Unresponsive;
}
}
}
···
Unresponsive:
// Reset temporary touch state to ensure we release unnecessary references to input channels.
mTempTouchState.reset();
nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime);
updateDispatchStatistics(currentTime, entry, injectionResult, timeSpentWaitingForApplication);
···
return injectionResult;
}
int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime,
const EventEntry* entry,
const sp& applicationHandle,
const sp& windowHandle,
nsecs_t* nextWakeupTime, const char* reason) {
//针对不同窗口类型,重置mInputTargetWaitCause/mInputTargetWaitApplicationToken状态
//前面提到重置ANR条件的应用
if (applicationHandle == nullptr && windowHandle == nullptr) {
if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY) {//system_server进程等
mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY;
···
mInputTargetWaitApplicationToken.clear();
}
} else {
if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
nsecs_t timeout;
//超时时间来源,默认5秒。例如ProcessRecord/ActivityRecord
if (windowHandle != nullptr) {
timeout = windowHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
} else if (applicationHandle != nullptr) {
timeout = applicationHandle->getDispatchingTimeout(
DEFAULT_INPUT_DISPATCHING_TIMEOUT);
} else {
timeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT;
}
mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
···
mInputTargetWaitApplicationToken.clear();
···
}
}
···
if (currentTime >= mInputTargetWaitTimeoutTime) {//如果当前时间大于5秒,则发生ANR
onANRLocked(currentTime, applicationHandle, windowHandle,
entry->eventTime, mInputTargetWaitStartTime, reason);
// Force poll loop to wake up immediately on next iteration once we get the
// ANR response back from the policy.
*nextWakeupTime = LONG_LONG_MIN;
return INPUT_EVENT_INJECTION_PENDING;
} else {
//默认nextWakeupTime为最大值
//重置nextWakeupTime为current+5秒
// Force poll loop to wake up when timeout is due.
if (mInputTargetWaitTimeoutTime < *nextWakeupTime) {
*nextWakeupTime = mInputTargetWaitTimeoutTime;
}
return INPUT_EVENT_INJECTION_PENDING;
}
}
5.anr发生之后的函数流转过程,从onANRLocked开始
1)input服务,从native层来到java层
2)再来到window服务
void InputDispatcher::onANRLocked(
nsecs_t currentTime, const sp& applicationHandle,
const sp& windowHandle,
nsecs_t eventTime, nsecs_t waitStartTime, const char* reason) {
···
CommandEntry* commandEntry = postCommandLocked(
& InputDispatcher::doNotifyANRLockedInterruptible);
commandEntry->inputApplicationHandle = applicationHandle;
commandEntry->inputChannel = windowHandle != nullptr ?
getInputChannelLocked(windowHandle->getToken()) : nullptr;
commandEntry->reason = reason;
}
调用doNotifyANRLockedInterruptible
void InputDispatcher::doNotifyANRLockedInterruptible(
CommandEntry* commandEntry) {
mLock.unlock();
nsecs_t newTimeout = mPolicy->notifyANR(
commandEntry->inputApplicationHandle,
commandEntry->inputChannel ? commandEntry->inputChannel->getToken() : nullptr,
commandEntry->reason);
mLock.lock();
···
}
mPolicy实际为NativeInputManager
nsecs_t NativeInputManager::notifyANR(const sp& inputApplicationHandle,
const sp& token, const std::string& reason) {
···
jstring reasonObj = env->NewStringUTF(reason.c_str());
jlong newTimeout = env->CallLongMethod(mServiceObj,
gServiceClassInfo.notifyANR, tokenObj,
reasonObj);
if (checkAndClearExceptionFromCallback(env, "notifyANR")) {
newTimeout = 0; // abort dispatch
} else {
assert(newTimeout >= 0);
}
return newTimeout;
}
执行InputManagerService对象的notifyANR
InputManagerService
// Native callback.
private long notifyANR(IBinder token, String reason) {
return mWindowManagerCallbacks.notifyANR(
token, reason);
}
mWindowManagerCallbacks为WindowManagerService的mInputManagerCallback对象
InputManagerCallback
public long notifyANR(IBinder token, String reason) {
AppWindowToken appWindowToken = null;
WindowState windowState = null;
boolean aboveSystem = false;
synchronized (mService.mGlobalLock) {
···
//input事件发生时的关键日志:
if (windowState != null) {
Slog.i(TAG_WM, "Input event dispatching timed out "
+ "sending to " + windowState.mAttrs.getTitle()
+ ". Reason: " + reason);
···
} else if (appWindowToken != null) {
Slog.i(TAG_WM, "Input event dispatching timed out "
+ "sending to application " + appWindowToken.stringName
+ ". Reason: " + reason);
} else {
Slog.i(TAG_WM, "Input event dispatching timed out "
+ ". Reason: " + reason);
}
···
}
···
if (appWindowToken != null && appWindowToken.appToken != null) {
//按键事件anr流转
final boolean abort = appWindowToken.keyDispatchingTimedOut(reason,
(windowState != null) ? windowState.mSession.mPid : -1);
if (!abort) {
// The activity manager declined to abort dispatching.
// Wait a bit longer and timeout again later.
return appWindowToken.mInputDispatchingTimeoutNanos;
}
} else if (windowState != null) {
//触摸事件anr流转
long timeout = mService.mAmInternal.inputDispatchingTimedOut(
windowState.mSession.mPid, aboveSystem, reason);
if (timeout >= 0) {
// The activity manager declined to abort dispatching.
// Wait a bit longer and timeout again later.
return timeout * 1000000L; // nanoseconds
}
}
return 0; // abort dispatching
}
input 发生anr时的关键日志:
TAG:WindowManager
关键:Input event dispatching timed out xxx. Reason: + reason, 其中xxx取值:
窗口类型: sending to windowState.mAttrs.getTitle()
应用类型: sending to application appWindowToken.stringName
其他类型: 则为空.
reason如下分析
6.发生ANR的原因
1)窗口暂停: Waiting because the [targetType] window is paused.
2)窗口未连接: Waiting because the [targetType] window’s input channel is not registered with the input dispatcher. The window may be in the process of being removed.
3)窗口连接已死亡:Waiting because the [targetType] window’s input connection is [Connection.Status]. The window may be in the process of being removed.
4)窗口连接已满:Waiting because the [targetType] window’s input channel is full. Outbound queue length: [outboundQueue长度]. Wait queue length: [waitQueue长度].
5)按键事件,输出队列或事件等待队列不为空:Waiting to send key event because the [targetType] window has not finished processing all of the input events that were previously delivered to it. Outbound queue length: [outboundQueue长度]. Wait queue length: [waitQueue长度].
6)非按键事件,事件等待队列不为空且头事件分发超时500ms:Waiting to send non-key event because the [targetType] window has not finished processing certain input events that were delivered to it over 500ms ago. Wait queue length: [waitQueue长度]. Wait queue head age: [等待时长].
std::string InputDispatcher::checkWindowReadyForMoreInputLocked(nsecs_t currentTime,
const sp& windowHandle, const EventEntry* eventEntry,
const char* targetType) {
// If the window is paused then keep waiting.
···
// If the window's connection is not registered then keep waiting.
···
// If the connection is dead then keep waiting.
···
// If the connection is backed up then keep waiting.
···
// Ensure that the dispatch queues aren't too far backed up for this event.
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.
···
} else {
// 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.
···
}
return "";
}
7.继续分析anr日志记录。以触摸事件为例,inputDispatchingTimedOut
从window服务来到activit服务
ActivityManagerService
public long inputDispatchingTimedOut(int pid, boolean aboveSystem, String reason) {
synchronized (ActivityManagerService.this) {
return ActivityManagerService.this.inputDispatchingTimedOut(
pid, aboveSystem, reason);
}
}
long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) {
···
if (inputDispatchingTimedOut(proc, null, null, null, null, aboveSystem, reason)) {
return -1;
}
return timeout;
}
boolean inputDispatchingTimedOut(ProcessRecord proc, String activityShortComponentName,
ApplicationInfo aInfo, String parentShortComponentName,
WindowProcessController parentProcess, boolean aboveSystem, String reason) {
···
if (proc != null) {
···
proc.appNotResponding(activityShortComponentName, aInfo,
parentShortComponentName, parentProcess, aboveSystem, annotation);
}
return true;
}
proc.appNotResponding是所有发生anr所走的地方,这里暂不分析。结果是把日志记录在/data/anr目录
总结
1.input服务的anr发生所依赖的服务过程:
从input服务的native层来到java层,再流转到window服务,最后来到activity服务记录日志
2.input 发生anr时的关键日志:
TAG:WindowManager
关键:Input event dispatching timed out xxx. Reason: + reason, 其中xxx取值:
窗口类型: sending to windowState.mAttrs.getTitle()
应用类型: sending to application appWindowToken.stringName
其他类型: 则为空.
3.超时默认5秒。
窗口类型和应用类型超时来自ActivityTaskManagerService.KEY_DISPATCHING_TIMEOUT_MS,其他类型超时来自InputDispatcher.DEFAULT_INPUT_DISPATCHING_TIMEOUT
参考学习
http://gityuan.com/2017/01/01/input-anr/