1. 程式人生 > >Android ANR 問題第二彈------Input事件是如何超時導致ANR的

Android ANR 問題第二彈------Input事件是如何超時導致ANR的

在Android ANR 問題第一彈中,我們介紹Android ANR問題分為三類:Input,Receiver,Service。我們現在就先來詳細的介紹Input事件超時是如何導致ANR問題發生的,我們只有從原理上去了解Input超時的本質,才能更好的分析解決實際開發中遇到的問題。本文會以Android8.1的程式碼為準,來簡要分析Input事件超時。

在分析Input超時之前,我們先來簡單的介紹一下Android的Input系統。Android Input體系中,大致有兩種型別的事件,實體按鍵key事件,螢幕點選觸控事件,當然如果根據事件型別的不同我們還能細分為基礎實體按鍵的key(power,volume up/down,recents,back,home),實體鍵盤按鍵,螢幕點選(多點,單點),螢幕滑動等等的事件。在Android整個Input體系中有三個格外重要的成員:Eventhub,InputReader,InputDispatcher。它們分別擔負著各自不同的職責,Eventhub負責監聽/dev/input產生Input事件,InputReader負責從Eventhub讀取事件,並將讀取的事件發給InputDispatcher,InputDispatcher則根據實際的需要具體分發給當前手機獲得焦點實際的Window。當然它們三者之間有工作遠比我介紹的要複雜的很多。

好了當我們知道什麼是Input的時候,我們現在就開始分析Input是如何超時導致ANR的。我們常說Input超時,都是指的是Input事件分發超時,因此整個超時計算以及觸發都在InputDispatcher這個類中。其程式碼路徑如下:/frameworks/native/services/inputflinger/InputDispatcher.cpp,Input分發事件的時候就是不斷執行InputDispatcher的threadLoop來讀取Input事件,並呼叫dispatchOnce進行分發事件的。當然如果沒有Input事件的時候,他會執行mLooper->pollOnce,進入等待狀態。這個就和Android應用UI主執行緒的Looper一樣,MessageQueue裡面沒有訊息的時候,等待於nativePollOnce方法,其實最終還是呼叫Looper->pollOnce進入等待狀態。

bool InputDispatcherThread::threadLoop() {
    mDispatcher->dispatchOnce();
    return true;
}

我們知道InputDispatcher會通過dispatchOnce不斷的讀取並分發Input事件,因此我直接來看InputDispatcher::dispatchOnceInnerLocked該方法,其中程式碼並非整個程式碼,我這邊只列取關鍵程式碼進行分析,而且整個分析過程主要以按鍵事件為主要分析物件。

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    nsecs_t currentTime = now();//記錄事件分發的第一時間點,很重要,此引數會不斷在接下來的方法中作為引數進行傳遞。
    // Ready to start a new event.
    // If we don't already have a pending event, go grab one.
    if (! mPendingEvent) { //只有但前mPendingEvent(正在分發的事件)為空的時候才進入
        //從註釋中可以看出這裡就是獲取一個Input事件,並且重置ANR時間計算的相關引數
        // Get ready to dispatch the event.
        resetANRTimeoutsLocked();
    }
    switch (mPendingEvent->type) {
    case EventEntry::TYPE_KEY: {
        KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
        //找到Input事件,讓我們發起來
        done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
        break;
    }
    }
    if (done) {
        if (dropReason != DROP_REASON_NOT_DROPPED) {
            dropInboundEventLocked(mPendingEvent, dropReason);//這裡稍微提一下,一般打出的一些drop***event的log都是從這裡輸出的
        }
    }
}

來看下resetANRTimeoutsLocked方法

void InputDispatcher::resetANRTimeoutsLocked() {
    // Reset input target wait timeout.
    mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE;
    mInputTargetWaitApplicationHandle.clear();
}

繼續事件分發dispatchKeyLocked,gogogo

bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
        DropReason* dropReason, nsecs_t* nextWakeupTime) {
    // Identify targets.
    Vector<InputTarget> inputTargets;
    int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,
            entry, inputTargets, nextWakeupTime); file descriptors from//這邊會找到當前有焦點的視窗window,並根據條件觸發ANR

    addMonitoringTargetsLocked(inputTargets);

    // Dispatch the key.
    dispatchEventLocked(currentTime, entry, inputTargets);//繼續執行事件分發流程
    return true;
}

重點分析findFocusedWindowTargetsLocked該方法

int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,
        const EventEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime) {
    int32_t injectionResult;
    String8 reason;
    // If there is no currently focused window and no focused application
    // then drop the event.
    if (mFocusedWindowHandle == NULL) {
        if (mFocusedApplicationHandle != NULL) {
            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;
        }//看到這裡,有沒有一絲的驚喜,是不是發現monkey test的時候經常遇到類似log的ANR?典型的無視窗,有應用的ANR問題,這裡我們就需要了解Android應用的啟動流程了(後續準備寫一篇Android應用啟動流程詳細分析的文章),一般此類問題都是Android應用首次啟動時會發生此類問題,此時我們應用本身需要檢查一下我們的Android應用重寫的Application onCreate方法,Android應用的啟動介面是否在onCreate onStart方法中是否存在耗時操作。當然不排除系統原因造成的啟動慢,直接導致ANR問題發生的情況

        ALOGI("Dropping event because there is no focused window or focused application.");
        injectionResult = INPUT_EVENT_INJECTION_FAILED;
        goto Failed;
    }


    // Check whether the window is ready for more input.//這裡將會進入更為詳細更多種類的ANR觸發過程
    reason = checkWindowReadyForMoreInputLocked(currentTime,
            mFocusedWindowHandle, entry, "focused");
    if (!reason.isEmpty()) {//一旦checkWindowReadyForMoreInputLocked返回不為空,怎說明存在應用ANR
        injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
                mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime, reason.string());
        goto Unresponsive;
    }

    // Success!  Output targets.
    injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
    addWindowTargetLocked(mFocusedWindowHandle,
            InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS, BitSet32(0),
            inputTargets);

    // Done.
Failed:
Unresponsive:
    nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime);
    updateDispatchStatisticsLocked(currentTime, entry,
            injectionResult, timeSpentWaitingForApplication);
#if DEBUG_FOCUS
    ALOGD("findFocusedWindow finished: injectionResult=%d, "
            "timeSpentWaitingForApplication=%0.1fms",
            injectionResult, timeSpentWaitingForApplication / 1000000.0);
#endif
    return injectionResult;
}

各種ANR種類判斷checkWindowReadyForMoreInputLocked,這裡就不去進行詳細的分析了,畢竟原始碼的註釋很瞭然了。

String8 InputDispatcher::checkWindowReadyForMoreInputLocked(nsecs_t currentTime,
        const sp<InputWindowHandle>& windowHandle, const EventEntry* eventEntry,
        const char* targetType) {
    // If the window is paused then keep waiting.
    if (windowHandle->getInfo()->paused) {
        return String8::format("Waiting because the %s window is paused.", targetType);
    }

    // If the window's connection is not registered then keep waiting.
    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);
    }

    // If the connection is dead then keep waiting.
    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 the connection is backed up then keep waiting.
    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());
    }

    // 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.
        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 {
        // 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 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();
}

根據各種reason,判斷是否已經超時,觸發ANR

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) {//無應用,無視窗,進入一次,繼續等待應用,不觸發ANR
        if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY) {
#if DEBUG_FOCUS
            ALOGD("Waiting for system to become ready for input.  Reason: %s", reason);
#endif
            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) {
#if DEBUG_FOCUS//這裡一般是有應用(application已經建立),無視窗,或者有應用,有視窗ANR的情形,一般同一個視窗至進入一次該方法
            ALOGD("Waiting for application to become ready for input: %s.  Reason: %s",
                    getApplicationWindowLabelLocked(applicationHandle, windowHandle).string(),
                    reason);
#endif
            nsecs_t timeout;
            if (windowHandle != NULL) {
                timeout = windowHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);//5s超時
            } else if (applicationHandle != NULL) {
                timeout = applicationHandle->getDispatchingTimeout(
                        DEFAULT_INPUT_DISPATCHING_TIMEOUT);//5s超時
            } 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;
    }

    if (currentTime >= mInputTargetWaitTimeoutTime) {//當前時間已經大於超時時間,說明應用有時間分發超時了,需要觸發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 {
        // Force poll loop to wake up when timeout is due.
        if (mInputTargetWaitTimeoutTime < *nextWakeupTime) {
            *nextWakeupTime = mInputTargetWaitTimeoutTime;
        }
        return INPUT_EVENT_INJECTION_PENDING;
    }
}

