Input子系统的启动
SystemServer创建InputManagerService这个系统服务。
InputManagerService构造方法中创建”android.display”线程,调用nativeInit函数,将”android.display”线程的Looper对应的MessageQueue传递到native层。
nativeInit函数中创建NativeInputManager对象,并将其指针返回到java层mPtr保存。
NativeInputManager构造函数中创建InputManager对象,并将其注册到ServiceManager,其服务名称为:“inputflinger”,InputManager构造函数中创建三个重要对象:InputDispatcher,InputClassifier,InputReader,比较重要的是在构造InputReader是创建了EventHub对象。
EventHub构造函数中通过inotify和epoll机制对目录”/dev/input”监听,主要监听此目录下文件的创建和删除,到此nativeInit函数完毕。
SystemServer中会接着调用InputManagerService的start方法,此方法中调用nativeStart作进一步初始化,nativeStart函数中调用InputManager的start函数。
InputManager的start函数中分别调用了InputDispatcher和InputReader的start函数,即分别启动了其内部线程InputThreadImpl,InputDispatcher内部线程(名字:“InputDispatcher”)启动调用了自己的dispatchOnce()函数,InputReader内部线程(名字:“InputReader”)启动调用了自己的loopOnce()函数。
InputReader线程读取输入事件
InputReader对input事件处理过程大致可以分为三大步:
- EventHub通过INotify与Epoll监听/dev/input下的事件,在读取到事件之后放入mEventBuffer,此步骤将input_event转换为了RawEvent。
- 拿到原始事件RawEvent之后调用processEventsLocked对事件进行加工,不同事件类型有不同的加工厂(InputMapper),此步骤将RawEvent转换为了NotifyKeyArgs。
- 通过QueuedListener的flush函数将事件发送到InputDispatcher线程。
InputDispatcher线程分发输入事件
InputDispatcher对事件的分发流程是相当复杂的,仅对最简单的按键类型事件进行分析,同时分析过程省略了相当对细节的处理,我们做的是把握整体架构,现在就对这个过程做一个总结:
- InputReader线程将驱动获取的原始输入事件封装为NotifyKeyArgs传递给了InputDispatcher线程,在放入InputDispatcher线程mInboundQueue队列之前会先将事件传递到java层PhoneWindowManager,没有被拦截的情况下才会将NotifyKeyArgs转换为KeyEntry并放入mInboundQueue队列,接着会唤醒InputDispatcher线程。
- InputDispatcher线程启动后就陷入了Looper死循环,等待输入事件的发生,被唤醒之后调用函数dispatchOnceInnerLocked处理事件,此函数在一些列判断之后没有丢弃事件则会进一步调用dispatchKeyLocked函数。
- dispatchKeyLocked函数在分发之前又会首先将按键事件传到java层PhoneWindowManager的interceptKeyBeforeDispatching中给个提前拦截的机会,如果没有被拦截则会通过findFocusedWindowTargetsLocked找到目标焦点窗口。
- findFocusedWindowTargetsLocked函数会从两个容器mFocusedWindowHandlesByDisplay和mFocusedApplicationHandlesByDisplay获得当前的焦点窗口和焦点应用,并且会对可能出现ANR的情况进行ANR timeout即ANR发生窗口的标记。
- 如果findFocusedWindowTargetsLocked函数返回结果为成功分发,则调用dispatchEventLocked函数继续分发输入事件,接着会将KeyEntry再转换为DispatchEntry,并存入目标窗口连接connection的outboundQueue队列,然后调用publishKeyEvent继续分发。
- publishKeyEvent函数中构造描述输入事件信息的InputMessage并通过InputChannel向”server”端socket写入数据以唤醒APP进程的socket”client”端,自此输入事件成功从InputDispatcher发送到了APP。
- 最后将DispatchEntry从目标窗口连接connection的outboundQueue队列中移除,并转移到目标窗口连接connection的waitQueue队列中。
整个过程中有三个重要队列,mInboundQueue,outboundQueue,waitQueue。
mInboundQueue位于InputDispatcher线程,代表即将分发的输入事件,outboundQueue位于目标窗口的connection,代表即将要分发给目标窗口的输入事件,waitQueue位于目标窗口的connection,代表等待目标窗口处理的输入事件。
InputChannel注册,建立APP和InputDispatcher的连接
首先当一个APP启动时,会将自己的Window添加到WMS,并传递一个空InputChannel过去。
WMS端,通过openInputChannel方法会创建一对InputChannel,是在native层完成的,这对InputChannel被分为“client”端和“server”端,其内部又会创建一对socket,和这对InputChannel一一对应。
“server”端InputChannel会被注册到InputDispatcher中去,注册的原理就是将InputChannel内部的socket添加到其Looper进行监听,注册过程中还会创建一个Connection对象,Connection用来描述InputDispatcher与此次注册InputChannel的窗口的连接。
“client”端InputChannel会被设置到APP进程中,接着通过InputEventReceiver注册到APP UI线程,同样是将InputChannel内部的socket添加到UI线程的Looper进行监听。
对于InputDispatcher线程,在接收到”client”端socket的消息时会回调其handleReceiveCallback函数,对于APP UI线程,在接收到”server”端socket的消息时会回调InputEventReceiver对应的native层对象NativeInputEventReceiver的handleEvent函数。
UI线程对Input事件的分发与结束处理
首先InputDispatcher通过server端InputChannel将输入事件发送给应用程序的client端。
client端收到输入事件,会在UI线程中调用向Looper注册的回调handleEvent,handleEvent主要是通过consumeEvents进一步处理事件。
输入事件在native层被转换为KeyEvent或者MotionEvent后会发送到java层InputEventReceiver的dispatchInputEvent方法,实际上最终是送到其子类WindowInputEventReceiver的onInputEvent方法来处理。
ViewRootImpl作为Android上层事件分发的起点,其中定义了多种InputStage来将事件分类处理,采用责任链模式,将每个InputStage实现类通过mNext变量连接起来,InputStage通过deliver分发事件,通过onProcess处理事件,通过forward向mNext传递事件.
当mNext指向null时则会调用finishInputEvent结束事件,之后会调到native层的InputConsumer的sendFinishedSignal函数,最终还是通过client端InputChannel的sendMessage通知InputDispatcher事件已经处理完成。
InputDispatcher这边的收尾工作主要就是将此事件从waitQueue移除并重置ANR时间。
View的事件分发机制
对于View来说主要使用ViewPostImeInputStage
这种类型的InputStage
来处理触摸事件。
输入事件从native层到ViewRootImpl
之后会经过:DecorView
->Activity
->PhoneWindow
->DecorView
->ViewGroup
的流程
ViewGroup的dispatchTouchEvent
方法进行总结:
首先会调用onFilterTouchEventForSecurity方法对此次事件进行初步判断,判断的是ViewGroup是否被遮挡或者隐藏。
步骤1的条件判断通过之后,接着对于ACTION_DOWN事件会清空上一个事件序列(一个事件序列通常由一个ACTION_DOWN,N个ACTION_MOVE,一个ACTION_UP组成)留下的各种状态,最主要是清空TouchTarget链表。
接着会有两个条件来判断是否走ViewGroup的拦截机制,条件1:此次事件是否为ACTION_DOWN,条件2:mFirstTouchTarget是否为空,这两个条件的意思是对于一个事件序列的ACTION_DOWN事件一定会走ViewGroup的拦截机制,并且同一事件序列的一个事件如果被拦截了,那么后续事件默认都会被拦截而不会再走拦截方法onInterceptTouchEvent,子View可以通过requestDisallowInterceptTouchEvent方法请求父View不要拦截。
接着又会两个条件来判断事件事件继续分发,canceled和intercepted,事件被取消和被拦截,其实canceled多半是因为intercepted导致的,这个后面再说。
对于没有被取消且没有被拦截,且是ACTION_DOWN的事件就要开始遍历View树找到真正消费事件的子View了,这里为何会单独对ACTION_DOWN进行判断呢?这是因为一个事件序列可能包含多个触摸事件,而触摸事件寻找消费的子View是通过递归遍历View树,为了性能考虑,Android的设计为:当接收到ACTION_DOWN时开始对View树进行遍历,找到最终消费事件的子View之后将其保存,同一事件序列的后续ACTION_MOVE,ACTION_UP则不再需要遍历,直接将事件发送给保存好的子View就行了,对子View的保存就用到了TouchTarget,这是一种链表结构,后面再说。
对于目标子View的寻找就比较简单了,首先将当前ViewGroup的所有子View以Z-order的顺序进行重建,保存在一个list中,然后遍历list,从Z-order最大的子View开始,遍历条件有两个:当前遍历的子View可以接收事件,并且触摸区域落在当前子View之内则说明成功找到子View,然后调用dispatchTransformedTouchEvent执行子View事件处理流程,如果事件成功处理则会为此子View构建TouchTarget,并赋值给mFirstTouchTarget。
接着对于mFirstTouchTarget不为空的情况会遍历链表,其目的是步骤6已经找到接收事件的目标子View并且保存到了TouchTarget链表,对于一个事件序列的后续事件只需要遍历链表分发事件就行了。
对于没有找到消费事件的子View即mFirstTouchTarget为空,以及事件被取消的情况会做一些收尾工作。