Android Systrace 基礎知識(6) - Input 解讀

本文是 Android Systrace 系列文章的第六篇,主要是對 Systrace 中的 Input 進行簡單介紹,介紹其 Input 的流程; Systrace 中 Input 信息的體現 ,以及如何結合 Input 信息,分析與 Input 相關的問題

本系列的目的是通過 Systrace 這個工具,從另外一個角度來看待 Android 系統整體的運行,同時也從另外一個角度來對 Framework 進行學習。也許你看了很多講 Framework 的文章,但是總是記不住代碼,或者不清楚其運行的流程,也許從 Systrace 這個圖形化的角度,你可以理解的更深入一些。

系列文章目錄

  1. Systrace 簡介
  2. Systrace 基礎知識 - Systrace 預備知識
  3. Systrace 基礎知識 - Why 60 fps ?
  4. Systrace 基礎知識 - SystemServer 解讀
  5. Systrace 基礎知識 - SurfaceFlinger 解讀
  6. Systrace 基礎知識 - Input 解讀
  7. Systrace 基礎知識 - Vsync 解讀
  8. Systrace 基礎知識 - Vsync-App :基於 Choreographer 的渲染機制詳解
  9. Systrace 基礎知識 - MainThread 和 RenderThread 解讀
  10. Systrace 基礎知識 - Binder 和鎖競爭解讀
  11. Systrace 基礎知識 - Triple Buffer 解讀
  12. Systrace 基礎知識 - CPU Info 解讀

正文

Android 基於 Choreographer 的渲染機制詳解 這篇文章中,我有講到,Android App 的主線程運行的本質是靠 Message 驅動的,這個 Message 可以是循環動畫、可以是定時任務、可以是其他線程喚醒,不過我們最常見的還是 Input Message ,這裏的 Input 是以 InputReader 這裏的分類,不僅包含觸摸事件(Down、Up、Move) , 可包含 Key 事件(Home Key 、 Back Key) . 這裏我們着重講的是觸摸事件

由於 Android 系統在 Input 鏈上加了一些 Trace 點,且這些 Trace 點也比較完善,部分廠家可能會自己加一些,不過我們這裏以標準的 Trace 點來講解,這樣不至於你換了個手機抓的 Trace 就不一樣了

Input 在 Android 中的地位是很高的,我們在玩手機的時候,大部分應用的滑動、跳轉這些都依靠 Input 事件來驅動,後續我會專門寫一篇文章,來介紹 Android 中基於 Input 的運行機制。這裏是從 Systrace 的角度來看 Input 。看下面的流程之前,腦子裏先有個關於 Input 的大概處理流程,這樣看的時候,就可以代入:

  1. 觸摸屏每隔幾毫秒掃描一次,如果有觸摸事件,那麼把事件上報到對應的驅動
  2. InputReader 讀取觸摸事件交給 InputDispatcher 進行事件派發
  3. InputDispatcher 將觸摸事件發給註冊了 Input 事件的 App
  4. App 拿到事件之後,進行 Input 事件分發,如果此事件分發的過程中,App 的 UI 發生了變化,那麼會請求 Vsync,則進行一幀的繪製

另外在看 Systrace 的時候,要牢記 Systrace 中時間是從左到右流逝的,也就是說如果你在 Systrace 上畫一條豎直線,那麼豎直線左邊的事件永遠比右邊的事件先發生,這也是我們分析源碼流程的一個基石。我希望大家在看基於 Systrace 的源碼流程分析之後,腦子裏有一個圖形化的、立體的流程圖,你跟的代碼走到哪一步了在圖形你在腦中可以快速定位出來

Input in Systrace

下面這張圖是一個概覽圖,以滑動桌面爲例 (滑動桌面包括一個 Input_Down 事件 + 若干個 Input_Move 事件 + 一個 Input_Up 事件,這些事件和事件流都會在 Systrace 上有所體現,這也是我們分析 Systrace 的一個重要的切入點),主要牽扯到的模塊是 SystemServer 和 App 模塊,其中用藍色標識的是事件的流動信息,紅色的是輔助信息。

滑動桌面的事件流
滑動桌面的事件流

