系统版本: Ubuntu 22.04 lts
AOSP分支: android-14.0.0_r28
Android的触摸事件是如何获取的
平常我们在应用层开发的时候,当我们想去处理view
的触摸事件,可能会写以下的代码:
Kotlin
mView.setOnTouchListener(object : OnTouchListener {
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
}
})
然后根据获取的MotionEvent
去处理具体的触摸事件,达成我们想要的效果,那么问题来了,从手指去触摸手机屏幕开始,事件是怎么传递到自定义View的onTouch
这里的?
这其实就是Inputflinger
的工作了。
首先打开adb shell
, 然后输入getevent -lrt
,可以看到如下界面:
这个时候我们使用手指滑动屏幕,就可以看到如下的打印:
这里,其实就是驱动层输出的数据了,我们的Inputflinger
在触摸这里的处理,就是要将我们从驱动层获取的这些数据,最终发送到我们触摸的View中去,然后去处理我们的上层逻辑,比如滑动,点击等。
Android驱动层协议
Android触摸相关的官方文档,可以看这里:
从文档中我们可以得知,Android的多点触控基于的是Linux的multi-touch-protocol
,这里可以看到具体的协议文档:
在官方文档中,我们可以看到驱动层传输的这些数据都代表着什么:
现在,我们清楚了这些之后,就可以看看,Android是怎么初步帮我们去解析并分发这些数据的。
启动InputManagerService
首先,我们先打开/frameworks/base/services/java/com/android/server/SystemServer.java
,可以看到InputManagerService
的初始化和启动相关的代码:
我们先来看InputManagerService
的初始化方法:
可以看到里面有个mNative
,从上面的代码,我们可以得知这个mNative
是NativeInputManagerService
类型,我们再看mNative
的初始化:
可以看到,最终的实现都是Native方法,然后在初始化的时候就调用了Native方法init
,我们再打开/frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
,就可以看到Nativeinit
的具体实现:
可以看到里面初始化了NativeInputManager
,我们再去它的初始化方法看一看:
可以看到这里创建了一个Native层的InputManager
,并且将它添加为了一个Binder服务,我们再看InputManager
的初始化方法:
可以看到这里初始化了四个对象mReader
,mBlocker
,mProcessor
和mDispatcher
,这里面的Blocker
和Processor
,其实都是过滤事件的,我们重点要关注的其实是mReader
和mDispatcher
,因为他们负责的就是事件的读取与分发,并且可以看到,这里使用的是责任链的模式去一层层的传递事件数据。
这里的初始化我们先略过,接下来我们回到/frameworks/base/services/java/com/android/server/SystemServer.java
,可以看到接下来就是启动我们的InputManagerService
:
我们再进入start
方法,可以看到最终调用的还是Native得start
方法:
可以看到,这里先调用的是mDispatcher
的start
,然后才是mReader
的start
,我们先看mReader
的start
:
可以看到这里就是开启了一个线程,并且最终不断调用自身的loopOnce
,那么这个loopOnce
里面,其实就是我们最终InputReader
去读取驱动事件的主要逻辑了。
加载并读取驱动数据
首先,我们进入loopOnce
方法,可以看到这里调用了mEventHub
的getEvents
方法:
在此方法中找到如下代码,可以看到这里调用了scanDevicesLocked
:
我们再进入此方法:
这个方法的主要作用,就是扫描我们的输入驱动设备了,可以看到目录扫描的DEVICE_INPUT_PATH
的值为/dev/input
如果对Linux系统有了解的话就会知道这个目录就是Linux的输入子系统,不光是触摸屏,键盘,手柄等其它输入设备也会挂载到这个目录下面。
先进入scanDirLocked
:
然后再进入openDeviceLocked
:
可以看到这个方法,就是用open
和ioctl
等系统调用去打开我们的驱动设备了,并且最终,会转换成一个Device
对象,并存储到mOpeningDevices
中。
打开设备之后,会进入第一段循环,这里会取出所有mOpeningDevices
,往要返回的事件中添加打开设备事件,并且把Device
实例添加到mDevice
里面:
然后再下面是这段:
这段,是往要返回的事件中添加扫描设备结束事件。
再然后,是第二段循环:
注意,这里当我们第一次进入的时候,是不会进来的,因为这个时候我们的mPendingEventCount
是0,所以继续往下看:
到这里,我们就会直接返回设备扫描和添加的相关事件,这里我们先跳过,因为我们知道InputThread
会不断的运行loopOnce
,那么当我们第二次进入这里的时候,因为我们的设备已经扫描过了,所以会跳过这一段,接着往下运行:
这里,线程就调用了epoll_wait
进入等待状态了,然后当我们读取到触摸事件的时候,这个时候就会继续往下运行并且将mPendingEventCount
置为非0
,并且再次循环,并且会进入到刚才提到的第二段循环中,进行epoll
事件的处理,进入此循环,直接看这段代码:
可以看到这里就会把获取到的触摸事件添加到events
里,然后再通过后面相同的逻辑把事件返回回去:
InputReader加工数据
现在,我们已经从驱动层获取到了触摸数据,但是现在的数据是RawEvent
,是非常原始的数据,所以下一步,我们就要对获取的数据进行加工:
回到InputReader
的loopOnce
方法,可以看到接下来就调用了processEventsLocked
,我们进入此方法:
可以看到,首先,我们处理数据之后返回的对象是是一个NotifyArgs
的list
,NotifyArgs
具体的类型可能会是这些:
然后第一步,会先取出RawEvent
的type
,并进行判断type < EventHubInterface::FIRST_SYNTHETIC_EVENT
,首先这个判断条件大概率是true
的,因为我们触摸的时候返回的type
基本上都是EV_ABS
,EV_KEY
之类的,在/bionic/libc/kernel/uapi/linux/input-event-codes.h
可以看到这些type
的值都很低:
而FIRST_SYNTHETIC_EVENT
的值却很高:
所以当我们触摸的时候,这里的判断基本都是true
。
然后再看里面的处理,是调用了processEventsForDeviceLocked
:
然后再下面,就是InputDevice
的process
方法:
可以看到,这里最终调用的就是mapper
的process
,那么这是什么意思呢?
Mapper的工作方式
打开/frameworks/native/services/inputflinger/reader/mapper/
,我们可以看到很多的Mapper
文件:
这些Mapper
文件,最主要的作用其实就是转换我们的RawEvent
的,他们会根据不同的输入协议,采用不同的逻辑,最后将我们的RawEvent
转换成不同的NotifyArgs
,比如我们想关心的是触摸屏的输入处理,那我们就打开MultiTouchInputMapper.cpp
看下它的处理:
具体的代码十分复杂,有兴趣可以自己看看,里面实现的各个方法其实就是对驱动层输入的数据的各种校准,储存,和计算,最后生成数据供更上层处理,建议结合协议文档去看,比如里面Slot
的概念,其实就是来自于协议中的概念:
传递事件给InputDispacher
我们再回到InputReader
的loopOnce
,可以看到当我们的notifyArgs
准备好的时候,就会调用notifyAll
:
这个mQueuedListener
,其实就是InputManager
初始化的时候传进来的mBlocker
,因为这里使用了责任链模式,事件最终经过过滤会传递给InputManager
的mDispacher
,我们再看下notify
方法:
可以看到,不同的NotifyArgs
会调用不同的notify
方法,因为我们传递进来的是触摸事件,也就是NotifyMotionArgs
,所以最后调用的其实是mDispacher
的notifyMotion
方法。
那么,接下来我们再进入/frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
的notifyMotion
,略过上面的一些打印log和检查的逻辑,直接进入方法的后面:
可以看到,这部分的逻辑主要就是用传入进来的NotifyMotionArgs
转换成一个MotionEntry
,之后调用enqueueInboundEventLocked
方法,然后进入此方法,可以看到这里主要就是将此MotionEntry
push给mInboundQueue
:
注意,MotionEntry
是EventEntry
的子类,mInboundQueue
后续处理的时候,都是使用EventEntry
来进行处理的
那么接下来,InputDispatcher
是怎么处理的呢?
InputDispatcher的分发处理
接下来,我们回到Native端的InputManager
,找到它的start
方法:
可以看到,在调用InputReader
的start
之前,InputManager
先调用了InputDispatcher
的start
方法,我们进入此方法:
可以看到这里所做的,就是新开始一个线程,然后不停调用dispatchOnce
方法,我们进入此方法:
可以看到里面调用了dispatchOnceInnerLocked
,我们再进入此方法,然后跳转到以下这段:
可以看到这里的逻辑,就是从mInboundQueue
取出一个EventEntry
,然后将它赋值给mPendingEvent
,接下来,就会对mPendingEvent
的type
进行判断:
我们直接进入Motion
部分:
可以看到,这里调用了dispatchMotionLocked
,我们进入此方法,找到如下代码段:
这部分的逻辑,就是找到我们的目标窗口,也就是我们事件分发的最终目的地,最后再执行真正的分发方法dispatchEventLocked
:
可以看到,这里的逻辑,就是根据我们的InputTarget
中的inputChannel
的ConnectionToken
,找到我们对应的Connection
:
然后再调用prepareDispatchCycleLocked
,这里的逻辑,我先按下不表,目前只需要知道Connection
这里是都能取到值就行,因为InputChannel
涉及到WMS
。
我们再进入prepareDispatchCycleLocked
,可以看到最后调用的是enqueueDispatchEntriesLocked
:
我们可以看到,这里先调用了6次enqueueDispatchEntryLocked
,最后调用startDispatchCycleLocked
,我们先进入enqueueDispatchEntryLocked
,可以看到它的主要逻辑就是根据我们的EventEntry
,创建了一个DispatchEntry
,然后将此DispatchEntry
添加给对应Connection
的outboundQueue
中:
然后我们再进入startDispatchCycleLocked
,可以看到里面调用了publishMotionEvent
:
然后可以看到,最终里面调用的就是对应Connection
的inputPublisher
的publishMotionEvent
:
我们再进入此方法,可以看到这里又将事件封装成了一个InputMessage
,最后调用了InputChannel
的sendMessage
:
再进入此方法,可以看到事件最终通过Socket
的方式发送出去了:
到这一步,我们的问题就来了,最终这个事件到底会被谁接收? 还有上面提到的那些InputChannel
,Connection
到底是什么? 那么我们接下来就要找到这些问题的答案。
WMS注册InputChannel
首先,我们要知道我们的Activity的根View实际上是一个DecorView
,当我们想要去显示我们的界面的时候,需要去通过WindowManager
去执行addView
方法,这个方法最终执行的,是WindowManagerGlobal
的addView
方法,而这个方法里面会创建一个ViewRootImpl
,最后会调用它的setView
,这个方法中,我们可以找到如下代码:
我们可以看到,在这个方法里,初始化了一个InputChannel
,并且将这个InputChannel
传递给了这个方法addToDisplayAsUser
,我们再跳转到/frameworks/base/services/core/java/com/android/server/wm/Session.java
,找到这个方法,可以看到这里调用的是WMS
的addWindow
:
我们进入此方法,可以看到这里又调用了openInputChannel
:
在openInputChannel
中,调用了InputManagerService
的createInputChannel
:
在其中,又调用了NativeInputManagerService
的createInputChannel
接下来我们再回到Native层:
可以看到,这里最终又调用回到了InputDispatcher
的createInputChannel
:
我们先来看openInputChannelPair
:
可以看到,这个方法主要就是创建了一个socketPair
和一个BBinder
,并且将fd
和创建的binder
传给新创建的两个InputChannel
,分别代表服务端和客户端。
我们再回到createInputChannel
,可以看到接下来就是根据服务端的InputChannel
创建一个Connection
,并且将它保存到InputDispatcher
的mConnectionsByToken
,最后再将客户端的InputChannel
传递回去。
最后,这个客户端的InputChannel
会被保存在应用层,至此,我们就完成了应用层的Window
和InputDispatcher
的联系,数据最终会通过Socket
的方式发送给不同进程中的应用。
InputReceiver的创建与初始化
接下来,我们再回到ViewRootImpl
的setView
,找到如下代码块:
这个时候,我们已经知道了,这块的inputChannel
是SocketPair
通信中代表客户端的InputChannel
,在这段代码中,我们可以看到mInputEventReceiver
被初始化为了一个WindowInputEventReceiver
,进入此类,我们可以看到它继承了InputEventReceiver
,进入InputEventReceiver
的构造方法:
可以看到这里调用nativeInit
,并且将InputChannel
和MessageQueue
都传了进去,接下来我们再打开/frameworks/base/core/jni/android_view_InputEventReceiver.cpp
,找到nativeInit
方法:
可以看到这里创建了一个NativeInputEventReceiver
,并且调用了它的initialize
,我们进入此方法:
可以看到这里调用了setFdEvents
,我们再进入此方法:
可以看到它是将InputChannel
的fd
传给了Looper
,并且设置了callBack
为当前的NativeInputEventReceiver
,也就是说当通过Socket
接收到数据时,最终会调用当前NativeInputEventReceiver
的handleEvent
,因为我们的NativeInputEventReceiver
是一个LooperCallback
的子类:
也就是说,当我们的触摸事件通过Socket
的方式传输出去的时候,最终调用的,就是NativeInputEventReceiver
的handleEvent
。
InputReceiver处理事件
我们进入handleEvent
,可以看到里面调用了consumeEvents
:
进入此方法,找到如下代码块:
可以看到它先调用了mInputConsumer
的consume
,mInputConsumer
是在NativeInputReceiver
初始化的时候根据InputChannel
创建的,而它的consume
方法主要就是为了用InputChannel
通过Socket
的方式读取到传输过来的触摸数据,并且将其写入到InputEvent
里面:
接下来我们回到consumeEvents
往下看,可以看到在这创建了一个inputEventObj
,并且将上面获取的InputEvent
转换为MotionEvent
,并且将其赋值给inputEventObj
:
最后,我们就可以看到这个inputEventObj
会被传递给Java层并回调其dispatchInputEvent
方法:
我们再回到Java层的InputEventReceiver
,找到对应的dispatchInputEvent
方法,可以看见这里调用的是自身的onInputEvent
方法:
我们再回到ViewRootImpl
,可以看到里面的WindowInputEventReceiver
类重写了onInputEvent
方法:
我们再进入enqueueInputEvent
:
可以看到,这个方法主要就是将我们的InputEvent
存储进这个QueuedInputEvent
中,最后执行doProcessInputEvents
:
在这里,会不断的取出待处理的InputEvent
,并且执行deliverInputEvent
,在这个方法中,可以看到其中调用的是InputStage
的deliver
方法:
注意,这里的InputStage
,使用的也是类似于责任链的模式,deliver
主要负责调用自身的OnProcess
方法,然后调用自身的apply
,然后根据OnProcess
的返回,决定调用forward
或者finish
,如果继续向下传递数据,就会调用forward
,forward
会去调用onDeliverToNext
,mNext
也会是一个InputStage
,他也会执行下一个InputStage
的deliver
方法,知道mNext
是空值,就会调用finishInputEvent
,结束这个InputEvent
的处理
我们可以看到在ViewRootImpl
的setView
方法中,一共有这些InputStage
:
最终我们事件的派发,实际上是在ViewPostImeInputStage
的OnProcess
方法中:
因为我们是事件来源是触摸屏,所以会执行processPointerEvent
:
可以看到,这里会调用dispatchPointerEvent
:
在进入方法,可以看到最终调用的是自身的dispatchTouchEvent
:
因为我们目前的这个ViewRootImpl
中的mView
实际上是一个DecorView
,所以最终调用的就是DecorView
中的dispatchTouchEvent
:
这里的mWindow
实际上就是Activity
持有的PhoneWindow
,在Activity
执行attach
的时候,会将PhoneWindow
的CallBack
设置为自身:
所以最终这里执行的,就是Activity
的dispatchTouchEvent
:
可以看到这里执行的就是持有的PhoneWindow
的superDispatchTouchEvent
,进入此方法,可以看到这里执行的是DecorView
的superDispatchTouchEvent
:
我们可以看到,最终执行的,其实就是ViewGroup
的dispatchTouchEvent
,在这个方法中,主要处理的就是根据触摸事件的坐标等信息,找到对应的ChildView
,并且调用dispatchTransformedTouchEvent
方法,然后调用子View
的dispatchTouchEvent
方法:
最终在子View
的dispatchTouchEvent
,回调自身的OnTouchListener
的onTouch
方法,完成最后的任务:
总结
整个Inputflinger
的关于触摸的事件处理及分发,主要的流程已经梳理出来了,可以看到里面涉及到了一些WMS
和AMS
的内容,还有finishTouchEvent
的部分,这部分涉及到ANR
的处理,我都会持续更新,希望看完感觉有帮助的兄弟能点个赞,谢谢。