我們來理一理該方法:

  • 當有事件第一次分發的時候,我們需要注意mFocusedWindowHandle和mFocusedApplicationHandle,暫不考慮無應用,無視窗的情況,這兩個引數都是通過WMS在應用啟動addWindow或者有Window切換的時候,通過JNI設定到InputDispatcher中的,所以我們在分發事件的時候,只會記錄Input事件第一次分發時的時間點,並設定該事件超時的相關引數。
  • 當InputDispatcher再次執行dispatchOnceInnerLocked的時候,發現當前的mPendingEvent不為空,所以不會重置ANR相關的timeout引數,因此只會不停的判斷當前的時間是否大於mInputTargetWaitTimeoutTime,如果大於則觸發ANR。
  • 什麼時候會重置ANR相關的timeout引數呢?分發到新的Input事件時(重置),也就是mpendingevent處理完(重置),又有新的Input事件產生的時候,焦點應用更新的時候,InputDispatcher自身重置的時候。
  • 當Input事件分發超時導致ANR時,真正的ANR發生的第一時間所以應該是InputDispatcherLog打出的時間點,當呼叫onANRLocked層層呼叫最終觸發appNotResponding列印event log ,ActivityManager anr log,記錄trace,因此我們說event log ,ActivityManager anr log,trace具有參考性,並不絕對,並無道理。

到此我們也應該對Input事件導致的ANR問題有一個基本的瞭解了,我們也能更快更準的定位ANR問題的發生的原因。當然,並不是大家看完本篇文章,就能立馬很好的分析ANR問題了,前提是我們自身還是要有充足的知識儲備,我們都在學習的路上,還是一句話,不為繁華異匠心,與君共勉之。

相關推薦

Android ANR 問題第二------Input事件是如何超時導致ANR

在Android ANR 問題第一彈中,我們介紹Android ANR問題分為三類:Input,Receiver,Service。我們現在就先來詳細的介紹Input事件超時是如何導致ANR問題發生的,我們只有從原理上去了解Input超時的本質,才能更好的分析解決實際開發中遇到

Android ANR 問題第二------Input超時實戰問題解析上

在前面的Android ANR 問題第二彈一文中,我們簡訴了Android Input超時的原因,我們瞭解到系統Input系統分發Input的事件時如果有5s超時會觸發應用ANR。在實際開發測試中,我們也會經常遇到Input超時導致的ANR問題,那麼現在我們就以實際問題分析一