InputReaderInputDispatcher 是跑在 SystemServer 裏面的兩個 Native 線程,負責讀取和分發 Input 事件,我們分析 Systrace 的 Input 事件流,首先是找到這裏。下面針對上圖中標號進行簡單說明

  1. InputReader 負責從 EventHub 裏面把 Input 事件讀取出來,然後交給 InputDispatcher 進行事件分發
  2. InputDispatcher 在拿到 InputReader 獲取的事件之後,對事件進行包裝和分發 (也就是發給對應的)
  3. OutboundQueue 裏面放的是即將要被派發給對應 AppConnection 的事件
  4. WaitQueue 裏面記錄的是已經派發給 AppConnection 但是 App 還在處理沒有返回處理成功的事件
  5. PendingInputEventQueue 裏面記錄的是 App 需要處理的 Input 事件,這裏可以看到已經到了應用進程
  6. deliverInputEvent 標識 App UI Thread 被 Input 事件喚醒
  7. InputResponse 標識 Input 事件區域,這裏可以看到一個 Input_Down 事件 + 若干個 Input_Move 事件 + 一個 Input_Up 事件的處理階段都被算到了這裏
  8. App 響應 Input 事件 : 這裏是滑動然後鬆手,也就是我們熟悉的桌面滑動的操作,桌面隨着手指的滑動更新畫面,鬆手後觸發 Fling 繼續滑動,從 Systrace 就可以看到整個事件的流程

下面以第一個 Input_Down 事件的處理流程來進行詳細的工作流說明,其他的 Move 事件和 Up 事件的處理是一樣的(部分不一樣,不過影響不大)

InputDown 事件在 SystemServer 的工作流

放大 SystemServer 的部分,可以看到其工作流(藍色),滑動桌面包括 Input_Down + 若干個 Input_Move + Input_Up ,我們這裏看的是 Input_Down 這個事件

Input In SystemServer
Input In SystemServer

InputDown 事件在 App 的工作流

應用在收到 Input 事件後,有時候會馬上去處理 (沒有 Vsync 的情況下),有時候會等 Vsync 信號來了之後去處理,這裏 Input_Down 事件就是直接去喚醒主線程做處理,其 Systrace 比較簡單,最上面有個 Input 事件隊列,主線程則是簡單的處理

Input In App
Input In App

App 的 Pending 隊列

Pending Queue
Pending Queue

主線程處理 Input 事件

主線程處理 Input 事件這個大家比較熟悉,從下面的調用棧可以看到,Input 事件傳到了 ViewRootImpl,最終到了 DecorView ,然後就是大家熟悉的 Input 事件分發機制

Input Trace
Input Trace

關鍵知識點和流程

從上面的 Systrace 來看,Input 事件的基本流向如下:

  1. InputReader 讀取 Input 事件
  2. InputReader 將讀取的 Input 事件放到 InboundQueue 中
  3. InputDispatcher 從 InboundQueue 中取出 Input 事件派發到各個 App(連接) 的 OutBoundQueue
  4. 同時將事件記錄到各個 App(連接) 的 WaitQueue
  5. App 接收到 Input 事件,同時記錄到 PendingInputEventQueue ,然後對事件進行分發處理
  6. App 處理完成後,回調 InputManagerService 將負責監聽的 WaitQueue 中對應的 Input 移除

通過上面的流程,一次 Input 事件就被消耗掉了(當然這只是正常情況,還有很多異常情況、細節處理,這裏就不細說了,自己看相關流程的時候可以深挖一下) , 那麼本節就從上面的關鍵流中取幾個重要的知識點講解(部分流程和圖參考和拷貝了 Gityuan 的博客的圖,鏈接在最下面參考那一節)

InputReader

InputReader 是一個 Native 線程,跑在 SystemServer 進程裏面,其核心功能是從 EventHub 讀取事件、進行加工、將加工好的事件發送到 InputDispatcher

InputReader Loop 流程如下

  1. getEvents:通過 EventHub (監聽目錄 /dev/input )讀取事件放入 mEventBuffer ,而mEventBuffer 是一個大小爲256的數組, 再將事件 input_event 轉換爲 RawEvent
  2. processEventsLocked: 對事件進行加工, 轉換 RawEvent -> NotifyKeyArgs(NotifyArgs)
  3. QueuedListener->flush:將事件發送到 InputDispatcher 線程, 轉換 NotifyKeyArgs -> KeyEntry(EventEntry)

核心代碼 loopOnce 處理流程如下:

loopOnce 處理流程,圖片來自 Gityuan 博客
loopOnce 處理流程,圖片來自 Gityuan 博客

InputReader 核心 Loop 函數 loopOnce 邏輯如下

