系列文章目录
ANR原理篇 - service/broadcast/provider超时机制
文章目录
前言
一、Input系统概述
当用户触摸屏幕或者按键操作,首次触发的是硬件驱动,驱动收到事件后,将该相应事件写入到输入设备节点, 这便产生了最原生态的内核事件。接着,输入系统取出原生态的事件,经过层层封装后成为KeyEvent或者MotionEvent ;最后,交付给相应的目标窗口(Window)来消费该输入事件。可见,输入系统在整个过程起到承上启下的衔接作用。
Input模块的主要组成:
Native层的InputReader负责从EventHub取出事件并处理,再交给InputDispatcher;
Native层的InputDispatcher接收来自InputReader的输入事件,并记录WMS的窗口信息,用于派发事件到合适的窗口;
Java层的InputManagerService跟WMS交互,WMS记录所有窗口信息,并同步更新到IMS,为InputDispatcher正确派发事件到ViewRootImpl提供保障;
二、整体框架
1.整体框架类图
2.核心启动过程
InputManager.cpp 核心代码如下:
2.1 initialize
void InputManager::initialize() {
//创建线程“InputReader”
mReaderThread = new InputReaderThread(mReader);
//创建线程”InputDispatcher“
mDispatcherThread = new InputDispatcherThread(mDispatcher);
}
InputReaderThread::InputReaderThread(const sp<InputReaderInterface>& reader) :
Thread(/*canCallJava*/ true), mReader(reader) {
}
InputDispatcherThread::InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher) :
Thread(/*canCallJava*/ true), mDispatcher(dispatcher) {
}
初始化的主要工作就是创建两个能访问Java代码的native线程:
创建线程“InputReader”
创建线程”InputDispatcher“
2.1 InputManager.start
status_t InputManager::start() {
result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
...
return OK;
}
该方法的主要功能是启动两个线程:
启动线程“InputReader”
启动线程”InputDispatcher“
三、InputReader线程
3.1 EventHuab
EventHub采用INotify + epoll机制实现监听目录/dev/input下的设备节点
3.2 InputReader核心流程
InputReader整个过程涉及多次事件封装转换,其主要工作核心是以下三大步骤:
- getEvents:通过EventHub(监听目录/dev/input)读取事件放入mEventBuffer,而mEventBuffer是一个大小为256的数组, 再将事件input_event转换为RawEvent;
- processEventsLocked: 对事件进行加工, 转换RawEvent -> NotifyKeyArgs(NotifyArgs)
- QueuedListener->flush:将事件发送到InputDispatcher线程, 转换NotifyKeyArgs -> KeyEntry(EventEntry)
InputReader线程不断循环地执行InputReader.loopOnce(), 每次处理完生成的是EventEntry(比如KeyEntry, MotionEntry), 接下来的工作就交给InputDispatcher线程。
四、InputDispatcher线程
4.1 核心方法
用一张图来整体概况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()方式向该队列添加的。
InputDispatcher中有一个线程,死循环执行dispatchOnce方法,该方法负责分发消息给APP侧,以及接受APP侧的返回并执行相关以后,最后执行ANR的判断。所以dispatchOnce属于整个的流程的核心,也是ANR处理流程的核心,所以我们重点了解下dispatchOnce这个方法。如下图所示:
dispatchOnce中主要完成四件事:
- dispatchOnceInnerLocked()方法负责把收到的输入信号分发给APP处理,发送成功会加入到InputDispatcher里的waitQueue队列和AnrTracker.cpp里的mAnrTimeouts。
- haveCommandsLocked()中查看队列中是否有任务,如果有就执行任务。这些任务就是执行doDispatchCycleFinishedCommand方法,该方法中,会根据收到的完成信号,完成对应的事件从waitQueue和mAnrTimeouts中移除的处理。
- processAnrsLocked中会进行一些逻辑判断,如果符合条件,则会触发ANR流程。
- pollOnce进入休眠,等待下一次的循环。
4.2 小节
InputReader读到输入事件后,就会传递到InputDispatcher,整个超时流程也都是由其负责的。InputDispatcher中主要是dispatchOnce方法来负责,它跑在单独的线程上。
首先它把收到的输入信号分发给APP一侧并记录到mAnrTimeouts集合上;
然后查看是否有APP侧传递过来的任务,如果有就执行,该任务就是把对应的输入事件从mAnrTimeouts集合中移除;
再然后判断mAnrTimeouts集合中是否有超时的事件,如果有就走ANR逻辑;
最后,一轮逻辑走完了,进入休眠,等待下一轮的唤醒。
五、Input系统之UI线程
在InputDispatcher的过程调用到InputChanel通过socket与远程进程通信,这里看下这个socket是如何建立的。
对于InputReader和InputDispatcher都是运行在system_server进程; 用户点击的界面往往可能是某一个app,而每个app一般地都运行在自己的进程,这里就涉及到跨进程通信,app进程是如何与system进程建立通信。
要解答这些问题,就要从Activity最基本的创建过程寻找。我们都知道一般地Activity对应一个应用窗口, 每一个窗口对应一个ViewRootImpl。
ViewRootImpl的setView()过程:
创建socket pair,作为InputChannel:
- socket服务端保存到system_server中的WindowState的mInputChannel;
- socket客户端通过binder传回到远程进程的UI主线程ViewRootImpl的mInputChannel;
IMS.registerInputChannel()注册InputChannel,监听socket服务端:
- Loop便是“InputDispatcher”线程的Looper;
- 回调方法handleReceiveCallback。
用一张图来整体概况UI线程跨进程通信的主要工作:
首先,通过openInputChannelPair来创建socket pair,作为InputChannel:
- socket服务端保存到system_server中的WindowState的mInputChannel;
- socket客户端通过binder传回到远程进程的UI主线程ViewRootImpl的mInputChannel;
紧接着,完成了两个线程的epoll监听工作:
- IMS.registerInputChannel(): “InputDispatcher”线程监听socket服务端,收到消息后回调InputDispatcher.handleReceiveCallback();
- setFdEvents(): UI主线程监听socket客户端,收到消息后回调NativeInputEventReceiver.handleEvent().
有了这些“InputDispatcher”和“UI”主线程便可以进行跨进程通信与交互。
六、Input事件处理全过程
6.1 整体框架图
6.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)中移除.
总结
简单总结和回顾以上文章的内容:
- InputReader线程:通过EventHub从/dev/input节点获取事件,转换成EventEntry事件加入到InputDispatcher的mInboundQueue。
- InputDispatcher线程:从mInboundQueue队列取出事件,转换成DispatchEntry事件加入到connection的outboundQueue队列。再然后开始处理分发事件,取出outbound队列,放入waitQueue.
- UI线程:创建socket pair,分别位于”InputDispatcher”线程和focused窗口所在进程的UI主线程,可相互通信:
- UI主线程:通过setFdEvents(), 监听socket客户端,收到消息后回调NativeInputEventReceiver();
- “InputDispatcher”线程: 通过IMS.registerInputChannel(),监听socket服务端,收到消息后回调handleReceiveCallback;