DOM事件第二(UIEvent事件

復合 綁定 階段 posit 5.1 ble pat fun 出錯 此文章主要總結UIEvent相關的事件,如有不對的地方,歡迎指正。 一、uitls.js(綁定事件公共類) var fixs = { ‘focusin‘: { stand

第二第二 iframe內嵌框架 和from表單和input的運用

tex class AC -s AI bsp box 內嵌 align 先是iframe框架 接下來是代碼的運用 <html> <head> <title>IFRAME</title> <meta charset

Layui 關於在使用Layui出層的時候,兩個視窗時再使用第二窗的時候導致視窗不能點選

問題如標題,原因是因為我做的新增和修改是在同一個視窗中,但是由於我在第一層的視窗中的一個按鈕需要呼叫開啟第二層視窗,所以就導致在程式碼上一個按鈕綁定了兩次click時間,在第一次使用過後,再去使用的時候會導致第一次的事件也會執行,頁面上會多彈出一個視窗導致頁面無法點選,解決方法就是在你彈出第二層視窗

[Android]通過adb shell input上報命令模擬螢幕點選事件

常用的 input上報命令: input text 1234 實際向介面注入1234文字,有輸入框,能明顯看到效果 input keyevent 4 鍵盤事件,4 為返回 input tap 100 300 單擊觸屏事件 ,模擬點選x=100 y = 30

uinput 用法 android 上層使用uinput 的用法來模擬 input 事件

android 上層使用uinput 的用法來模擬 input 事件#ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include <stdio.h> #include <stdlib.h> #inc

Android:Consumer closed input channel or an error occurred. events=0x8異常導致程式異常終止問題解決方案

程式異常終止但沒有任何異常資訊,只捕獲到Log裡面下面的有用資訊: 09-19 21:57:02.222: W/InputDispatcher(153): channel '410c5958 com.nju.ecg/com.nju.ecg.wave.WaveScreen

Android WebView載入帶有Input的輸入框時點選無法出軟鍵盤的問題解決

方案一:http://www.2cto.com/kf/201412/359293.html 方案二:mWebView.requestFocus(View.FOCUS_DOWN) ;或者this.mWe

android Dialog 底部

param sid tde 獲取 半透明 overlay desc .get owa 、if (dialShareDialog == null) { dialShareDialog = new Dialog(context, R.style.dialog);

Android - 隱藏EditText出的軟鍵盤輸入(SoftInput)

resize visible nbsp ont mil windows andro car src 隱藏EditText彈出的軟鍵盤輸入(SoftInput)本文地址: http://blog.csdn.net/caroline_wendy保持界面的整潔, 能夠選擇在進入

android入門 — ListView點擊事件

.com 方法 itemclick text his class 處理 分享 str listView中提供了兩種點擊事件的處理方法,分別是OnItemClick和OnItemLongClick。 OnItemClick提供的是點擊操作的處理,OnItemLongClic

pythion 第二

統計 mov 第二彈 false 轉變 插入 part 查找字符 可變 ################################第二節################################################python中數據類型的常見的方法

『PyTorch』第二_張量

ref play spl rip 出錯 margin logs 互轉 演示 參考:http://www.jianshu.com/p/5ae644748f21# 幾個數學概念: 標量(Scalar)是只有大小,沒有方向的量,如1,2,3等 向量(Vector)是有大小和方向的

Linux常用命令及操作(第二

linux home .gz 紅旗 關閉 linu tty 歸檔文件 過程 Ctrl l清屏 Ctrl d關閉終端 Ctrl Alt T打開終端 pwd 查看當前的目錄 Shift Ctrl C復制 Shift Ctrl V粘貼 Shift Ctrl N打開新的終端 F1

Python學習之路——第二(認識python)

內容 代碼結構 計算 戰術 個人 方法 十分 現在 目的   第一彈中我是說明了學習python的目的,主要為了自我提升的考慮,那麽為什麽我對python感興趣,python有什麽用了?本章就簡單說明下。   python的用途很廣,而且代碼十分簡潔,不像java、c等其他

android學習-第二講(修改項目名稱和圖標,log,過濾器)

pre activit png activity 圖標 alt 類名 mage ani 一、在app/src/main/res下有 AndroidManifest.xml打開,打開後如下圖1 二、日誌工具log log.v() log.d() log.i() l

[Android] 開發第二

知識 blog tex 運行 manifest clas col 帶來 ive 使用 Android-Studio 時,如果對界面不習慣,可以在 http://color-themes.com/ 網站裏找喜歡的主題免費下載後使用 Android-Studio 的菜單 F

Android 輸入管理服務-輸入事件到達之後的處理流程

content 例如 enter 技術 fontsize tail 流程 ref ora 接上一篇博客“Android 輸入管理服務啟動過程的流程”。這兩天分析了Android 輸入管理服務接收到輸入事件之後的處理流程,詳細流程例如以下面兩圖所看到的:

mysql數據庫第二

這也 arc 範圍 例如 reat .... 重建 跳過 數據 mysql數據庫針對表的操作 表記錄的增刪改查 1.增加一張表 插入記錄之前必須得先有表結構! CREATE TABLE score( id int PRIMARY KEY auto_increment,