void InputReader::loopOnce() {
    int32_t oldGeneration;
    int32_t timeoutMillis;
    bool inputDevicesChanged = false;
    std::vector<InputDeviceInfo> inputDevices;
    { // acquire lock
    ......
    //獲取輸入事件、設備增刪事件,count 爲事件數量
    size_t count = mEventHub ->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
    {
    ......
        if (count) {//處理事件
            processEventsLocked(mEventBuffer, count);
        }

    }
    ......
    mQueuedListener->flush();//將事件傳到 InputDispatcher,這裏getListener 得到的就是 InputDispatcher
}

InputDispatcher

上面的 InputReader 調用 mQueuedListener->flush 之後 ,將 Input 事件加入到InputDispatcher 的 mInboundQueue ,然後喚醒 InputDispatcher , 從 Systrace 的喚醒信息那裏也可以看到 InputDispatch 線程是被 InputReader 喚醒的

InputDispatcher
InputDispatcher

InputDispatcher 的核心邏輯如下:

  1. dispatchOnceInnerLocked(): 從 InputDispatcher 的 mInboundQueue 隊列,取出事件 EventEntry。另外該方法開始執行的時間點 (currentTime) 便是後續事件 dispatchEntry 的分發時間 (deliveryTime)
  2. dispatchKeyLocked():滿足一定條件時會添加命令 doInterceptKeyBeforeDispatchingLockedInterruptible;
  3. enqueueDispatchEntryLocked():生成事件 DispatchEntry 並加入 connection 的 outbound 隊列
  4. startDispatchCycleLocked():從 outboundQueue 中取出事件 DispatchEntry, 重新放入 connection 的 waitQueue 隊列;
  5. InputChannel.sendMessage 通過 socket 方式將消息發送給遠程進程;
  6. runCommandsLockedInterruptible():通過循環遍歷地方式,依次處理 mCommandQueue 隊列中的所有命令。而 mCommandQueue 隊列中的命令是通過 postCommandLocked() 方式向該隊列添加的。
Input Dispatcher 流程, 圖片來自 Gityuan 博客
Input Dispatcher 流程, 圖片來自 Gityuan 博客

其核心處理邏輯在 dispatchOnceInnerLocked 這裏

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

        // Poke user activity for this event.
        if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {
            pokeUserActivityLocked(mPendingEvent);
        }

        // Get ready to dispatch the event.
        resetANRTimeoutsLocked();
    }
    case EventEntry::TYPE_MOTION: {
        done = dispatchMotionLocked(currentTime, typedEntry,
                &dropReason, nextWakeupTime);
        break;
    }

    if (done) {
        if (dropReason != DROP_REASON_NOT_DROPPED) {
            dropInboundEventLocked(mPendingEvent, dropReason);
        }
        mLastDropReason = dropReason;
        releasePendingEventLocked();
        *nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately
    }
}

InboundQueue

InputDispatcher 執行 notifyKey 的時候,會將 Input 事件封裝後放到 InboundQueue 中,後續 InputDispatcher 循環處理 Input 事件的時候,就是從 InboundQueue 取出事件然後做處理

InboundQueue
InboundQueue

OutboundQueue

Outbound 意思是出站,這裏的 OutboundQueue 指的是要被 App 拿去處理的事件隊列,每一個 App(Connection) 都對應有一個 OutboundQueue ,從 InboundQueue 那一節的圖來看,事件會先進入 InboundQueue ,然後被 InputDIspatcher 派發到各個 App 的 OutboundQueue

OutboundQueue
OutboundQueue

WaitQueue

當 InputDispatcher 將 Input 事件分發出去之後,將 DispatchEntry 從 outboundQueue 中取出來放到 WaitQueue 中,當 publish 出去的事件被處理完成(finished),InputManagerService 就會從應用中得到一個回覆,此時就會取出 WaitQueue 中的事件,從 Systrace 中看就是對應 App 的 WaitQueue 減少

如果主線程發生卡頓,那麼 Input 事件沒有及時被消耗,也會在 WaitQueue 這裏體現出來,如下圖:

WaitQueue
WaitQueue

整體邏輯

圖來自 Gityuan 博客

整體邏輯
整體邏輯

Input 刷新與 Vsync

Input 的刷新取決於觸摸屏的採樣,目前比較多的屏幕採樣率是 120Hz 和 160Hz (不過隨着目前屏幕刷新率的提升, Input 的採樣率已經到了 180Hz 或者 240Hz) ,對應就是 8ms 採樣一次或者 6.25ms 採樣一次,我們來看一下其在 Systrace 上的展示

Input 與 Vsync
Input 與 Vsync

