iT邦幫忙

2025 iThome 鐵人賽

DAY 16
0

Input Dispatch 與 UI Thread 機制

前言

在 Android 的使用者體驗中,螢幕觸控與鍵盤輸入是最直接的互動方式。當我們在畫面上點擊 Button、滑動 RecyclerView,或是輸入文字,這些行為都會觸發 Input 事件(如 TouchEvent、KeyEvent)。
但這些事件如何從底層硬體驅動一路傳遞到應用程式的 UI Thread?又是如何確保應用不會「卡住」?
這牽涉到兩個重要的概念:

  • Input Dispatch(輸入事件分發)
  • UI Thread 機制(主執行緒的事件迴圈與處理模型)

系統整體架構

  1. Input 系統三大核心元件
  • InputReader
    • 負責從硬體驅動讀取原始事件(例如座標、按鍵掃描碼),並轉換成 Android 可以理解的格式。
  • InputDispatcher
    • 負責將事件派發到合適的目標 Window,例如某個 Activity 的視窗。
  • WindowManagerService (WMS)
    • 決定哪個 Window 應該接收事件(例如前景 Activity、Dialog、系統欄位)。
  1. 與應用端的關聯
  • ViewRootImpl:接收來自 WMS 的事件,並交給 DecorView 開始分發。
  • UI Thread:透過 Looper + MessageQueue 接收事件,並呼叫對應的 View 回調(如 onTouchEvent()、onKeyDown())。

Input Dispatch 流程

以下用一個 "click Button" 的例子來說明事件流程:
https://ithelp.ithome.com.tw/upload/images/20250930/20178907eEU887GUo8.png

  1. InputReader
  • 從 EventHub(與 Linux kernel input subsystem 交互)讀取事件。
  • 將硬體輸入轉換成 Android 的 InputEvent(TouchEvent、KeyEvent)。
  1. InputDispatcher
  • 根據 Window Focus 判斷應該把事件送到哪個應用。
  • 可能會遇到多個 Window 疊加的情境,例如:
    • 系統 Dialog 在前景
    • Activity 在後方
    • InputDispatcher 確保事件送到 最有資格的 Window。
  1. WindowManagerService
  • 保持所有 Window 的 Z-order 與焦點狀態。
  • 提供 InputDispatcher 「哪個 Window 有輸入焦點」的判斷依據。
  1. ViewRootImpl
  • 每個 Window 對應一個 ViewRootImpl。
  • 負責將 WMS 傳來的事件放入應用的 MessageQueue。
  • 在 UI Thread 取出後,呼叫 DecorView 的 dispatchInputEvent()。
  1. View 與事件分發
  • DecorView 作為 View Tree 的 root,開始遞迴分發事件。
  • 事件的傳遞順序:
    Activity.dispatchTouchEvent()Window.superDispatchTouchEvent()DecorView.dispatchTouchEvent() → 子 View。

UI Thread 機制

  1. UI Thread 的核心:Looper 與 MessageQueue
  • 每個 Android 應用啟動時,ActivityThread.main() 會建立主執行緒,並初始化:
public static void main(String[] args) {
    Looper.prepareMainLooper();   // 建立 Looper 與 MessageQueue
    ActivityThread thread = new ActivityThread();
    thread.attach(false);
    Looper.loop();                // 進入事件迴圈
}
  • Looper:事件迴圈,不斷從 MessageQueue 取出訊息。
  • MessageQueue:訊息佇列,存放所有待處理事件(包含輸入事件、UI 更新、Handler post 的任務)。
  • Handler:開發者使用 Handler 將任務送到 UI Thread 執行。
  1. 為什麼只能在 UI Thread 更新 UI?
  • 所有 View 的屬性(位置、繪製、事件處理)都與主執行緒的 Looper 綁定。
  • 若在背景執行緒更新 UI,會造成 race condition 與非同步繪製錯誤。
  • 因此 Android 強制所有 UI 操作必須在 UI Thread 進行。
  1. Input 事件如何進入 UI Thread?
  • ViewRootImpl 將事件透過 MessageQueue.enqueueMessage() 放入。
  • Looper 在下一次迴圈取出事件。
  • 呼叫 doDispatchInputEvent(),再交給對應 View 處理。

Input 與 UI Thread 的互動細節

  1. 單一事件的完整生命週期
  • 硬體:觸控螢幕產生座標事件。
  • InputReader:轉換成 MotionEvent。
  • InputDispatcher:透過 Binder IPC 將事件送到對應的應用 Window。
  • ViewRootImpl:將事件放入 UI Thread 的 MessageQueue。
  • Looper:取出事件,交給 ViewRootImpl。
  • DecorView:遞迴分發事件到目標 Button。
  • Button.onTouchEvent():呼叫回調,觸發 onClickListener。
  1. Input 與 UI Thread 卡頓問題
  • 若 UI Thread 阻塞(例如在主執行緒執行了耗時任務),可能會造成:
  • 輸入事件無法及時處理。
  • 使用者點擊沒有反應,造成 ANR(Application Not Responding)。
  • Android 的 ANR 判斷機制
  • InputDispatcher 會監控事件是否在 5 秒內被處理。
  • 若超時,系統會彈出 ANR 對話框。

圖表解析

  1. Input 事件總覽
    https://ithelp.ithome.com.tw/upload/images/20250930/20178907HkirmZ71G8.png

  2. UI Thread 機制
    https://ithelp.ithome.com.tw/upload/images/20250930/20178907UYDFv5LCOV.png

questions

  • 為什麼 Android 限制 UI 更新必須在主執行緒?
    • 因為所有 View 的繪製與屬性存取都依附於主執行緒的 Looper,避免多執行緒操作導致狀態不一致。
  • 輸入事件從硬體到應用的完整流程?
    • InputReader → InputDispatcher → WMS 判斷焦點 → ViewRootImpl → UI Thread MessageQueue → DecorView → View Tree。
  • ANR 的觸發條件是什麼?
    • 若輸入事件在 5 秒內沒有被應用處理,或 BroadcastReceiver 在 10 秒內沒有完成,則會觸發 ANR。
  • ViewRootImpl 的角色?
    • 負責應用與 WMS 的橋樑,接收輸入事件並交給 UI Thread,負責 measure/layout/draw 與事件分發。
  • InputDispatcher 與 UI Thread 的關係?
  • InputDispatcher 將事件透過 Binder 送到 ViewRootImpl,然後由 UI Thread 的 Looper 取出並執行。

summary

  • Input Dispatch:從硬體到應用 Window 的輸入分發機制,確保事件送到正確的目標。
  • UI Thread 機制:所有輸入事件與 UI 更新都依附於主執行緒的 Looper + MessageQueue。
  • 關鍵元件:InputReader、InputDispatcher、WMS、ViewRootImpl、Looper。
  • 核心原則:UI 必須在主執行緒處理,否則會造成 ANR。

上一篇
#14
系列文
安豬複習16
  1. 12
    #11
  2. 13
    #12
  3. 14
    #13
  4. 15
    #14
  5. 16
    #15
完整目錄
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言