1. 程式人生 > >Input系統:ANR原理分析

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的主要工作分兩部分:

  1. 呼叫EventHub的getEvents()讀取節點/dev/input的input_event結構體轉換成RawEvent結構體,RawEvent根據不同InputMapper來轉換成相應的EventEntry,比如按鍵事件則對應KeyEntry,觸控事件則對應MotionEntry。
    • 轉換結果:inut_event -> EventEntry;
  2. 將事件新增到mInboundQueue佇列尾部,加入該佇列前有以下兩個過濾:
    • IMS.interceptKeyBeforeQueueing:事件分發前可增加業務邏輯;
    • IMS.filterInputEvent:可攔截事件,當返回值為false的事件都直接攔截,沒有機會加入mInboundQueue佇列,不會再往下分發;否則進入下一步;
    • enqueueInboundEventLocked:該事件放入mInboundQueue佇列尾部;
    • mLooper->wake:並根據情況來喚醒InputDispatcher執行緒.

1.2 InputDispatcher

  1. dispatchOnceInnerLocked(): 從InputDispatcher的 mInboundQueue 佇列,取出事件EventEntry。另外該方法開始執行的時間點(currentTime)便是後續事件dispatchEntry的分發時間(deliveryTime)
  2. dispatchKeyLocked():滿足一定條件時會新增命令doInterceptKeyBeforeDispatchingLockedInterruptible;
  3. enqueueDispatchEntryLocked():生成事件DispatchEntry並加入connection的 outbound 佇列
  4. startDispatchCycleLocked():從outboundQueue中取出事件DispatchEntry, 重新放入connection的 waitQueue 佇列;
  5. runCommandsLockedInterruptible():通過迴圈遍歷地方式,依次處理mCommandQueue佇列中的所有命令。而mCommandQueue佇列中的命令是通過postCommandLocked()方式向該佇列新增的。ANR回撥命令便是在這個時機執行。
  6. 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;

該方法主要功能:

  1. mDispatchFrozen用於決定是否凍結事件分發工作不再往下執行;
  2. 當事件分發的時間點距離該事件加入mInboundQueue的時間超過500ms,則認為app切換過期,即isAppSwitchDue=true;
  3. mInboundQueue不為空,則取出頭部的事件,放入mPendingEvent變數;並重置ANR時間;
  4. 根據情況設定dropReason;
  5. 根據EventEntry的type型別分別處理,比如按鍵呼叫dispatchKeyLocked分發事件;
  6. 執行完成後,根據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主要有以下幾類:

  1. 無視窗, 有應用 :Waiting because no window has focus but there is a focused application that may eventually add a window when it finishes starting up.
  2. 視窗連線已死亡 :Waiting because the [targetType] window’s input connection is [Connection.Status]. The window may be in the process of being removed.
  3. 視窗連線已滿 :Waiting because the [targetType] window’s input channel is full. Outbound queue length: [outboundQueue長度]. Wait queue length: [waitQueue長度].
  4. 按鍵事件,輸出佇列或事件等待佇列不為空 :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長度].
  5. 非按鍵事件,事件等待佇列不為空且頭事件分發超時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完成,輸出事件丟棄的原因:

  1. DROP_REASON_POLICY: “inbound event was dropped because the policy consumed it”;
  2. DROP_REASON_DISABLED: “inbound event was dropped because input dispatch is disabled”;
  3. DROP_REASON_APP_SWITCH: “inbound event was dropped because of pending overdue app switch”;
  4. DROP_REASON_BLOCKED: “inbound event was dropped because the current application is not responding and the user has started interacting with a different application””;
  5. DROP_REASON_STALE: “inbound event was dropped because it is stale”;


(責任編輯:那一抹憂傷)