可以看到上圖中, InputReader 每隔 6.25ms 就可以讀上來一個數據,交給 InputDispatcher 去分發給 App ,那麼是不是屏幕採樣率越高越好呢?也不一定,比如上面那張圖,雖然 InputReader 每隔 6.25ms 就可以讀上來一個數據給 InputDispatcher 去分發給 App ,但是從 WaitQueue 的表現來看,應用並沒有消耗這個 Input 事件,這是爲什麼呢?

原因在於應用消耗 Input 事件的時機是 Vsync 信號來了之後,刷新率爲 60Hz 的屏幕,一般系統也是 60 fps ,也就是說兩個 Vsync 的間隔在 16.6ms ,這期間如果有兩個或者三個 Input 事件,那麼必然有一個或者兩個要被拋棄掉,只拿最新的那個。也就是說:

  1. 在屏幕刷新率和系統 FPS 都是 60 的時候,盲目提高觸摸屏的採樣率,是沒有太大的效果的,反而有可能出現上面圖中那樣,有的 Vsync 週期中有兩個 Input 事件,而有的 Vsync 週期中有三個 Input 事件,這樣造成事件不均勻,可能會使 UI 產生抖動
  2. 在屏幕刷新率和系統 FPS 都是 60 的時候,使用 120Hz 採樣率的觸摸屏就可以了
  3. 如果在屏幕刷新率和系統 FPS 都是 90 的時候 ,那麼 120Hz 採樣率的觸摸屏顯然不夠用了,這時候應該採用 180Hz 採樣率的屏幕

Input 調試信息

Dumpsys Input 主要是 Debug 用,我們也可以來看一下其中的一些關鍵信息,到時候遇到了問題也可以從這裏面找 , 其命令如下:

adb shell dumpsys input

其中的輸出比較多,我們終點截取 Device 信息、InputReader、InputDispatcher 三段來看就可以了

Device 信息

主要是目前連接上的 Device 信息,下面摘取的是 touch 相關的

    3: main_touch
      Classes: 0x00000015
      Path: /dev/input/event6
      Enabled: true
      Descriptor: 4055b8a032ccf50ef66dbe2ff99f3b2474e9eab5
      Location: main_touch/input0
      ControllerNumber: 0
      UniqueId: 
      Identifier: bus=0x0000, vendor=0xbeef, product=0xdead, version=0x28bb
      KeyLayoutFile: /system/usr/keylayout/main_touch.kl
      KeyCharacterMapFile: /system/usr/keychars/Generic.kcm
      ConfigurationFile: 
      HaveKeyboardLayoutOverlay: false

Input Reader 狀態

