AndroidR Input子系统总结

Input子系统的启动

  1. SystemServer创建InputManagerService这个系统服务。

  2. InputManagerService构造方法中创建”android.display”线程,调用nativeInit函数,将”android.display”线程的Looper对应的MessageQueue传递到native层。

  3. nativeInit函数中创建NativeInputManager对象,并将其指针返回到java层mPtr保存。

  4. NativeInputManager构造函数中创建InputManager对象,并将其注册到ServiceManager,其服务名称为:“inputflinger”,InputManager构造函数中创建三个重要对象:InputDispatcher,InputClassifier,InputReader,比较重要的是在构造InputReader是创建了EventHub对象。

  5. EventHub构造函数中通过inotify和epoll机制对目录”/dev/input”监听,主要监听此目录下文件的创建和删除,到此nativeInit函数完毕。

  6. SystemServer中会接着调用InputManagerService的start方法,此方法中调用nativeStart作进一步初始化,nativeStart函数中调用InputManager的start函数。

  7. InputManager的start函数中分别调用了InputDispatcher和InputReader的start函数,即分别启动了其内部线程InputThreadImpl,InputDispatcher内部线程(名字:“InputDispatcher”)启动调用了自己的dispatchOnce()函数,InputReader内部线程(名字:“InputReader”)启动调用了自己的loopOnce()函数。

InputReader线程读取输入事件

InputReader对input事件处理过程大致可以分为三大步:

  1. EventHub通过INotify与Epoll监听/dev/input下的事件,在读取到事件之后放入mEventBuffer,此步骤将input_event转换为了RawEvent。
  2. 拿到原始事件RawEvent之后调用processEventsLocked对事件进行加工,不同事件类型有不同的加工厂(InputMapper),此步骤将RawEvent转换为了NotifyKeyArgs。
  3. 通过QueuedListener的flush函数将事件发送到InputDispatcher线程。

InputDispatcher线程分发输入事件

InputDispatcher对事件的分发流程是相当复杂的,仅对最简单的按键类型事件进行分析,同时分析过程省略了相当对细节的处理,我们做的是把握整体架构,现在就对这个过程做一个总结:

  1. InputReader线程将驱动获取的原始输入事件封装为NotifyKeyArgs传递给了InputDispatcher线程,在放入InputDispatcher线程mInboundQueue队列之前会先将事件传递到java层PhoneWindowManager,没有被拦截的情况下才会将NotifyKeyArgs转换为KeyEntry并放入mInboundQueue队列,接着会唤醒InputDispatcher线程。
  2. InputDispatcher线程启动后就陷入了Looper死循环,等待输入事件的发生,被唤醒之后调用函数dispatchOnceInnerLocked处理事件,此函数在一些列判断之后没有丢弃事件则会进一步调用dispatchKeyLocked函数。
  3. dispatchKeyLocked函数在分发之前又会首先将按键事件传到java层PhoneWindowManager的interceptKeyBeforeDispatching中给个提前拦截的机会,如果没有被拦截则会通过findFocusedWindowTargetsLocked找到目标焦点窗口。
  4. findFocusedWindowTargetsLocked函数会从两个容器mFocusedWindowHandlesByDisplay和mFocusedApplicationHandlesByDisplay获得当前的焦点窗口和焦点应用,并且会对可能出现ANR的情况进行ANR timeout即ANR发生窗口的标记。
  5. 如果findFocusedWindowTargetsLocked函数返回结果为成功分发,则调用dispatchEventLocked函数继续分发输入事件,接着会将KeyEntry再转换为DispatchEntry,并存入目标窗口连接connection的outboundQueue队列,然后调用publishKeyEvent继续分发。
  6. publishKeyEvent函数中构造描述输入事件信息的InputMessage并通过InputChannel向”server”端socket写入数据以唤醒APP进程的socket”client”端,自此输入事件成功从InputDispatcher发送到了APP。
  7. 最后将DispatchEntry从目标窗口连接connection的outboundQueue队列中移除,并转移到目标窗口连接connection的waitQueue队列中。

整个过程中有三个重要队列,mInboundQueue,outboundQueue,waitQueue。
mInboundQueue位于InputDispatcher线程,代表即将分发的输入事件,outboundQueue位于目标窗口的connection,代表即将要分发给目标窗口的输入事件,waitQueue位于目标窗口的connection,代表等待目标窗口处理的输入事件。

