本文是 Systrace 系列文章的第四篇,主要是對 Systrace 中的 Input 進行簡單介紹,介紹其 Input 的流程; Systrace 中 Input 信息的體現 ,以及如何結合 Input 信息,分析與 Input 相關的問題android
本系列的目的是經過 Systrace 這個工具,從另一個角度來看待 Android 系統總體的運行,同時也從另一個角度來對 Framework 進行學習。也許你看了不少講 Framework 的文章,可是老是記不住代碼,或者不清楚其運行的流程,也許從 Systrace 這個圖形化的角度,你能夠理解的更深刻一些。git
在Android 基於 Choreographer 的渲染機制詳解 這篇文章中,我有講到,Android App 的主線程運行的本質是靠 Message 驅動的,這個 Message 能夠是循環動畫、能夠是定時任務、能夠是其餘線程喚醒,不過咱們最多見的仍是 Input Message ,這裏的 Input 是以 InputReader 這裏的分類,不只包含觸摸事件(Down、Up、Move) , 可包含 Key 事件(Home Key 、 Back Key) . 這裏咱們着重講的是觸摸事件github
因爲 Android 系統在 Input 鏈上加了一些 Trace 點,且這些 Trace 點也比較完善,部分廠家可能會本身加一些,不過咱們這裏以標準的 Trace 點來說解,這樣不至於你換了個手機抓的 Trace 就不同了shell
Input 在 Android 中的地位是很高的,咱們在玩手機的時候,大部分應用的滑動、跳轉這些都依靠 Input 事件來驅動,後續我會專門寫一篇文章,來介紹 Android 中基於 Input 的運行機制。這裏是從 Systrace 的角度來看 Input 。看下面的流程以前,腦子裏先有個關於 Input 的大概處理流程,這樣看的時候,就能夠代入:數組
另外在看 Systrace 的時候,要牢記 Systrace 中時間是從左到右流逝的,也就是說若是你在 Systrace 上畫一條豎直線,那麼豎直線左邊的事件永遠比右邊的事件先發生,這也是咱們分析源碼流程的一個基石。我但願你們在看基於 Systrace 的源碼流程分析以後,腦子裏有一個圖形化的、立體的流程圖,你跟的代碼走到哪一步了在圖形你在腦中能夠快速定位出來瀏覽器
下面這張圖是一個概覽圖,以滑動桌面爲例 (滑動桌面包括一個 Input_Down 事件 + 若干個 Input_Move 事件 + 一個 Input_Up 事件,這些事件和事件流都會在 Systrace 上有所體現,這也是咱們分析 Systrace 的一個重要的切入點),主要牽扯到的模塊是 SystemServer 和 App 模塊,其中用藍色標識的是事件的流動信息,紅色的是輔助信息。app
InputReader 和 InputDispatcher 是跑在 SystemServer 裏面的兩個 Native 線程,負責讀取和分發 Input 事件,咱們分析 Systrace 的 Input 事件流,首先是找到這裏。下面針對上圖中標號進行簡單socket
下面以第一個 Input_Down 事件的處理流程來進行詳細的工做流說明,其餘的 Move 事件和 Up 事件的處理是同樣的(部分不同,不過影響不大)函數
放大 SystemServer 的部分,能夠看到其工做流(藍色),滑動桌面包括 Input_Down + 若干個 Input_Move + Input_Up ,咱們這裏看的是 Input_Down 這個事件工具
應用在收到 Input 事件後,有時候會立刻去處理 (沒有 Vsync 的狀況下),有時候會等 Vsync 信號來了以後取處理,這裏 Input_Donw 事件就是直接去喚醒主線程作處理,其 Systrace 比較簡單,最上面有個 Input 事件隊列,主線程則是簡單的處理
主線程處理 Input 事件這個你們比較熟悉,從下面的調用棧能夠看到,Input 事件傳到了 ViewRootImpl,最終到了 DecorView ,而後就是你們熟悉的 Input 事件分發機制
從上面的 Systrace 來看,Input 事件的基本流向以下:
經過上面的流程,一次 Input 事件就被消耗掉了(固然這只是正常狀況,還有不少異常狀況、細節處理,這裏就不細說了,本身看相關流程的時候能夠深挖一下) , 那麼本節就從上面的關鍵流中取幾個重要的知識點講解(部分流程和圖參考和拷貝了 Gityuan 的博客的圖,連接在最下面參考那一節)
InputReader 是一個 Native 線程,跑在 SystemServer 進程裏面,其核心功能是從 EventHub 讀取事件、進行加工、將加工好的事件發送到 InputDispatcher
InputReader Loop 流程以下
核心代碼 loopOnce 處理流程以下:
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
}
複製代碼
上面的 InputReader 調用 mQueuedListener->flush 以後 ,將 Input 事件加入到InputDispatcher 的 mInboundQueue ,而後喚醒 InputDispatcher , 從 Systrace 的喚醒信息那裏也能夠看到 InputDispatch 線程是被 InputReader 喚醒的
InputDispatcher 的核心邏輯以下:
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
}
}
複製代碼
InputDispatcher 執行 notifyKey 的時候,會將 Input 事件封裝後放到 InboundQueue 中,後續 InputDispatcher 循環處理 Input 事件的時候,就是從 InboundQueue 取出事件而後作處理
Outbound 意思是出站,這裏的 OutboundQueue 指的是要被 App 拿去處理的事件隊列,每個 App(Connection) 都對應有一個 OutboundQueue ,從 InboundQueue 那一節的圖來看,事件會先進入 InboundQueue ,而後被 InputDIspatcher 派發到各個 App 的 OutboundQueue
當 InputDispatcher 將 Input 事件分發出去以後,將 DispatchEntry 從 outboundQueue 中取出來放到 WaitQueue 中,當 publish 出去的事件被處理完成(finished),InputManagerService 就會從應用中獲得一個回覆,此時就會取出 WaitQueue 中的事件,從 Systrace 中看就是對應 App 的 WaitQueue 減小
若是主線程發生卡頓,那麼 Input 事件沒有及時被消耗,也會在 WaitQueue 這裏體現出來,以下圖:
圖來自 Gityuan 博客
Input 的刷新取決於觸摸屏的採樣,目前比較多的屏幕採樣率是 120Hz 和 160Hz ,對應就是 8ms 採樣一次或者 6.25ms 採樣一次,咱們來看一下其在 Systrace 上的展現
能夠看到上圖中, InputReader 每隔 6.25ms 就能夠讀上來一個數據,交給 InputDispatcher 去分發給 App ,那麼是否是屏幕採樣率越高越好呢?也不必定,好比上面那張圖,雖然 InputReader 每隔 6.25ms 就能夠讀上來一個數據給 InputDispatcher 去分發給 App ,可是從 WaitQueue 的表現來看,應用並無消耗這個 Input 事件,這是爲何呢?
緣由在於應用消耗 Input 事件的時機是 Vsync 信號來了以後,刷新率爲 60Hz 的屏幕,通常系統也是 60 fps ,也就是說兩個 Vsync 的間隔在 16.6ms ,這期間若是有兩個或者三個 Input 事件,那麼必然有一個或者兩個要被拋棄掉,只拿最新的那個。也就是說:
Dumpsys Input 主要是 Debug 用,咱們也能夠來看一下其中的一些關鍵信息,到時候遇到了問題也能夠從這裏面找 , 其命令以下:
adb shell dumpsys input
複製代碼
其中的輸出比較多,咱們終點截取 Device 信息、InputReader、InputDispatcher 三段來看就能夠了
主要是目前鏈接上的 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
複製代碼
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
複製代碼
InputDispatch 這裏的重要信息主要包括
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 流程的細節部分,推薦你們在看完這篇文章後,若是對代碼細節感興趣,能夠仔細研讀下面這幾篇很是棒的文章。
本文涉及到的附件也上傳了,各位下載後解壓,使用 Chrome 瀏覽器打開便可 點此連接下載文章所涉及到的 Systrace 附件
小廠系統研發工程師 , 更多信息能夠點擊 關於我 , 很是但願和你們一塊兒交流 , 共同進步 .
一我的能夠走的更快 , 一羣人能夠走的更遠