InputReader 這裏就是當前 Input 事件的一些展示

  Device 3: main_touch
    Generation: 24
    IsExternal: false
    HasMic:     false
    Sources: 0x00005103
    KeyboardType: 1
    Motion Ranges:
      X: source=0x00005002, min=0.000, max=1079.000, flat=0.000, fuzz=0.000, resolution=0.000
      Y: source=0x00005002, min=0.000, max=2231.000, flat=0.000, fuzz=0.000, resolution=0.000
      PRESSURE: source=0x00005002, min=0.000, max=1.000, flat=0.000, fuzz=0.000, resolution=0.000
      SIZE: source=0x00005002, min=0.000, max=1.000, flat=0.000, fuzz=0.000, resolution=0.000
      TOUCH_MAJOR: source=0x00005002, min=0.000, max=2479.561, flat=0.000, fuzz=0.000, resolution=0.000
      TOUCH_MINOR: source=0x00005002, min=0.000, max=2479.561, flat=0.000, fuzz=0.000, resolution=0.000
      TOOL_MAJOR: source=0x00005002, min=0.000, max=2479.561, flat=0.000, fuzz=0.000, resolution=0.000
      TOOL_MINOR: source=0x00005002, min=0.000, max=2479.561, flat=0.000, fuzz=0.000, resolution=0.000
    Keyboard Input Mapper:
      Parameters:
        HasAssociatedDisplay: false
        OrientationAware: false
        HandlesKeyRepeat: false
      KeyboardType: 1
      Orientation: 0
      KeyDowns: 0 keys currently down
      MetaState: 0x0
      DownTime: 521271703875000
    Touch Input Mapper (mode - direct):
      Parameters:
        GestureMode: multi-touch
        DeviceType: touchScreen
        AssociatedDisplay: hasAssociatedDisplay=true, isExternal=false, displayId=''
        OrientationAware: true
      Raw Touch Axes:
        X: min=0, max=1080, flat=0, fuzz=0, resolution=0
        Y: min=0, max=2232, flat=0, fuzz=0, resolution=0
        Pressure: min=0, max=127, flat=0, fuzz=0, resolution=0
        TouchMajor: min=0, max=512, flat=0, fuzz=0, resolution=0
        TouchMinor: unknown range
        ToolMajor: unknown range
        ToolMinor: unknown range
        Orientation: unknown range
        Distance: unknown range
        TiltX: unknown range
        TiltY: unknown range
        TrackingId: min=0, max=65535, flat=0, fuzz=0, resolution=0
        Slot: min=0, max=20, flat=0, fuzz=0, resolution=0
      Calibration:
        touch.size.calibration: geometric
        touch.pressure.calibration: physical
        touch.orientation.calibration: none
        touch.distance.calibration: none
        touch.coverage.calibration: none
      Affine Transformation:
        X scale: 1.000
        X ymix: 0.000
        X offset: 0.000
        Y xmix: 0.000
        Y scale: 1.000
        Y offset: 0.000
      Viewport: displayId=0, orientation=0, logicalFrame=[0, 0, 1080, 2232], physicalFrame=[0, 0, 1080, 2232], deviceSize=[1080, 2232]
      SurfaceWidth: 1080px
      SurfaceHeight: 2232px
      SurfaceLeft: 0
      SurfaceTop: 0
      PhysicalWidth: 1080px
      PhysicalHeight: 2232px
      PhysicalLeft: 0
      PhysicalTop: 0
      SurfaceOrientation: 0
      Translation and Scaling Factors:
        XTranslate: 0.000
        YTranslate: 0.000
        XScale: 0.999
        YScale: 1.000
        XPrecision: 1.001
        YPrecision: 1.000
        GeometricScale: 0.999
        PressureScale: 0.008
        SizeScale: 0.002
        OrientationScale: 0.000
        DistanceScale: 0.000
        HaveTilt: false
        TiltXCenter: 0.000
        TiltXScale: 0.000
        TiltYCenter: 0.000
        TiltYScale: 0.000
      Last Raw Button State: 0x00000000
      Last Raw Touch: pointerCount=1
        [0]: id=0, x=660, y=1338, pressure=44, touchMajor=44, touchMinor=44, toolMajor=0, toolMinor=0, orientation=0, tiltX=0, tiltY=0, distance=0, toolType=1, isHovering=false
      Last Cooked Button State: 0x00000000
      Last Cooked Touch: pointerCount=1
        [0]: id=0, x=659.389, y=1337.401, pressure=0.346, touchMajor=43.970, touchMinor=43.970, toolMajor=43.970, toolMinor=43.970, orientation=0.000, tilt=0.000, distance=0.000, toolType=1, isHovering=false
      Stylus Fusion:
        ExternalStylusConnected: false
        External Stylus ID: -1
        External Stylus Data Timeout: 9223372036854775807
      External Stylus State:
        When: 9223372036854775807
        Pressure: 0.000000
        Button State: 0x00000000
        Tool Type: 0

InputDispatcher 狀態

InputDispatch 這裏的重要信息主要包括

  1. FocusedApplication :當前獲取焦點的應用
  2. FocusedWindow : 當前獲取焦點的窗口
  3. TouchStatesByDisplay
  4. Windows :所有的 Window
  5. MonitoringChannels :Window 對應的 Channel
  6. Connections :所有的連接
  7. AppSwitch: not pending
  8. Configuration
