Input系統:ANR原理分析
轉自:http://www.qingpingshan.com/m/view.php?aid=191258
基於Android 6.0原始碼, 分析Input事件發生ANR的原理
一. ANR概述
當input事件處理得慢就會觸發ANR,那ANR內部原理是什麼,哪些場景會產生ANR呢。 “工欲善其事必先利其器”,為了理解input ANR原理,前面幾篇文章疏通了整個input框架的處理流程,都是為了這篇文章而做鋪墊。在正式開始分析ANR觸發原理以及觸發場景之前,先來回顧一下input流程。
- Input系統—啟動篇:Input系統也是伴隨著system_server程序啟動過程而啟動。
- Input系統—InputReader執行緒 :通過EventHub從/dev/input節點獲取事件,轉換成EventEntry事件加入到InputDispatcher的mInboundQueue。
- Input系統—InputDispatcher執行緒 :從mInboundQueue佇列取出事件,轉換成DispatchEntry事件加入到connection的outboundQueue佇列。再然後開始處理分發事件,取出outbound佇列,放入waitQueue.
- Input系統—UI執行緒:Activity的onResume執行完便會向WMS註冊視窗資訊,並建立socket pair用於跨程序通訊;
- Input系統—程序互動:系統程序的”InputDispatcher”執行緒和應用程序的UI主執行緒通過socket pair進行互動;
有了以上基礎,再來從ANR視角看看Input系統。
1.1 InputReader
InputReader的主要工作分兩部分:
- 呼叫EventHub的getEvents()讀取節點/dev/input的input_event結構體轉換成RawEvent結構體,RawEvent根據不同InputMapper來轉換成相應的EventEntry,比如按鍵事件則對應KeyEntry,觸控事件則對應MotionEntry。
- 轉換結果:inut_event -> EventEntry;
- 將事件新增到mInboundQueue佇列尾部,加入該佇列前有以下兩個過濾:
- IMS.interceptKeyBeforeQueueing:事件分發前可增加業務邏輯;
- IMS.filterInputEvent:可攔截事件,當返回值為false的事件都直接攔截,沒有機會加入mInboundQueue佇列,不會再往下分發;否則進入下一步;
- enqueueInboundEventLocked:該事件放入mInboundQueue佇列尾部;
- mLooper->wake:並根據情況來喚醒InputDispatcher執行緒.
1.2 InputDispatcher
- dispatchOnceInnerLocked(): 從InputDispatcher的
mInboundQueue
佇列,取出事件EventEntry。另外該方法開始執行的時間點(currentTime)便是後續事件dispatchEntry的分發時間(deliveryTime) - dispatchKeyLocked():滿足一定條件時會新增命令doInterceptKeyBeforeDispatchingLockedInterruptible;
- enqueueDispatchEntryLocked():生成事件DispatchEntry並加入connection的
outbound
佇列 - startDispatchCycleLocked():從outboundQueue中取出事件DispatchEntry, 重新放入connection的
waitQueue
佇列; - runCommandsLockedInterruptible():通過迴圈遍歷地方式,依次處理mCommandQueue佇列中的所有命令。而mCommandQueue佇列中的命令是通過postCommandLocked()方式向該佇列新增的。ANR回撥命令便是在這個時機執行。
- handleTargetsNotReadyLocked(): 該過程會判斷是否等待超過5s來決定是否呼叫onANRLocked().
1.3 UI執行緒
- “InputDispatcher”執行緒監聽socket服務端,收到訊息後回撥InputDispatcher.handleReceiveCallback();
- UI主執行緒監聽socket客戶端,收到訊息後回撥NativeInputEventReceiver.handleEvent().
對於ANR的觸發主要是在InputDispatcher過程,下面再從ANR的角度來說一說ANR觸發過程。
二. ANR觸發機制
2.1 dispatchOnceInnerLocked
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) { nsecs_t currentTime = now(); //當前時間 if (!mDispatchEnabled) { //預設值為false resetKeyRepeatLocked(); //重置操作 } if (mDispatchFrozen) { //預設值為false return; //當分發被凍結,則不再處理超時和分發事件的工作,直接返回 } //優化app切換延遲,當切換超時,則搶佔分發,丟棄其他所有即將要處理的事件。 bool isAppSwitchDue = mAppSwitchDueTime <= currentTime; ... if (!mPendingEvent) { if (mInboundQueue.isEmpty()) { if (!mPendingEvent) { return; //沒有事件需要處理,則直接返回 } } else { //從mInboundQueue取出頭部的事件 mPendingEvent = mInboundQueue.dequeueAtHead(); } ... resetANRTimeoutsLocked(); //重置ANR資訊[見小節2.1.1] } 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; } ... 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; } // 分發按鍵事件[見小節2.2] done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime); break; } ... } ... //分發操作完成,則進入該分支 if (done) { if (dropReason != DROP_REASON_NOT_DROPPED) { //[見小節2.1.2] dropInboundEventLocked(mPendingEvent, dropReason); } mLastDropReason = dropReason; releasePendingEventLocked(); //釋放pending事件 *nextWakeupTime = LONG_LONG_MIN; //強制立刻執行輪詢 } }
在enqueueInboundEventLocked()的過程中已設定mAppSwitchDueTime等於eventTime加上500ms:
mAppSwitchDueTime = keyEntry->eventTime + APP_SWITCH_TIMEOUT;
該方法主要功能:
- mDispatchFrozen用於決定是否凍結事件分發工作不再往下執行;
- 當事件分發的時間點距離該事件加入mInboundQueue的時間超過500ms,則認為app切換過期,即isAppSwitchDue=true;
- mInboundQueue不為空,則取出頭部的事件,放入mPendingEvent變數;並重置ANR時間;
- 根據情況設定dropReason;
- 根據EventEntry的type型別分別處理,比如按鍵呼叫dispatchKeyLocked分發事件;
- 執行完成後,根據dropReason來決定是否丟失事件,以及釋放當前事件;
接下來以按鍵為例來展開說明, 則進入[小節2.2] dispatchKeyLocked
2.1.1 resetANRTimeoutsLocked
void InputDispatcher::resetANRTimeoutsLocked() { // 重置等待超時cause和handle mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE; mInputTargetWaitApplicationHandle.clear(); }
2.1.2 dropInboundEventLocked
void InputDispatcher::dropInboundEventLocked(EventEntry* entry, DropReason dropReason) { const char* reason; switch (dropReason) { case DROP_REASON_POLICY: reason = "inbound event was dropped because the policy consumed it"; break; case DROP_REASON_DISABLED: if (mLastDropReason != DROP_REASON_DISABLED) { ALOGI("Dropped event because input dispatch is disabled."); } reason = "inbound event was dropped because input dispatch is disabled"; break; case DROP_REASON_APP_SWITCH: ALOGI("Dropped event because of pending overdue app switch."); reason = "inbound event was dropped because of pending overdue app switch"; break; case DROP_REASON_BLOCKED: ALOGI("Dropped event because the current application is not responding and the user " "has started interacting with a different application."); reason = "inbound event was dropped because the current application is not responding " "and the user has started interacting with a different application"; break; case DROP_REASON_STALE: ALOGI("Dropped event because it is stale."); reason = "inbound event was dropped because it is stale"; break; default: return; } switch (entry->type) { case EventEntry::TYPE_KEY: { CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS, reason); synthesizeCancelationEventsForAllConnectionsLocked(options); break; } ... } }
2.2 dispatchKeyLocked
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) { ... if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) { //當前時間小於喚醒時間,則進入等待狀態。 if (currentTime < entry->interceptKeyWakeupTime) { if (entry->interceptKeyWakeupTime < *nextWakeupTime) { *nextWakeupTime = entry->interceptKeyWakeupTime; } return false; //直接返回 } entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN; entry->interceptKeyWakeupTime = 0; } if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) { //讓policy有機會執行攔截操作 if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) { CommandEntry* commandEntry = postCommandLocked( & InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible); if (mFocusedWindowHandle != NULL) { commandEntry->inputWindowHandle = mFocusedWindowHandle; } commandEntry->keyEntry = entry; entry->refCount += 1; return false; //直接返回 } else { entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE; } } else if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) { if (*dropReason == DROP_REASON_NOT_DROPPED) { *dropReason = DROP_REASON_POLICY; } } //如果需要丟棄該事件,則執行清理操作 if (*dropReason != DROP_REASON_NOT_DROPPED) { setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY ? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED); return true; //直接返回 } Vector<InputTarget> inputTargets; // 【見小節2.3】 int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime, entry, inputTargets, nextWakeupTime); if (injectionResult == INPUT_EVENT_INJECTION_PENDING) { return false; //直接返回 } setInjectionResultLocked(entry, injectionResult); if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) { return true; //直接返回 } addMonitoringTargetsLocked(inputTargets); //只有injectionResult是成功,才有機會執行分發事件【見小節2.5】 dispatchEventLocked(currentTime, entry, inputTargets); return true; }
在以下場景下,有可能無法分發事件:
- 當前時間小於喚醒時間(nextWakeupTime)的情況;
- policy需要提前攔截事件的情況;
- 需要drop事件的情況;
- 尋找聚焦視窗失敗的情況;
如果成功跳過以上所有情況,則會進入執行事件分發的過程。
2.3 findFocusedWindowTargetsLocked
int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime, const EventEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime) { int32_t injectionResult; String8 reason; if (mFocusedWindowHandle == NULL) { if (mFocusedApplicationHandle != NULL) { //【見小節2.3.2】 injectionResult = handleTargetsNotReadyLocked(currentTime, entry, mFocusedApplicationHandle, NULL, nextWakeupTime, "Waiting because no window has focus but there is a " "focused application that may eventually add a window " "when it finishes starting up."); goto Unresponsive; } ALOGI("Dropping event because there is no focused window or focused application."); injectionResult = INPUT_EVENT_INJECTION_FAILED; goto Failed; } //許可權檢查 if (! checkInjectionPermission(mFocusedWindowHandle, entry->injectionState)) { injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED; goto Failed; } //檢測視窗是否為更多的輸入操作而準備就緒【見小節2.3.1】 reason = checkWindowReadyForMoreInputLocked(currentTime, mFocusedWindowHandle, entry, "focused"); if (!reason.isEmpty()) { //【見小節2.3.2】 injectionResult = handleTargetsNotReadyLocked(currentTime, entry, mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime, reason.string()); goto Unresponsive; } injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED; //成功找到目標視窗,新增到目標視窗 addWindowTargetLocked(mFocusedWindowHandle, InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS, BitSet32(0), inputTargets); Failed: Unresponsive: //TODO: 統計等待時長資訊,目前沒有實現,這個方法還是很值得去改造 nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime); updateDispatchStatisticsLocked(currentTime, entry, injectionResult, timeSpentWaitingForApplication); return injectionResult; }
尋找聚焦視窗失敗的情況:
- 無視窗,無應用:Dropping event because there is no focused window or focused application.(這並不導致ANR的情況,因為沒有機會呼叫handleTargetsNotReadyLocked)
- 無視窗, 有應用:Waiting because no window has focus but there is a focused application that may eventually add a window when it finishes starting up.
另外,還有更多多的失敗場景見checkWindowReadyForMoreInputLocked的過程,如下:
2.3.1 checkWindowReadyForMoreInputLocked
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 { 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(); }
2.3.2 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) { if (applicationHandle == NULL && windowHandle == NULL) { if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY) { mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY; mInputTargetWaitStartTime = currentTime; //當前時間 mInputTargetWaitTimeoutTime = LONG_LONG_MAX; mInputTargetWaitTimeoutExpired = false; mInputTargetWaitApplicationHandle.clear(); } } else { if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) { nsecs_t timeout; if (windowHandle != NULL) { timeout = windowHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT); } else if (applicationHandle != NULL) { timeout = applicationHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT); } else { timeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT; // 5s } mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY; mInputTargetWaitStartTime = currentTime; //當前時間 mInputTargetWaitTimeoutTime = currentTime + timeout; mInputTargetWaitTimeoutExpired = false; mInputTargetWaitApplicationHandle.clear(); if (windowHandle != NULL) { mInputTargetWaitApplicationHandle = windowHandle->inputApplicationHandle; } if (mInputTargetWaitApplicationHandle == NULL && applicationHandle != NULL) { mInputTargetWaitApplicationHandle = applicationHandle; } } } if (mInputTargetWaitTimeoutExpired) { return INPUT_EVENT_INJECTION_TIMED_OUT; //等待超時已過期,則直接返回 } //當超時5s則進入ANR流程 if (currentTime >= mInputTargetWaitTimeoutTime) { onANRLocked(currentTime, applicationHandle, windowHandle, entry->eventTime, mInputTargetWaitStartTime, reason); *nextWakeupTime = LONG_LONG_MIN; //強制立刻執行輪詢來執行ANR策略 return INPUT_EVENT_INJECTION_PENDING; } else { if (mInputTargetWaitTimeoutTime < *nextWakeupTime) { *nextWakeupTime = mInputTargetWaitTimeoutTime; //當觸發超時則強制執行輪詢 } return INPUT_EVENT_INJECTION_PENDING; } }
- 當applicationHandle和windowHandle同時為空, 且system準備就緒的情況下
- 設定等待理由 INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY;
- 設定超時等待時長為無限大;
- 設定TimeoutExpired= false
- 清空等待佇列;
- 當applicationHandle和windowHandle至少一個不為空, 且application準備就緒的情況下:
- 設定等待理由 INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
- 設定超時等待時長為5s;
- 設定TimeoutExpired= false
- 清空等待佇列;
繼續回到[小節2.3]findFocusedWindowTargetsLocked,如果沒有發生ANR,則addWindowTargetLocked()將該事件新增到inputTargets。
2.4 dispatchEventLocked
void InputDispatcher::dispatchEventLocked(nsecs_t currentTime, EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) { //向mCommandQueue佇列新增doPokeUserActivityLockedInterruptible命令 pokeUserActivityLocked(eventEntry); for (size_t i = 0; i < inputTargets.size(); i++) { const InputTarget& inputTarget = inputTargets.itemAt(i); ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel); if (connectionIndex >= 0) { sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex); //找到目標連線[見小節2.5] prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget); } } }
該方法主要功能是將eventEntry傳送到目標inputTargets.
2.5 prepareDispatchCycleLocked
void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) { if (connection->status != Connection::STATUS_NORMAL) { return; //當連線已破壞,則直接返回 } ... //[見小節2.6] enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget); }
2.6 enqueueDispatchEntriesLocked
void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime, const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) { bool wasEmpty = connection->outboundQueue.isEmpty(); //[見小節2.7] enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT); ... if (wasEmpty && !connection->outboundQueue.isEmpty()) { //當原先的outbound佇列為空, 且當前outbound不為空的情況執行.[見小節2.8] startDispatchCycleLocked(currentTime, connection); } }
2.7 enqueueDispatchEntryLocked
void InputDispatcher::enqueueDispatchEntryLocked( const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget, int32_t dispatchMode) { int32_t inputTargetFlags = inputTarget->flags; if (!(inputTargetFlags & dispatchMode)) { return; //分發模式不匹配,則直接返回 } inputTargetFlags = (inputTargetFlags & ~InputTarget::FLAG_DISPATCH_MASK) | dispatchMode; //生成新的事件, 加入connection的outbound佇列 DispatchEntry* dispatchEntry = new DispatchEntry(eventEntry, inputTargetFlags, inputTarget->xOffset, inputTarget->yOffset, inputTarget->scaleFactor); ... //新增到outboundQueue隊尾 connection->outboundQueue.enqueueAtTail(dispatchEntry); }
該方法主要功能:
- 根據dispatchMode來決定是否需要加入outboundQueue佇列;
- 根據EventEntry,來生成DispatchEntry事件;
- 將dispatchEntry加入到connection的outbound佇列.
執行到這裡,其實等於由做了一次搬運的工作,將InputDispatcher中mInboundQueue中的事件取出後, 找到目標window後,封裝dispatchEntry加入到connection的outbound佇列.
如果當connection原先的outbound佇列為空, 經過enqueueDispatchEntryLocked處理後, 該outbound不為空的情況下, 則執行startDispatchCycleLocked()方法.
2.8 startDispatchCycleLocked
void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection) { //當Connection狀態正常,且outboundQueue不為空 while (connection->status == Connection::STATUS_NORMAL && !connection->outboundQueue.isEmpty()) { DispatchEntry* dispatchEntry = connection->outboundQueue.head; dispatchEntry->deliveryTime = currentTime; //設定deliveryTime時間 status_t status; EventEntry* eventEntry = dispatchEntry->eventEntry; switch (eventEntry->type) { case EventEntry::TYPE_KEY: { KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry); //釋出按鍵時間 [見小節2.9] 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; } ... } //釋出結果分析 if (status) { if (status == WOULD_BLOCK) { if (connection->waitQueue.isEmpty()) { //pipe已滿,但waitQueue為空. 不正常的行為 abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/); } else { // 處於阻塞狀態 connection->inputPublisherBlocked = true; } } else { //不不正常的行為 abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/); } return; } //從outboundQueue中取出事件,重新放入waitQueue佇列 connection->outboundQueue.dequeue(dispatchEntry); connection->waitQueue.enqueueAtTail(dispatchEntry); } }
startDispatchCycleLocked的主要功能: 從outboundQueue中取出事件,重新放入waitQueue佇列
- abortBrokenDispatchCycleLocked()方法最終會呼叫到Java層的IMS.notifyInputChannelBroken().
2.9 inputPublisher.publishKeyEvent
[-> InputTransport.cpp]
status_t InputPublisher::publishKeyEvent(...) { if (!seq) { return BAD_VALUE; } InputMessage msg; msg.header.type = InputMessage::TYPE_KEY; msg.body.key.seq = seq; msg.body.key.deviceId = deviceId; msg.body.key.source = source; msg.body.key.action = action; msg.body.key.flags = flags; msg.body.key.keyCode = keyCode; msg.body.key.scanCode = scanCode; msg.body.key.metaState = metaState; msg.body.key.repeatCount = repeatCount; msg.body.key.downTime = downTime; msg.body.key.eventTime = eventTime; return mChannel->sendMessage(&msg); }
2.10 下一步
經過Input系統—程序互動幾經周折,runCommandsLockedInterruptible過程處理的便是如下命令:
void InputDispatcher::doDispatchCycleFinishedLockedInterruptible( CommandEntry* commandEntry) { sp<Connection> connection = commandEntry->connection; nsecs_t finishTime = commandEntry->eventTime; uint32_t seq = commandEntry->seq; bool handled = commandEntry->handled; //獲取分發事件 DispatchEntry* dispatchEntry = connection->findWaitQueueEntry(seq); if (dispatchEntry) { nsecs_t eventDuration = finishTime - dispatchEntry->deliveryTime; //打印出所有分發時間超過2s的事件 if (eventDuration > SLOW_EVENT_PROCESSING_WARNING_TIMEOUT) { String8 msg; msg.appendFormat("Window '%s' spent %0.1fms processing the last input event: ", connection->getWindowName(), eventDuration * 0.000001f); dispatchEntry->eventEntry->appendDescription(msg); ALOGI("%s", msg.string()); } bool restartEvent; if (dispatchEntry->eventEntry->type == EventEntry::TYPE_KEY) { KeyEntry* keyEntry = static_cast<KeyEntry*>(dispatchEntry->eventEntry); restartEvent = afterKeyEventLockedInterruptible(connection, dispatchEntry, keyEntry, handled); } ... if (dispatchEntry == connection->findWaitQueueEntry(seq)) { //將dispatchEntry事件從等待佇列(waitQueue)中移除 connection->waitQueue.dequeue(dispatchEntry); if (restartEvent && connection->status == Connection::STATUS_NORMAL) { connection->outboundQueue.enqueueAtHead(dispatchEntry); } else { releaseDispatchEntryLocked(dispatchEntry); } } //啟動下一個事件處理迴圈。 startDispatchCycleLocked(now(), connection); } }
只有收到該命令,才完成dispatchEntry事件從等待佇列( waitQueue
)中移除操作。 這便是完整的一次input事件處理過程。
三. command
當執行findFocusedWindowTargetsLocked()過程呼叫到handleTargetsNotReadyLocked,且滿足超時5s的情況則會呼叫onANRLocked().
3.1 onANRLocked
[-> InputDispatcher.cpp]
void InputDispatcher::onANRLocked( nsecs_t currentTime, const sp<InputApplicationHandle>& applicationHandle, const sp<InputWindowHandle>& windowHandle, nsecs_t eventTime, nsecs_t waitStartTime, const char* reason) { float dispatchLatency = (currentTime - eventTime) * 0.000001f; float waitDuration = (currentTime - waitStartTime) * 0.000001f; ALOGI("Application is not responding: %s. " "It has been %0.1fms since event, %0.1fms since wait started. Reason: %s", getApplicationWindowLabelLocked(applicationHandle, windowHandle).string(), dispatchLatency, waitDuration, reason); //捕獲ANR的現場資訊 time_t t = time(NULL); struct tm tm; localtime_r(&t, &tm); char timestr[64]; strftime(timestr, sizeof(timestr), "%F %T", &tm); mLastANRState.clear(); mLastANRState.append(INDENT "ANR:\n"); mLastANRState.appendFormat(INDENT2 "Time: %s\n", timestr); mLastANRState.appendFormat(INDENT2 "Window: %s\n", getApplicationWindowLabelLocked(applicationHandle, windowHandle).string()); mLastANRState.appendFormat(INDENT2 "DispatchLatency: %0.1fms\n", dispatchLatency); mLastANRState.appendFormat(INDENT2 "WaitDuration: %0.1fms\n", waitDuration); mLastANRState.appendFormat(INDENT2 "Reason: %s\n", reason); dumpDispatchStateLocked(mLastANRState); //將ANR命令加入mCommandQueue CommandEntry* commandEntry = postCommandLocked( & InputDispatcher::doNotifyANRLockedInterruptible); commandEntry->inputApplicationHandle = applicationHandle; commandEntry->inputWindowHandle = windowHandle; commandEntry->reason = reason; }
發生ANR呼叫onANRLocked()的過程會將doNotifyANRLockedInterruptible加入mCommandQueue。 在下一輪InputDispatcher.dispatchOnce的過程中會先執行runCommandsLockedInterruptible()方法,取出 mCommandQueue佇列的所有命令逐一執行。那麼ANR所對應的命令doNotifyANRLockedInterruptible,接下來看該方法。
3.2 doNotifyANRLockedInterruptible
[-> InputDispatcher.cpp]
void InputDispatcher::doNotifyANRLockedInterruptible( CommandEntry* commandEntry) { mLock.unlock(); //[見小節3.3] nsecs_t newTimeout = mPolicy->notifyANR( commandEntry->inputApplicationHandle, commandEntry->inputWindowHandle, commandEntry->reason); mLock.lock(); //newTimeout =5s [見小節3.8] resumeAfterTargetsNotReadyTimeoutLocked(newTimeout, commandEntry->inputWindowHandle != NULL ? commandEntry->inputWindowHandle->getInputChannel() : NULL); }
mPolicy是指NativeInputManager
3.3 NativeInputManager.notifyANR
[-> com_android_server_input_InputManagerService.cpp]
nsecs_t NativeInputManager::notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle, const sp<InputWindowHandle>& inputWindowHandle, const String8& reason) { JNIEnv* env = jniEnv(); jobject inputApplicationHandleObj = getInputApplicationHandleObjLocalRef(env, inputApplicationHandle); jobject inputWindowHandleObj = getInputWindowHandleObjLocalRef(env, inputWindowHandle); jstring reasonObj = env->NewStringUTF(reason.string()); //呼叫Java方法[見小節3.4] jlong newTimeout = env->CallLongMethod(mServiceObj, gServiceClassInfo.notifyANR, inputApplicationHandleObj, inputWindowHandleObj, reasonObj); if (checkAndClearExceptionFromCallback(env, "notifyANR")) { newTimeout = 0; //丟擲異常,則清理並重置timeout } ... return newTimeout; }
先看看register_android_server_InputManager過程:
int register_android_server_InputManager(JNIEnv* env) { int res = jniRegisterNativeMethods(env, "com/android/server/input/InputManagerService", gInputManagerMethods, NELEM(gInputManagerMethods)); jclass clazz; FIND_CLASS(clazz, "com/android/server/input/InputManagerService"); ... GET_METHOD_ID(gServiceClassInfo.notifyANR, clazz, "notifyANR", "(Lcom/android/server/input/InputApplicationHandle;Lcom/android/server/input/InputWindowHandle;Ljava/lang/String;)J"); ... }
可知gServiceClassInfo.notifyANR是指IMS.notifyANR
3.4 IMS.notifyANR
[-> InputManagerService.java]
private long notifyANR(InputApplicationHandle inputApplicationHandle, InputWindowHandle inputWindowHandle, String reason) { //[見小節3.5] return mWindowManagerCallbacks.notifyANR( inputApplicationHandle, inputWindowHandle, reason); }
此處mWindowManagerCallbacks是指InputMonitor物件。
3.5 InputMonitor.notifyANR
[-> InputMonitor.java]
public long notifyANR(InputApplicationHandle inputApplicationHandle, InputWindowHandle inputWindowHandle, String reason) { AppWindowToken appWindowToken = null; WindowState windowState = null; boolean aboveSystem = false; synchronized (mService.mWindowMap) { if (inputWindowHandle != null) { windowState = (WindowState) inputWindowHandle.windowState; if (windowState != null) { appWindowToken = windowState.mAppToken; } } if (appWindowToken == null && inputApplicationHandle != null) { appWindowToken = (AppWindowToken)inputApplicationHandle.appWindowToken; } //輸出input事件分發超時log if (windowState != null) { Slog.i(WindowManagerService.TAG, "Input event dispatching timed out " + "sending to " + windowState.mAttrs.getTitle() + ". Reason: " + reason); int systemAlertLayer = mService.mPolicy.windowTypeToLayerLw( WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); aboveSystem = windowState.mBaseLayer > systemAlertLayer; } else if (appWindowToken != null) { Slog.i(WindowManagerService.TAG, "Input event dispatching timed out " + "sending to application " + appWindowToken.stringName + ". Reason: " + reason); } else { Slog.i(WindowManagerService.TAG, "Input event dispatching timed out " + ". Reason: " + reason); } mService.saveANRStateLocked(appWindowToken, windowState, reason); } if (appWindowToken != null && appWindowToken.appToken != null) { //【見小節3.6.1】 boolean abort = appWindowToken.appToken.keyDispatchingTimedOut(reason); if (! abort) { return appWindowToken.inputDispatchingTimeoutNanos; //5s } } else if (windowState != null) { //【見小節3.6.2】 long timeout = ActivityManagerNative.getDefault().inputDispatchingTimedOut( windowState.mSession.mPid, aboveSystem, reason); if (timeout >= 0) { return timeout * 1000000L; //5s } } return 0; }
發生input相關的ANR時在system log輸出ANR資訊。
3.6 DispatchingTimedOut
3.6.1 Token.keyDispatchingTimedOut
[-> ActivityRecord.java :: Token]
final class ActivityRecord { static class Token extends IApplicationToken.Stub { public boolean keyDispatchingTimedOut(String reason) { ActivityRecord r; ActivityRecord anrActivity; ProcessRecord anrApp; synchronized (mService) { r = tokenToActivityRecordLocked(this); if (r == null) { return false; } anrActivity = r.getWaitingHistoryRecordLocked(); anrApp = r != null ? r.app : null; } //[見小節3.7] return mService.inputDispatchingTimedOut(anrApp, anrActivity, r, false, reason); } ... } }
3.6.2 AMS.inputDispatchingTimedOut
public long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) { ... ProcessRecord proc; long timeout; synchronized (this) { synchronized (mPidsSelfLocked) { proc = mPidsSelfLocked.get(pid); //根據pid檢視程序record } timeout = getInputDispatchingTimeoutLocked(proc); } //【見小節3.7】 if (!inputDispatchingTimedOut(proc, null, null, aboveSystem, reason)) { return -1; } return timeout; }
inputDispatching的超時為 KEY_DISPATCHING_TIMEOUT
,即timeout = 5s。
3.7 AMS.inputDispatchingTimedOut
public boolean inputDispatchingTimedOut(final ProcessRecord proc, final ActivityRecord activity, final ActivityRecord parent, final boolean aboveSystem, String reason) { ... final String annotation; if (reason == null) { annotation = "Input dispatching timed out"; } else { annotation = "Input dispatching timed out (" + reason + ")"; } if (proc != null) { ... //通過handler機制,交由“ActivityManager”執行緒執行ANR處理過程。 mHandler.post(new Runnable() { public void run() { appNotResponding(proc, activity, parent, aboveSystem, annotation); } }); } return true; }
appNotResponding會輸出現場的重要程序的trace等資訊。 再回到【小節3.2】處理完ANR後再呼叫resumeAfterTargetsNotReadyTimeoutLocked。
3.8 resumeAfterTargetsNotReadyTimeoutLocked
[-> InputDispatcher.cpp]
void InputDispatcher::resumeAfterTargetsNotReadyTimeoutLocked(nsecs_t newTimeout, const sp<InputChannel>& inputChannel) { if (newTimeout > 0) { //超時時間增加5s mInputTargetWaitTimeoutTime = now() + newTimeout; } else { // Give up. mInputTargetWaitTimeoutExpired = true; // Input state will not be realistic. Mark it out of sync. if (inputChannel.get()) { ssize_t connectionIndex = getConnectionIndexLocked(inputChannel); if (connectionIndex >= 0) { sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex); sp<InputWindowHandle> windowHandle = connection->inputWindowHandle; if (windowHandle != NULL) { const InputWindowInfo* info = windowHandle->getInfo(); if (info) { ssize_t stateIndex = mTouchStatesByDisplay.indexOfKey(info->displayId); if (stateIndex >= 0) { mTouchStatesByDisplay.editValueAt(stateIndex).removeWindow( windowHandle); } } } if (connection->status == Connection::STATUS_NORMAL) { CancelationOptions options(CancelationOptions::CANCEL_ALL_EVENTS, "application not responding"); synthesizeCancelationEventsForConnectionLocked(connection, options); } } } } }
四. 總結
4.1 dispatching超時型別
由小節[3.5] InputMonitor.notifyANR完成, 當發生ANR時system log中會出現以下資訊:
- 視窗:Input event dispatching timed out sending to
windowState.mAttrs.getTitle()
. Reason: +reason
- 應用:Input event dispatching timed out sending to application
appWindowToken.stringName
. Reason: +reason
- 其他:Input event dispatching timed out. Reason: +
reason
4.2 reason型別
由小節[2.3.1]checkWindowReadyForMoreInputLocked完成, ANR reason主要有以下幾類:
- 無視窗, 有應用 :Waiting because no window has focus but there is a focused application that may eventually add a window when it finishes starting up.
- 視窗連線已死亡 :Waiting because the [targetType] window’s input connection is [Connection.Status]. The window may be in the process of being removed.
- 視窗連線已滿 :Waiting because the [targetType] window’s input channel is full. Outbound queue length: [outboundQueue長度]. Wait queue length: [waitQueue長度].
- 按鍵事件,輸出佇列或事件等待佇列不為空 :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長度].
- 非按鍵事件,事件等待佇列不為空且頭事件分發超時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: [等待時長].
其中
- targetType的取值為”focused”或者”touched”
- Connection.Status的取值為”NORMAL”,”BROKEN”,”ZOMBIE”
4.3 dropReason型別
由小節[2.1.2] dropInboundEventLocked完成,輸出事件丟棄的原因:
- DROP_REASON_POLICY: “inbound event was dropped because the policy consumed it”;
- DROP_REASON_DISABLED: “inbound event was dropped because input dispatch is disabled”;
- DROP_REASON_APP_SWITCH: “inbound event was dropped because of pending overdue app switch”;
- DROP_REASON_BLOCKED: “inbound event was dropped because the current application is not responding and the user has started interacting with a different application””;
- DROP_REASON_STALE: “inbound event was dropped because it is stale”;
(責任編輯:那一抹憂傷)