为了分析Input场景下ANR发生的原因,特意找了对Input系统全面介绍的一篇文章,如果系统对于Input Event超过预定时间(5s)没有响应,则会弹出ANR提示用户继续等待或者选择FC。通过下面文章对Input事件传递流程的分析,在InputDispatcher.cpp里面会对持有分发锁有一个超时时间(3s),在WMS中才是对事件真正开始计时。
InputDispatching Timeout: 输入事件分发超时5s,包括按键和触摸事件。
一. 概述
先简单总结和回顾以下前几篇文章的内容:
- Input系统—InputReader线程:通过EventHub从/dev/input节点获取事件,转换成EventEntry事件加入到InputDispatcher的mInboundQueue。
- Input系统—InputDispatcher线程:从mInboundQueue队列取出事件,转换成DispatchEntry事件加入到connection的outboundQueue队列。再然后开始处理分发事件,取出outbound队列,放入waitQueue.
- Input系统—UI线程:创建socket pair,分别位于”InputDispatcher”线程和focused窗口所在进程的UI主线程,可相互通信。
- UI主线程:通过setFdEvents(), 监听socket客户端,收到消息后回调NativeInputEventReceiver();【见小节2.1】
- “InputDispatcher”线程: 通过IMS.registerInputChannel(),监听socket服务端,收到消息后回调handleReceiveCallback;【见小节3.1】
接下来,以按键事件为例,说一说一次事件处理过程是如何完成。按键事件经过InputReader,再到InputDispatcher的startDispatchCycleLocked()过程会调用publishKeyEvent(),从该方法说起。
二. InputDispatcher线程
2.1 InputPublisher.publishKeyEvent
[-> InputTransport.cpp]
2.2 InputChannel.sendMessage
[-> InputTransport.cpp]
Input系统—UI线程讲解了会创建socket pair,用于两个进程的线程间相互通信。当mFd写入消息后,此时会唤醒处于epoll_wait状态的应用进程的UI线程,见下文。
另外,当写入失败,则返回值为WOULD_BLOCK或者DEAD_OBJECT。
三. UI主线程
当收到消息的处理过程,Android消息机制在获取下一条消息的时候,会调用lnativePollOnce(),最终进入到Looper::pollInner()过程,如下:
3.1 Looper::pollInner
此处response.request.callback是指NativeInputEventReceiver,接下来便是执行NativeInputEventReceiver.handleEvent();
3.2 handleEvent
[-> android_view_InputEventReceiver.cpp]
UI线程收到Key事件后,开始处理该事件。
3.3 NativeInputEventReceiver.consumeEvents
[-> android_view_InputEventReceiver.cpp]
3.3.1 InputConsumer.consume
[->InputTransport.cpp ::InputConsumer]
3.3.2 InputChannel.receiveMessage
[-> InputTransport.cpp]
3.3.3 InputEventReceiver.dispachInputEvent
[-> InputEventReceiver.java]
3.3.4 onInputEvent
[-> ViewRootImpl.java ::WindowInputEventReceiver]
3.3.5 enqueueInputEvent
[-> ViewRootImpl.java]
3.3.6 doProcessInputEvents
[-> ViewRootImpl.java]
3.3.7 事件分发
[-> ViewRootImpl.java]
经过一系列的InputStage调用, 最终会分发到真正需要处理该时间的窗口. 当处理完后会调用finishInputEvent(), 见小节3.4
3.4 finishInputEvent
[-> ViewRootImpl.java]
3.4.1 mReceiver.finishInputEvent
3.5 sendFinishedSignal
[-> InputTransport.cpp ::InputConsumer]
3.5.1 sendUnchainedFinishedSignal
[-> InputTransport.cpp ::InputConsumer]
通过InputChannel->sendMessage,将TYPE_FINISHED类型的消息,发送回InputDispatcher线程。
四. InputDispatcher线程
4.1 Looper::pollInner
此处response.request.callback是指SimpleLooperCallback。接下来调用SimpleLooperCallback.handleEvent(). 执行后的返回值callbackResult=0则移除该fd,否则稍后重新尝试。
4.2 handleEvent
[-> Looper.cpp ::SimpleLooperCallback]
IMS.registerInputChannel()过程,会调用Looper.addFd()完成的赋值操作,mCallback等于handleReceiveCallback()方法。
4.3 handleReceiveCallback
[-> InputDispatcher]
4.4 InputPublisher.receiveFinishedSignal
[-> InputTransport.cpp]
4.5 finishDispatchCycleLocked
[-> InputDispatcher.cpp]
4.5.1 onDispatchCycleFinishedLocked
[-> InputDispatcher.cpp]
4.6 runCommandsLockedInterruptible
[-> InputDispatcher.cpp]
由【小节4.5】,可以队列中的元素至少有doDispatchCycleFinishedLockedInterruptible。
4.6.1 doDispatchCycleFinishedLockedInterruptible
[-> InputDispatcher.cpp]
该方法主要功能:
- 打印出所有分发时间超过2s的事件;
- 将dispatchEntry事件从等待队列(waitQueue)中移除;
- 启动下一个事件处理循环。
五. 总结
5.1 整体框架图
5.2 交互过程
用一张图来总结交互过程,主要是通过一对socket方式来通信。 当input时间分发到app端, 那么便进入来了InputEventReceiver.dispatchInputEvent()过程.
图解:
- InputDispatcher线程调用InputPublisher的publishKeyEvent向UI主线程发送input事件;
- UI主线程接收到该事件后,调用InputConsumer的consumeEvents来处理该事件, 一路执行到ViewRootImpl.deliverInputEvent()方法;
- UI主线程经过一系列的InputStage来处理, 当事件分发完成,则会执行finishInputEvent()方法.再进一步调用InputConsumer::sendFinishedSignal 告知InputDispatcher线程该时事件已处理完成.
- InputDispatcher线程收到该事件后, 执行InputDispatcher::handleReceiveCallback();最终会调用doDispatchCycleFinishedLockedInterruptible()方法 ,将dispatchEntry事件从等待队列(waitQueue)中移除.
基于Android 6.0源码, 分析InputManagerService的启动过程
一. 概述
当用户触摸屏幕或者按键操作,首次触发的是硬件驱动,驱动收到事件后,将该相应事件写入到输入设备节点, 这便产生了最原生态的内核事件。接着,输入系统取出原生态的事件,经过层层封装后成为KeyEvent或者MotionEvent ;最后,交付给相应的目标窗口(Window)来消费该输入事件。可见,输入系统在整个过程起到承上启下的衔接作用。
Input模块的主要组成:
- Native层的InputReader负责从EventHub取出事件并处理,再交给InputDispatcher;
- Native层的InputDispatcher接收来自InputReader的输入事件,并记录WMS的窗口信息,用于派发事件到合适的窗口;
- Java层的InputManagerService跟WMS交互,WMS记录所有窗口信息,并同步更新到IMS,为InputDispatcher正确派发事件到ViewRootImpl提供保障;
Input相关的动态库:
- libinputflinger.so:frameworks/native/services/inputflinger/
- libinputservice.so:frameworks/base/libs/input/
- libinput.so: frameworks/native/libs/input/
1.1 整体框架类图
InputManagerService作为system_server中的重要服务,继承于IInputManager.Stub, 作为Binder服务端,那么Client位于InputManager的内部通过IInputManager.Stub.asInterface() 获取Binder代理端,C/S两端通信的协议是由IInputManager.aidl来定义的。
Input模块所涉及的重要类的关系如下:
图解:
- InputManagerService位于Java层的InputManagerService.java文件;
- 其成员
mPtr
- 指向Native层的NativeInputManager对象;
- NativeInputManager位于Native层的com_android_server_input_InputManagerService.cpp文件;
- 其成员
mServiceObj
- 指向Java层的IMS对象;
- 其成员
mLooper
- 是指“android.display”线程的Looper;
- InputManager位于libinputflinger中的InputManager.cpp文件;
- InputDispatcher和InputReader的成员变量
mPolicy
- 都是指NativeInputManager对象;
- InputReader的成员
mQueuedListener
- ,数据类型为QueuedInputListener;通过其内部成员变量mInnerListener指向InputDispatcher对象; 这便是InputReader跟InputDispatcher交互的中间枢纽。
1.2 启动调用栈
IMS服务是伴随着system_server进程的启动而启动,整个调用过程:
InputReader,InputDispatcher。
二. 启动过程
2.1 InputManagerService
[-> InputManagerService.java]
2.2 nativeInit
[-> com_android_server_input_InputManagerService.cpp]
2.3 NativeInputManager
[-> com_android_server_input_InputManagerService.cpp]
此处的mLooper是指“android.display”线程的Looper; libinputservice.so库中PointerController和SpriteController对象都继承于于MessageHandler, 这两个Handler采用的便是该mLooper.
2.4 EventHub
[-> EventHub.cpp]
该方法主要功能:
- 初始化INotify(监听”/dev/input”),并添加到epoll实例
- 创建非阻塞模式的管道,并添加到epoll;
2.5 InputManager
[-> InputManager.cpp]
InputDispatcher和InputReader的mPolicy成员变量都是指NativeInputManager对象。
2.6 InputDispatcher
[-> InputDispatcher.cpp]
该方法主要工作:
- 创建属于自己线程的Looper对象;
- 超时参数来自于IMS,参数默认值keyRepeatTimeout = 500,keyRepeatDelay = 50。
2.7 InputReader
[-> InputReader.cpp]
mInnerListener便是InputDispatcher对象。 前面【小节2.5】InputManager创建完InputDispatcher和InputReader对象, 接下里便是调用initialize初始化。
2.8 initialize
[-> InputManager.cpp]
初始化的主要工作就是创建两个能访问Java代码的native线程。
- 创建线程“InputReader”
- 创建线程”InputDispatcher“
到此[2.1-2.8]整个的InputManagerService对象初始化过程并完成,接下来便是调用其start方法。
2.9 IMS.start
[-> InputManagerService.java]
2.10 nativeStart
[-> com_android_server_input_InputManagerService.cpp]
2.11 InputManager.start
[InputManager.cpp]
该方法的主要功能是启动两个线程:
- 启动线程“InputReader”
- 启动线程”InputDispatcher“
三. 总结
分层视角:
- Java层InputManagerService:采用android.display线程处理Message.
- JNI的NativeInputManager:采用android.display线程处理Message,以及创建EventHub。
- Native的InputManager:创建InputReaderThread和InputDispatcherThread两个线程
主要功能:
- IMS服务中的成员变量mPtr记录Native层的NativeInputManager对象;
- IMS对象的初始化过程的重点在于native初始化,分别创建了以下对象:
- NativeInputManager;
- EventHub, InputManager;
- InputReader,InputDispatcher;
- InputReaderThread,InputDispatcherThread
- IMS启动过程的主要功能是启动以下两个线程:
- InputReader:从EventHub取出事件并处理,再交给InputDispatcher
- InputDispatcher:接收来自InputReader的输入事件,并派发事件到合适的窗口。
android.display, InputReader,InputDispatcher。
- InputDispatcher线程:属于Looper线程,会创建属于自己的Looper,循环分发消息;
- InputReader线程:通过getEvents()调用EventHub读取输入事件,循环读取消息;
- android.display线程:属于Looper线程,用于处理Java层的IMS.InputManagerHandler和JNI层的NativeInputManager中指定的MessageHandler消息;
Input事件流程:Linux Kernel -> IMS(InputReader -> InputDispatcher) -> WMS -> ViewRootImpl, 后续再进一步介绍。
四. 附录
最后在列举整个input处理流程中常见的重要对象或结构体,后续input系列文章直接使用以上结构体,可回过来查看。
4.1 InputReader.h
4.1.1 InputDevice
4.2 InputDispatcher.h
4.2.1 DropReason
4.2.2 Connection
4.2.3 EventEntry
此处type的可取值为:
- TYPE_CONFIGURATION_CHANGED
- TYPE_DEVICE_RESET
- TYPE_KEY: 按键事件
- TYPE_MOTION: 触摸时间
4.2.4 INPUT_EVENT_INJECTION
4.3 InputTransport.h
4.3.1 InputChannel
sendMessage的返回值:
- OK: 代表成功;
- WOULD_BLOCK: 代表Channel已满;
- DEAD_OBJECT: 代表Channel已关闭;
receiveMessage的返回值:
- OK: 代表成功;
- WOULD_BLOCK: 代表Channel为空;
- DEAD_OBJECT: 代表Channel已关闭;
4.3.2 InputTarget
4.3.3 InputPublisher
4.3.4 InputConsumer
4.4 input.h
4.4.1 KeyEvent
4.4.2 MotionEvent
4.5 InputListener.h
4.5.1 NotifyKeyArgs
一. InputReader起点
上一篇文章Input系统—启动篇,介绍IMS服务的启动过程会创建两个native线程,分别是InputReader,InputDispatcher. 接下来从InputReader线程的执行过程从threadLoop为起点开始分析。
1.1 threadLoop
[-> InputReader.cpp]
threadLoop返回值true代表的是会不断地循环调用loopOnce()。另外,如果当返回值为false则会 退出循环。整个过程是不断循环的地调用InputReader的loopOnce()方法,先来回顾一下InputReader对象构造方法。
1.2 loopOnce
[-> InputReader.cpp]
二. EventHub
2.1 getEvents
[-> EventHub.cpp]
/dev/input下的设备节点,经过EventHub将input_event结构体 + deviceId 转换成RawEvent结构体,如下:
2.1.1 RawEvent
[-> InputEventReader.h]
此处事件类型:
- DEVICE_ADDED(添加)
- DEVICE_REMOVED(删除)
- FINISHED_DEVICE_SCAN(扫描完成)
- type<FIRST_SYNTHETIC_EVENT(其他事件)
getEvents()已完成转换事件转换工作, 接下来,顺便看看设备扫描过程.
2.2 设备扫描
2.2.1 scanDevicesLocked
2.2.2 scanDirLocked
2.2.3 openDeviceLocked
2.2.4 addDeviceLocked
介绍了EventHub从设备节点获取事件的流程,当收到事件后接下里便开始处理事件。
三. InputReader
3.1 processEventsLocked
[-> InputReader.cpp]
事件处理总共有下几类类型:
- DEVICE_ADDED(设备增加), [见小节3.2]
- DEVICE_REMOVED(设备移除)
- FINISHED_DEVICE_SCAN(设备扫描完成)
- 数据事件[见小节3.4]
先来说说DEVICE_ADDED设备增加的过程。
3.2 设备增加
3.2.1 addDeviceLocked
3.2.2 createDeviceLocked
该方法主要功能:
- 创建InputDevice对象,将InputReader的mContext赋给InputDevice对象所对应的变量
- 根据设备类型来创建并添加相对应的InputMapper,同时设置mContext.
input设备类型有很多种,以上代码只列举部分常见的设备以及相应的InputMapper:
- 键盘类设备:KeyboardInputMapper
- 触摸屏设备:MultiTouchInputMapper或SingleTouchInputMapper
- 鼠标类设备:CursorInputMapper
介绍完设备增加过程,继续回到[小节3.1]除了设备的增删,更常见事件便是数据事件,那么接下来介绍数据事件的 处理过程。
3.3 事件处理
3.3.1 processEventsForDeviceLocked
3.3.2 InputDevice.process
小节[3.2]createDeviceLocked创建设备并添加InputMapper,提到会有多种InputMapper。 这里以KeyboardInputMapper(按键事件)为例来展开说明
3.4 按键事件处理
3.4.1 KeyboardInputMapper.process
[-> InputReader.cpp ::KeyboardInputMapper]
3.4.2 EventHub::mapKey
[-> EventHub.cpp]
将事件的扫描码(scanCode)转换成键盘码(Keycode)
3.4.3 KeyCharacterMap::mapKey
[-> KeyCharacterMap.cpp]
再回到[3.4.1],接下来进入如下过程:
3.4.4 InputMapper.processKey
[-> InputReader.cpp]
参数说明:
- mKeyDowns记录着所有按下的键;
- mDownTime记录按下时间点;
- 此处KeyboardInputMapper的mContext指向InputReader,getListener()获取的便是mQueuedListener。 接下来调用该对象的notifyKey.
3.4.5 QueuedInputListener.notifyKey
[-> InputListener.cpp]
mArgsQueue的数据类型为Vector<NotifyArgs*>,将该key事件压人该栈顶。 到此,整个事件加工完成, 再然后就是将事件发送给InputDispatcher线程.
四. QueuedListener
4.1 QueuedInputListener.flush
[-> InputListener.cpp]
mInnerListener便是InputDispatcher对象。
4.2 NotifyKeyArgs.notify
[-> InputListener.cpp]
4.3 InputDispatcher.notifyKey
[-> InputDispatcher.cpp]
该方法的主要功能:
- 调用NativeInputManager.interceptKeyBeforeQueueing,加入队列前执行拦截动作,但并不改变流程,调用链:
- IMS.interceptKeyBeforeQueueing
- InputMonitor.interceptKeyBeforeQueueing (继承IMS.WindowManagerCallbacks)
- PhoneWindowManager.interceptKeyBeforeQueueing (继承WindowManagerPolicy)
- 当mInputFilterEnabled=true(该值默认为false,可通过setInputFilterEnabled设置),则调用NativeInputManager.filterInputEvent过滤输入事件;
- 当返回值为false则过滤该事件,不再往下分发;
- 生成KeyEvent,并调用enqueueInboundEventLocked,将该事件加入到InputDispatcherd的成员变量mInboundQueue。
4.3.1 interceptKeyBeforeQueueing
该方法会调用Java层的InputManagerService的interceptKeyBeforeQueueing()方法。
4.3.2 filterInputEvent
当inputEventObj不为空,则调用Java层的IMS.filterInputEvent(). 经过层层调用后, 最终会再调用InputDispatcher.injectInputEvent(),该基本等效于该方法的后半段:
- enqueueInboundEventLocked
- wakeup
4.3.3 enqueueInboundEventLocked
AppSwitchKeyEvent是指keyCode等于以下值:
- AKEYCODE_HOME
- AKEYCODE_ENDCALL
- AKEYCODE_APP_SWITCH
4.3.4 findTouchedWindowAtLocked
[-> InputDispatcher.cpp]
此处mWindowHandles的赋值过程是由Java层的InputMonitor.setInputWindows(),经过JNI调用后进入InputDispatcher::setInputWindows()方法完成. 进一步说, 就是WMS执行addWindow()过程或许UI改变等场景,都会触发该方法的修改.
4.3.5 Looper.wake
[-> system/core/libutils/Looper.cpp]
[小节4.3]的过程会调用enqueueInboundEventLocked()方法来决定是否需要将数字1写入句柄mWakeEventFd来唤醒InputDispatcher线程. 满足唤醒的条件:
- 执行enqueueInboundEventLocked方法前,mInboundQueue队列为空,执行完必然不再为空,则需要唤醒分发线程;
- 当事件类型为key事件,且发生一对按下和抬起操作,则需要唤醒;
- 当事件类型为motion事件,且当前可触摸的窗口属于另一个应用,则需要唤醒.
五. 总结
5.1 核心工作
InputReader整个过程涉及多次事件封装转换,其主要工作核心是以下三大步骤:
- getEvents:通过EventHub(监听目录/dev/input)读取事件放入mEventBuffer,而mEventBuffer是一个大小为256的数组, 再将事件input_event转换为RawEvent; [见小节2.1]
- processEventsLocked: 对事件进行加工, 转换RawEvent -> NotifyKeyArgs(NotifyArgs) [见小节3.1]
- QueuedListener->flush:将事件发送到InputDispatcher线程, 转换NotifyKeyArgs -> KeyEntry(EventEntry) [见小节4.1]
InputReader线程不断循环地执行InputReader.loopOnce(), 每次处理完生成的是EventEntry(比如KeyEntry, MotionEntry), 接下来的工作就交给InputDispatcher线程。
5.2 流程图
点击查看大图:
InputReader的核心工作就是从EventHub获取数据后生成EventEntry事件,加入到InputDispatcher的mInboundQueue队列,再唤醒InputDispatcher线程。
说明:
- IMS.filterInputEvent可以过滤无需上报的事件,当该方法返回值为false则代表是需要被过滤掉的事件,无机会交给InputDispatcher来分发。
- 节点/dev/input的event事件所对应的输入设备信息位于
/proc/bus/input/devices
- ,也可以通过
getevent
- 来获取事件. 不同的input事件所对应的物理input节点,比如常见的情形:
- 屏幕触摸和(MENU,HOME,BACK)3按键:对应同一个input设备节点;
- POWER和音量(下)键:对应同一个input设备节点;
- 音量(上)键:对应同一个input设备节点;
一. InputDispatcher起点
上篇文章输入系统之InputReader线程,介绍InputReader利用EventHub获取数据后生成EventEntry事件,加入到InputDispatcher的mInboundQueue队列,再唤醒InputDispatcher线程。本文将介绍InputDispatcher,同样从threadLoop为起点开始分析。
1.1 threadLoop
先来回顾一下InputDispatcher对象的初始化过程:
该方法主要工作:
- 创建属于自己线程的Looper对象;
- 超时参数来自于IMS,参数默认值keyRepeatTimeout = 500,keyRepeatDelay = 50。
[-> InputDispatcher.cpp]
整个过程不断循环地调用InputDispatcher的dispatchOnce()来分发事件
1.2 dispatchOnce
[-> InputDispatcher.cpp]
线程执行Looper->pollOnce,进入epoll_wait等待状态,当发生以下任一情况则退出等待状态:
- callback:通过回调方法来唤醒;
- timeout:到达nextWakeupTime时间,超时唤醒;
- wake: 主动调用Looper的wake()方法;
二. InputDispatcher
2.1 dispatchOnceInnerLocked
在enqueueInboundEventLocked()的过程中已设置mAppSwitchDueTime等于eventTime加上500ms:
该方法主要功能:
- mDispatchFrozen用于决定是否冻结事件分发工作不再往下执行;
- 当事件分发的时间点距离该事件加入mInboundQueue的时间超过500ms,则认为app切换过期,即isAppSwitchDue=true;
- mInboundQueue不为空,则取出头部的事件,放入mPendingEvent变量;并重置ANR时间;
- 根据EventEntry的type类型分别处理,比如按键调用dispatchKeyLocked分发事件;再根据分发结果来决定是否进入done;
- 执行完成(done)的处理:
- 根据dropReason(默认NOT_DROPPED不处理)来决定是否丢失事件; dropInboundEventLocked
- 释放当前正在处理的事件(即mPendingEvent); releasePendingEventLocked
关于dispatchKeyLocked分发事件,
- 不会执行done过情况:
- 当前Event时间小于唤醒时间;
- 让policy有机会执行拦截操作;
- 调用findFocusedWindowTargetsLocked方法的返回结果是INPUT_EVENT_INJECTION_PENDING, 即targets没有处于Ready状态;
- 会执行done的情况:
- 该事件需要丢弃, 即dropReason != DROP_REASON_NOT_DROPPED;
- findFocusedWindowTargetsLocked的返回结果不是INPUT_EVENT_INJECTION_PENDING(没有正在处理的事件);
接下来以按键为例来展开说明, 则进入[小节2.2] dispatchKeyLocked.
2.1.1 resetANRTimeoutsLocked
2.1.2 dropInboundEventLocked
2.2 dispatchKeyLocked
在以下场景下,有可能无法分发事件:
- 当前时间小于唤醒时间(nextWakeupTime)的情况;
- policy需要提前拦截事件的情况;
- 需要drop事件的情况;
- 寻找聚焦窗口失败的情况;
如果成功跳过以上所有情况,则会进入执行事件分发的过程。
2.3 findFocusedWindowTargetsLocked
此处mFocusedWindowHandle是何处赋值呢?是在InputDispatcher.setInputWindows()方法,具体见下一篇文章Input系统—UI线程.
寻找聚焦窗口失败的情况:
- 无窗口,无应用: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
2.3.2 handleTargetsNotReadyLocked
此处mInputTargetWaitTimeoutTime是由当前时间戳+5s, 并设置mInputTargetWaitCause等于INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY. 也就是说ANR时间段是指input等待理由处于INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY(应用没有准备就绪)的时间长达5s的场景.而前面resetANRTimeoutsLocked() 过程是唯一用于重置等待理由的地方.
那么, ANR时间区间是指当前这次的事件dispatch过程中执行findFocusedWindowTargetsLocked()方法到下一次执行resetANRTimeoutsLocked()的时间区间.
- 当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.3.3 addWindowTargetLocked
将当前聚焦窗口mFocusedWindowHandle的inputChannel传递到inputTargets。
2.4 dispatchEventLocked
该方法主要功能是将eventEntry发送到目标inputTargets.
userActivityFromNative()方法. 这也是PMS中唯一的native call方法.
2.4.1 pokeUserActivityLocked
2.4.2 postCommandLocked
2.4.3 getConnectionIndexLocked
mConnectionsByFd队列中查询目标connection.
2.5 prepareDispatchCycleLocked
当connection状态不正确,则直接返回。
2.6 enqueueDispatchEntriesLocked
该方法主要功能:
- 根据dispatchMode来分别执行DispatchEntry事件加入队列的操作。
- 当起初connection.outboundQueue等于空, 经enqueueDispatchEntryLocked处理后, outboundQueue不等于空情况下, 则执行startDispatchCycleLocked()方法.
2.7 enqueueDispatchEntryLocked
该方法主要功能:
- 根据dispatchMode来决定是否需要加入outboundQueue队列;
- 根据EventEntry,来生成DispatchEntry事件;
- 将dispatchEntry加入到connection的outbound队列.
执行到这里,其实等于由做了一次搬运的工作,将InputDispatcher中mInboundQueue中的事件取出后, 找到目标window后,封装dispatchEntry加入到connection的outbound队列.
2.8 startDispatchCycleLocked
startDispatchCycleLocked的主要功能: 从outboundQueue中取出事件,重新放入waitQueue队列
- startDispatchCycleLocked触发时机:当起初connection.outboundQueue等于空, 经enqueueDispatchEntryLocked处理后, outboundQueue不等于空。
- startDispatchCycleLocked主要功能: 从outboundQueue中取出事件,重新放入waitQueue队列
- publishKeyEvent执行结果status不等于OK的情况下:
- WOULD_BLOCK,且waitQueue等于空,则调用abortBrokenDispatchCycleLocked(),该方法最终会调用到Java层的IMS.notifyInputChannelBroken().
- WOULD_BLOCK,且waitQueue不等于空,则处于阻塞状态,即inputPublisherBlocked=true
- 其他情况,则调用abortBrokenDispatchCycleLocked
- abortBrokenDispatchCycleLocked()方法最终会调用到Java层的IMS.notifyInputChannelBroken().
2.9 inputPublisher.publishKeyEvent
[-> InputTransport.cpp]
InputChannel通过socket向远端的socket发送消息。socket通道是如何建立的呢? InputDispatcher又是如何与前台的window通信的呢? 见下一篇文章Input系统—进程交互, 从文章的小节2.1开始继续往下说.
2.10 releasePendingEventLocked
三. 处理Comand
3.1 runCommandsLockedInterruptible
通过循环方式处理完mCommandQueue队列的所有命令,处理过程从mCommandQueue中取出CommandEntry.
前面小节【2.4.1】添加的doPokeUserActivityLockedInterruptible命令. 接下来进入该方法:
3.2 doPokeUserActivityLockedInterruptible
[-> InputDispatcher]
3.3 pokeUserActivity
[-> com_android_server_input_InputManagerService.cpp]
3.4 android_server_PowerManagerService_userActivity
[-> com_android_server_power_PowerManagerService.cpp]
3.5 PMS.userActivityFromNative
[-> PowerManagerService.java]
runCommandsLockedInterruptible是不断地从mCommandQueue队列取出命令,然后执行直到全部执行完成。 除了doPokeUserActivityLockedInterruptible,还有其他如下命令:
- doNotifyANRLockedInterruptible
- doInterceptKeyBeforeDispatchingLockedInterruptible
- doDispatchCycleFinishedLockedInterruptible
- doNotifyInputChannelBrokenLockedInterruptible
- doNotifyConfigurationChangedInterruptible
四. 总结
4.1 流程图
点击查看大图:
4.2 核心方法
用一张图来整体概况InputDispatcher线程的主要工作:
图解:
- dispatchOnceInnerLocked(): 从InputDispatcher的
mInboundQueue
- 队列,取出事件EventEntry。另外该方法开始执行的时间点(currentTime)便是后续事件dispatchEntry的分发时间(deliveryTime)
- dispatchKeyLocked():满足一定条件时会添加命令doInterceptKeyBeforeDispatchingLockedInterruptible;
- enqueueDispatchEntryLocked():生成事件DispatchEntry并加入connection的
outbound
- 队列
- startDispatchCycleLocked():从outboundQueue中取出事件DispatchEntry, 重新放入connection的
waitQueue
- 队列;
- InputChannel.sendMessage通过socket方式将消息发送给远程进程;
- runCommandsLockedInterruptible():通过循环遍历地方式,依次处理mCommandQueue队列中的所有命令。而mCommandQueue队列中的命令是通过postCommandLocked()方式向该队列添加的。
一. 概述
前面文章都是介绍了两个线程InputReader和InputDispatcher的工作过程。在InputDispatcher的过程讲到 调用InputChanel通过socket与远程进程通信,本文便展开讲解这个socket是如何建立的。
对于InputReader和InputDispatcher都是运行在system_server进程; 用户点击的界面往往可能是某一个app,而每个app一般地都运行在自己的进程,这里就涉及到跨进程通信,app进程是如何与system进程建立通信。
要解答这些问题,从Activity最基本的创建过程开始说起。我们都知道一般地Activity对应一个应用窗口, 每一个窗口对应一个ViewRootImpl。窗口是如何添加到Activity的,从Activity.onCreate()为起点讲解。
二. UI线程
总所周知,Activity的生命周期的回调方法都是运行在主线程,也称之为UI线程,所有UI相关的操作都需要运行在该线程。本文虽然是UI线程,但并非只介绍所有运行在UI线程的流程,文中还涉及binder thread。
2.1 onCreate
[-> Activity.java]
Activity启动是由system进程控制:
- handleLaunchActivity():会调用Activity.onCreate(), 该方法内再调用setContentView(),经过AMS与WMS的各种交互,层层调用后,进入step2
- handleResumeActivity():会调用Activity.makeVisible(),该方法继续调用便会执行到WindowManagerImpl.addView(), 该方法内部再调用WindowManagerGlobal.addView(),
2.2 addView
[-> WindowManagerGlobal.java]
2.3 ViewRootImpl
[-> ViewRootImpl.java]
2.3.1 getWindowSession
[-> WindowManagerGlobal.java]
2.3.2 WMS.openSession
再次经过Binder将数据写回app进程,则获取的便是Session的代理对象。
2.3.3 setView
[-> ViewRootImpl.java]
该方法主要功能:
- 创建Java层的InputChannel对象mInputChannel
- 向WMS注册InputChannel信息,通过InputChannel.openInputChannelPair创建的socket pair,将其中的客户端赋值给mInputChannel.
- 创建WindowInputEventReceiver对象
跨进程调用,进入binder thread执行如下方法:
2.4 Session.addToDisplay
[-> Session.java]
2.5 WMS.addToDisplay
[-> WindowManagerService.java]
inputChannels数组:
- inputChannels[0]所对应的InputChannel名称的后缀为
(server)
- ;
- inputChannels[1]所对应的InputChannel名称的后缀为
(client)
- ;
其中:
- 服务端inputChannels[0]保存到WindowState的mInputChannel;
- 客户端inputChannels[1]传递给outInputChannel,最终传递给ViewRootImpl的mInputChannel;
2.5.1 WindowState初始化
[-> WindowState.java]
2.5.2 updateInputWindowsLw
[-> InputMonitor.java]
setInputWindows的调用链:(最终设置mFocusedWindowHandle值)
dragWindowHandle的初始化过程:
2.6 openInputChannelPair
[-> InputChannel.java]
这个过程的主要功能
- 创建两个socket通道(非阻塞, buffer上限32KB)
- 创建两个InputChannel对象;
- 创建两个NativeInputChannel对象;
- 将nativeInputChannel保存到Java层的InputChannel的成员变量mPtr
2.6.1 nativeOpenInputChannelPair
[-> android_view_InputChannel.cpp]
2.6.2 openInputChannelPair
[-> InputTransport.cpp]
该方法主要功能:
- 创建socket pair; (
非阻塞式
- 的socket)
- 设置两个socket的接收和发送的buffer
上限为32KB
- ;
- 创建client和server的Native层InputChannel对象;
- sockets[0]所对应的InputChannel名称的后缀为
(server)
- ;
- sockets[1]所对应的InputChannel名称的后缀为
(client)
创建InputChannel对象位于文件InputTransport.cpp,如下:
另外,创建NativeInputChannel对象位于文件android_view_InputChannel.cpp,如下:
2.6.3 android_view_InputChannel_createInputChannel
[-> android_view_InputChannel.cpp]
此处:
- gInputChannelClassInfo.clazz是指Java层的InputChannel类
- gInputChannelClassInfo.ctor是指Java层的InputChannel构造方法;
- gInputChannelClassInfo.mPtr是指Java层的InputChannel的成员变量mPtr;
2.7 transferTo
[-> InputChannel.java]
2.7.1 nativeTransferTo
[-> android_view_InputChannel.cpp]
inputChannels[1].transferTo(outInputChannel)主要功能:
- 当outInputChannel.mPtr不为空,则直接返回;否则进入step2;
- 将inputChannels[1].mPtr的值赋给outInputChannel.mPtr;
- 清空inputChannels[1].mPtr值;
也就是将socket客户端inputChannels[1]传递给outInputChannel;
2.8 IMS.registerInputChannel
[-> InputManagerService.java]
- inputChannel是指inputChannels[0],即socket服务端;
- inputWindowHandle是指WindowState.mInputWindowHandle;
2.8.1 nativeRegisterInputChannel
[-> com_android_server_input_InputManagerService.cpp]
2.8.2 registerInputChannel
[-> com_android_server_input_InputManagerService.cpp]
mInputManager是指NativeInputManager初始化过程创建的InputManager对象(C++).
2.8.3 registerInputChannel
[-> InputDispatcher.cpp]
将新创建的connection保存到mConnectionsByFd成员变量,“InputDispatcher”线程的Looper添加对socket服务端的监听功能; 当该socket有消息时便会唤醒该线程工作。
2.8.4 初始化Connection
[-> InputDispatcher.cpp]
其中InputPublisher初始化位于文件InputTransport.cpp
此处inputChannel是指前面openInputChannelPair创建的socket服务端,将其同时保存到Connection.inputChannel和InputPublisher.mChannel。
2.8.5 Looper.addFd
[-> system/core/libutils/Looper.cpp]
此处Loop便是“InputDispatcher”线程的Looper,将socket服务端的fd采用epoll机制注册监听.
小节
虽然本文介绍的UI线程的工作,
- [小节2.1 ~ 2.3]: 运行在UI线程;
- [小节2.4 ~ 2.8]:通过IWindowSession的Binder IPC调用,运行在system_server的binder thread;
ViewRootImpl的setView()过程:
- 创建socket pair,作为InputChannel:
- socket服务端保存到system_server中的WindowState的mInputChannel;
- socket客户端通过binder传回到远程进程的UI主线程ViewRootImpl的mInputChannel;
- IMS.registerInputChannel()注册InputChannel,监听socket服务端:
- Loop便是“InputDispatcher”线程的Looper;
- 回调方法handleReceiveCallback。
三. WindowInputEventReceiver
接下来,看看【小节2.3.3】创建WindowInputEventReceiver对象
3.1 WindowInputEventReceiver初始化
[-> ViewRootImpl.java]
3.2 InputEventReceiver
[-> InputEventReceiver.java]
3.3 nativeInit
[-> android_view_InputEventReceiver.cpp]
3.4 NativeInputEventReceiver
[-> android_view_InputEventReceiver.cpp]
3.4.1 InputConsumer
[-> InputTransport.cpp]
此处inputChannel是指socket客户端。
3.5 initialize
[-> android_view_InputEventReceiver.cpp]
3.6 setFdEvents
[-> android_view_InputEventReceiver.cpp]
3.6.1 Looper.addFd
[-> system/core/libutils/Looper.cpp]
此处的Looper便是UI主线程的Looper,将socket客户端的fd添加到UI线程的Looper来监听,回调方法为NativeInputEventReceiver。
四. 总结
首先,通过openInputChannelPair来创建socket pair,作为InputChannel:
- socket服务端保存到system_server中的WindowState的mInputChannel;
- socket客户端通过binder传回到远程进程的UI主线程ViewRootImpl的mInputChannel;
紧接着,完成了两个线程的epoll监听工作:
- [小节2.8]IMS.registerInputChannel(): “InputDispatcher”线程监听socket服务端,收到消息后回调InputDispatcher.handleReceiveCallback();
- [小节3.6]setFdEvents(): UI主线程监听socket客户端,收到消息后回调NativeInputEventReceiver.handleEvent().
有了这些“InputDispatcher”和“UI”主线程便可以进行跨进程通信与交互。