Input Dispatcher State:
  DispatchEnabled: 1
  DispatchFrozen: 0
  FocusedApplication: name='AppWindowToken{ac6ec28 token=Token{a38a4b ActivityRecord{7230f1a u0 com.meizu.flyme.launcher/.Launcher t13}}}', dispatchingTimeout=5000.000ms
  FocusedWindow: name='Window{3c007ad u0 com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher}'
  TouchStatesByDisplay:
    0: down=true, split=true, deviceId=3, source=0x00005002
      Windows:
        0: name='Window{3c007ad u0 com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher}', pointerIds=0x80000000, targetFlags=0x105
        1: name='Window{8cb8f7 u0 com.android.systemui.ImageWallpaper}', pointerIds=0x0, targetFlags=0x4102
  Windows:
    2: name='Window{ba2fc6b u0 NavigationBar}', displayId=0, paused=false, hasFocus=false, hasWallpaper=false, visible=true, canReceiveKeys=false, flags=0x21840068, type=0x000007e3, layer=0, frame=[0,2136][1080,2232], scale=1.000000, touchableRegion=[0,2136][1080,2232], inputFeatures=0x00000000, ownerPid=26514, ownerUid=10033, dispatchingTimeout=5000.000ms
    3: name='Window{72b7776 u0 StatusBar}', displayId=0, paused=false, hasFocus=false, hasWallpaper=false, visible=true, canReceiveKeys=false, flags=0x81840048, type=0x000007d0, layer=0, frame=[0,0][1080,84], scale=1.000000, touchableRegion=[0,0][1080,84], inputFeatures=0x00000000, ownerPid=26514, ownerUid=10033, dispatchingTimeout=5000.000ms
    9: name='Window{3c007ad u0 com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher}', displayId=0, paused=false, hasFocus=true, hasWallpaper=true, visible=true, canReceiveKeys=true, flags=0x81910120, type=0x00000001, layer=0, frame=[0,0][1080,2232], scale=1.000000, touchableRegion=[0,0][1080,2232], inputFeatures=0x00000000, ownerPid=27619, ownerUid=10021, dispatchingTimeout=5000.000ms
  MonitoringChannels:
    0: 'WindowManager (server)'
  RecentQueue: length=10
    MotionEvent(deviceId=3, source=0x00005002, action=MOVE, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (524.5, 1306.4)]), policyFlags=0x62000000, age=61.2ms
    MotionEvent(deviceId=3, source=0x00005002, action=MOVE, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (543.5, 1309.4)]), policyFlags=0x62000000, age=54.7ms
  PendingEvent: <none>
  InboundQueue: <empty>
  ReplacedKeys: <empty>
  Connections:
    0: channelName='WindowManager (server)', windowName='monitor', status=NORMAL, monitor=true, inputPublisherBlocked=false
      OutboundQueue: <empty>
      WaitQueue: <empty>
    5: channelName='72b7776 StatusBar (server)', windowName='Window{72b7776 u0 StatusBar}', status=NORMAL, monitor=false, inputPublisherBlocked=false
      OutboundQueue: <empty>
      WaitQueue: <empty>
    6: channelName='ba2fc6b NavigationBar (server)', windowName='Window{ba2fc6b u0 NavigationBar}', status=NORMAL, monitor=false, inputPublisherBlocked=false
      OutboundQueue: <empty>
      WaitQueue: <empty>
    12: channelName='3c007ad com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher (server)', windowName='Window{3c007ad u0 com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher}', status=NORMAL, monitor=false, inputPublisherBlocked=false
      OutboundQueue: <empty>
      WaitQueue: length=3
        MotionEvent(deviceId=3, source=0x00005002, action=MOVE, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (634.4, 1329.4)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=2, age=17.4ms, wait=16.8ms
        MotionEvent(deviceId=3, source=0x00005002, action=MOVE, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (647.4, 1333.4)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=2, age=11.1ms, wait=10.4ms
        MotionEvent(deviceId=3, source=0x00005002, action=MOVE, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (659.4, 1337.4)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=2, age=5.2ms, wait=4.6ms
  AppSwitch: not pending
  Configuration:
    KeyRepeatDelay: 50.0ms
    KeyRepeatTimeout: 500.0ms

參考

本文部分圖文參考和拷貝自下面幾篇文章,同時下面幾篇文章講解了 Input 流程的細節部分,推薦大家在看完這篇文章後,如果對代碼細節感興趣,可以仔細研讀下面這幾篇非常棒的文章。

  1. http://gityuan.com/2016/12/11/input-reader/
  2. http://gityuan.com/2016/12/10/input-manager/
  3. http://gityuan.com/2016/12/17/input-dispatcher/
  4. https://zhuanlan.zhihu.com/p/29386642

附件

本文涉及到的附件也上傳了,各位下載後解壓,使用 Chrome 瀏覽器打開即可 點此鏈接下載文章所涉及到的 Systrace 附件

本文其他地址

由於博客留言交流不方便,點贊或者交流,可以移步本文的知乎或者掘金頁面

掘金 - Systrace 基礎知識 - Input 解讀

關於我 && 博客

  1. 關於我 , 非常希望和大家一起交流 , 共同進步 .
  2. 博客內容導航
  3. 優秀博客文章記錄 - Android 性能優化必知必會

一個人可以走的更快 , 一羣人可以走的更遠

本文使用 mdnice 排版

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章