InputChannel注册,建立APP和InputDispatcher的连接

  1. 首先当一个APP启动时,会将自己的Window添加到WMS,并传递一个空InputChannel过去。

  2. WMS端,通过openInputChannel方法会创建一对InputChannel,是在native层完成的,这对InputChannel被分为“client”端和“server”端,其内部又会创建一对socket,和这对InputChannel一一对应。

  3. “server”端InputChannel会被注册到InputDispatcher中去,注册的原理就是将InputChannel内部的socket添加到其Looper进行监听,注册过程中还会创建一个Connection对象,Connection用来描述InputDispatcher与此次注册InputChannel的窗口的连接。

  4. “client”端InputChannel会被设置到APP进程中,接着通过InputEventReceiver注册到APP UI线程,同样是将InputChannel内部的socket添加到UI线程的Looper进行监听。

  5. 对于InputDispatcher线程,在接收到”client”端socket的消息时会回调其handleReceiveCallback函数,对于APP UI线程,在接收到”server”端socket的消息时会回调InputEventReceiver对应的native层对象NativeInputEventReceiver的handleEvent函数。

UI线程对Input事件的分发与结束处理

  1. 首先InputDispatcher通过server端InputChannel将输入事件发送给应用程序的client端。

  2. client端收到输入事件,会在UI线程中调用向Looper注册的回调handleEvent,handleEvent主要是通过consumeEvents进一步处理事件。

  3. 输入事件在native层被转换为KeyEvent或者MotionEvent后会发送到java层InputEventReceiver的dispatchInputEvent方法,实际上最终是送到其子类WindowInputEventReceiver的onInputEvent方法来处理。

  4. ViewRootImpl作为Android上层事件分发的起点,其中定义了多种InputStage来将事件分类处理,采用责任链模式,将每个InputStage实现类通过mNext变量连接起来,InputStage通过deliver分发事件,通过onProcess处理事件,通过forward向mNext传递事件.

  5. 当mNext指向null时则会调用finishInputEvent结束事件,之后会调到native层的InputConsumer的sendFinishedSignal函数,最终还是通过client端InputChannel的sendMessage通知InputDispatcher事件已经处理完成。

  6. InputDispatcher这边的收尾工作主要就是将此事件从waitQueue移除并重置ANR时间。

View的事件分发机制

对于View来说主要使用ViewPostImeInputStage这种类型的InputStage来处理触摸事件。

输入事件从native层到ViewRootImpl之后会经过:DecorView->Activity->PhoneWindow->DecorView->ViewGroup的流程

ViewGroup的dispatchTouchEvent方法进行总结:

  1. 首先会调用onFilterTouchEventForSecurity方法对此次事件进行初步判断,判断的是ViewGroup是否被遮挡或者隐藏。

  2. 步骤1的条件判断通过之后,接着对于ACTION_DOWN事件会清空上一个事件序列(一个事件序列通常由一个ACTION_DOWN,N个ACTION_MOVE,一个ACTION_UP组成)留下的各种状态,最主要是清空TouchTarget链表。

  3. 接着会有两个条件来判断是否走ViewGroup的拦截机制,条件1:此次事件是否为ACTION_DOWN,条件2:mFirstTouchTarget是否为空,这两个条件的意思是对于一个事件序列的ACTION_DOWN事件一定会走ViewGroup的拦截机制,并且同一事件序列的一个事件如果被拦截了,那么后续事件默认都会被拦截而不会再走拦截方法onInterceptTouchEvent,子View可以通过requestDisallowInterceptTouchEvent方法请求父View不要拦截。

  4. 接着又会两个条件来判断事件事件继续分发,canceled和intercepted,事件被取消和被拦截,其实canceled多半是因为intercepted导致的,这个后面再说。

  5. 对于没有被取消且没有被拦截,且是ACTION_DOWN的事件就要开始遍历View树找到真正消费事件的子View了,这里为何会单独对ACTION_DOWN进行判断呢?这是因为一个事件序列可能包含多个触摸事件,而触摸事件寻找消费的子View是通过递归遍历View树,为了性能考虑,Android的设计为:当接收到ACTION_DOWN时开始对View树进行遍历,找到最终消费事件的子View之后将其保存,同一事件序列的后续ACTION_MOVE,ACTION_UP则不再需要遍历,直接将事件发送给保存好的子View就行了,对子View的保存就用到了TouchTarget,这是一种链表结构,后面再说。

  6. 对于目标子View的寻找就比较简单了,首先将当前ViewGroup的所有子View以Z-order的顺序进行重建,保存在一个list中,然后遍历list,从Z-order最大的子View开始,遍历条件有两个:当前遍历的子View可以接收事件,并且触摸区域落在当前子View之内则说明成功找到子View,然后调用dispatchTransformedTouchEvent执行子View事件处理流程,如果事件成功处理则会为此子View构建TouchTarget,并赋值给mFirstTouchTarget。

  7. 接着对于mFirstTouchTarget不为空的情况会遍历链表,其目的是步骤6已经找到接收事件的目标子View并且保存到了TouchTarget链表,对于一个事件序列的后续事件只需要遍历链表分发事件就行了。

  8. 对于没有找到消费事件的子View即mFirstTouchTarget为空,以及事件被取消的情况会做一些收尾工作。