了解事件机制

从事Android开发,事件的处理是必不可少的,相信大多数的开发人员对事件到达Activity后的分发,拦截和处理都有不错的了解。而写这篇文章的时候我其实更渴望知道用户点击了屏幕或者操作了按键,事件是怎么形成然后如何准确的到达前台Activity的,为此,我下了一番功夫研究,下面将把得到的结果记录下来。

 

事件的来源

我们知道,Android系统其底层是Linux Kernel,Kernel层有许多驱动程序,这些驱动程序完成了操作系统必备的一些功能。Android事件就是来源于Kernel层,驱动程序将事件不断的写入到设备节点,输入子系统不断的从设备节点读取事件,然后分发给对应的处理对象。

在/dev/input目录下,有许许多多的设备节点,这些设备节点都与具体的硬件相关联,硬件驱动就是往这些节点中写事件数据,而输入系统也正是从这些设备节点中读数据的。

以上是我的手机一加5 /dev/input目录的情况,大概有8,9个输入设备,adb shell进入系统后,getevent 可以查看到设备的一些信息还可以查看到输入事件信息,当我们执行触摸屏幕或者点击按键或者关开屏等有事件产生的动作时,getevent会有源源不断的事件显示。(-c 20表示打印条数,因为getevent是源源不断打印的,这样可以只看前20条)

 

事件的传递流程

事件从产生到处理大概是这么一个流程:1、当有事件产生,这些事件包括按键,触摸屏,鼠标,轨迹球,红外等,对应模块的硬件驱动程序就会触发,驱动程序将得到的事件写入到设备节点;2、Android系统运行着各式各样的服务,它们随着系统启动而启动。其中一个服务InputManagerService启动了两个线程,一个负责从设备节点读取事件,一个负责分发事件;3、当我们启动应用窗口,应用会通过另一个服务WindowManagerService创建一个Socket通信通道,通道一端给到应用作为客户端,一端给到InputManagerService作为服务端;4、服务端也就是InputManagerService端,从设备节点读取事件后,通过Socket跨进程传递到客户端也就是应用端;5、应用端拿到事件,走一遍应用端的处理流程,这里有我们熟悉的分发,拦截和处理,处理结束后通知服务端处理完成;6、服务端收到处理完成消息,继续下一个事件分发。

InputManagerService和WindowManagerService是在SystemServer启动时启动的,SystemServer在Zygote启动后启动,Zygote是Android系统运行的第一个Dalvik虚拟机程序由init.rc配置启动。Zygote和SystemServer承载了Android Framework的运行。

事件传递流程主要涉及的类和接口有 InputReader,EventHub,InputDispatcher, InputPublisher,InputChannel,InputConsumer,WindowInputEventReceiver,ViewRootImpl, DecorView,Window 等。这里列出来目的是先眼熟一下。

InputReaderInputDispatcherInputManagerService通过JNI调用启动的,内部各有InputReaderThreadInputDispatcherThread线程,InputReader通过EventHubgetEvents方法读取设备节点的事件信息,然后传给InputDispatcher进行分发。InputDispatcher找到前台的Window,获取它的InputChannelInputChannelSocket套接字,它的创建在WindowManagerService的中,当用户打开一个新窗口就会创建一对socket套接字,一端给到应用窗口,一端给到InputManagerServiceInputDispatcher获取到InputChannel,通过InputPublisher调用InputChannelsendMessage方法,通过Socket方式跨进程通讯到应用端。应用端的InputConsumer通过调用InputChannelreceiveMessage接收Socket消息,然后上报到Java层的WindowInputEventReceiverWindowInputEventReceiverViewRootImpl里,通过调用ViewRootImpl的方法将事件传递给DecorViewDecorView再通过获取WindowCallBack将事件传递到Activity,接着就会进入我们以Activity开始的分发过程了,就是大致的从ActivityViewGroup再到View的过程,处理完成事件之后还需要通知InputManagerService事件处理结束,以便继续分发下一个事件。

 

代码流程

下面我们以Touch事件为例,从源码角度过一遍这个流程,源码基于Android7.1.1,流程比较长,但是绝对对形成理性认识有帮助,建议打开源码一起来跟踪哦。

 

服务端建立部分

 

    1、InputManagerService的启动

InputManagerService与WindowManagerService的启动在SystemServer的startOtherServices()方法中

InputManagerService的构造方法调用了nativeInit方法,它会经过一系列调用初始化InputReaderThread和InputDispatcherThread线程。而inputManager.start则会启动这两个线程。

InputManagerService的构造方法调用了nativeInit()

 

    2、InputReader与InputDispatcher的创建

naviteInit()是native方法,JNI调用一般有个规律,对应的C++文件一般是java端的全类名,只要将点换成下划线就可以。InputManagerService的全类名是com.android.server.input.InputManagerService。对应的C++文件就叫com_android_server_input_InputManagerService.cpp。找到它,查看它的nativeInit()方法,它创建了NativeInputManager对象

NativeInputManager的构造方法创建了EventHub以及InputManager对象,InputManager依赖于EventHub

InputManager的构造方法创建了InputReader和InputDispatcher对象以及InputReaderThread和InputDispatcherThread对象。InputReader依赖于EventHub和InputDespatcher,InputReaderThread依赖于InputReader,InputDispatcherThread依赖于InputDispatcher。

再看InputDispatcher构造方法,它创建了一个Looper

还有InputReader的构造方法,记住mEventHub就是EventHub,而mQueuedListener依赖于第三个参数,它的listener实现对象就是InputDispatcher

 

    3、InputReaderThread和InputDispatcherThread的启动

InputReader就是读取时间的线程,InputDispatcherThread就是分发事件的线程。

来看InputManagerService的start()方法,它调用了nativeStart()方法

同样对应于com_android_server_input_InputManagerService的nativeStart方法,它获取NativeInputManager的InputManager对象,然后调用它的start()方法

InputManager的start()方法调用了两个线程的run()方法

查阅资料,Thread的两个run方法执行之后,__threadLoop()方法将被调用,它会调用threadLoop()方法,在threadLoop()返回true的时候while循环反复调用threadLoop方法

来看看两个线程的threadLoop方法

到这里,输入系统服务端就已经初始化完成了,其中有的对象是持有另一个对象的引用的,这说明它可以调用另一个对象方法,比如InputReader就持有EventHub和InputDispatcher对象。

我们继续

 

    4、事件的读取过程 InputReader

InputReader的loopOnce()开始事件读取,它调用了EventHub的getEvents方法,还有自己的processEventLocked()方法,还有QueuedInputListener的flush()方法。

EventHub的getEvents()方法通过scanDevicesLocked扫描设备,调用read读取事件,将获取的事件信息与deviceId保存到RawEvent对象。

scanDevicesLocked调用了scanDirLocked,看常量DEVICE_PATH就知道是扫描/dev/input下的设备节点,内部是遍历然后打开以及获取文件描述符的过程,不再跟踪。

getEvents得到原始事件RawEvent(raw的意思是生的,未加工的),processEventLocked开始处理和分发事件,它调用了processEventsForDeviceLocked

processEventsForDeviceLocked根据deviceId得到应该处理该事件的InputDevice对象,调用它的process方法

device的process调用了InputMapper的process方法

process方法的具体实现在子类,mapper有很多子类,截图一下大概是这样的情况

InputDevice和InputMapper的关系由InputReader的addDeviceLocked()和createDeviceLocked()可以看出来

addDeviceLocked()方法调用了createDeviceLocked()方法

createDeviceLocked()方法创建了InputDevice,并且根据传入的classes绑定了InputMapper,classes由deviceId得到,所以也可以看做根据deviceId得到InputMapper

所以根据deviceId选取InputDevice,然后执行它的process(),也表示根据deviceId得到InputMapper,然后执行它的process()方法

总结一下上述流程,从EventHub的getEvents()方法得到RawEvent,processEventsLocked()方法根据RawEvent的deviceId得到应该处理这个事件的InputDevice,InputDevice得到自己的InputMapper处理事件。就这样找到了每个事件自己对应的InputMapper对象,执行它的process()方法。



    6、从InputReader到InputDispatcher

InputReader读取的事件是转到InputDispatcher去分发。接下来我们看看事件是如何从InputReader到InputDispatcher的。

来看SingleTouchInputMapper单点触摸事件的process()方法,它调用了父类的process()方法

TouchInputMapper的process()方法调用了sync方法

sync()方法调用了processRawTouches()方法

processRawTouches调用了cookAndDispatch方法

cookAndDispatch调用了dispatchPointerUsage方法

dispatchPointerUsage()调用dispatchPointerStylus()方法

dispatchPointerStylus()调用dispatchPointerSimple()方法

dispatchPointerSimple调用QueuedInputListener的notifyMotion()方法

QueuedInputListener的notifyMotion往队列放入了一个NotifyMotionArgs

之前InputReader的loopOnce()方法调用processEventLocked()之后还调用了QueueInputListener的flush()方法,flush()方法取出队列的NotifyArgs对象,调用它的notify方法

NotifyMotionArgs的notify方法调用了listener的notifyMotion方法,这个listener的实现是InputDispatcher,它在创建QueueInputListener的时候传入的,前面有说到

所以事件就这样传到了InputDispatcher了。

总结一下,InputReader的一次loopOnce()调用,利用EventHub获取到原始事件,然后processEventsLocked传递事件,其中也对事件做了初步加工,比如cookAndDispatch里有将事件的原始数据转为屏幕坐标的操作等,最后会生成一个NotifyArgs对象,QueuedInputListener的flush()方法将完成InputDispatcher方法的调用以及NotifyArgs的传递。

 

    7、InputDispatcher分发事件

InputDispatcher会处理事件,并将事件写入Socket传给客户端,来看看这个流程。

InputDispatcher的notifyMotion()方法生成了一个MotionEntry对象,并且调用了enqueueInboundEventLocked方法,还有最后调用了Looper的wake方法。

enqueueInboundEventLocked只一句话,enqueueAtTail,很显然它把事件插入到了mInboundQueue的尾部。

这个时候可能会发现调用栈跟踪不下去了。前面我们说过,InputDispatcherThread线程启动,threadLoop会持续调用,InputDispatcherThread的threadLoop调用了dispatchOnce方法

dispatchOnce方法如下

最后的mLooper->pollOnce()是一个阻塞方法,它会被mLooper->wake()方法唤醒。所以notifyMotion与dispatchOnce的关系大概是notifyMotion来了之后dispatchOnce执行一次,然后阻塞,等待下一次notifyMotion到来唤醒。当然,不止notifyMotion能唤醒dispatchOnce,notifyKey等许多方法同样可以,并且还有超时唤醒,客户端回调唤醒等。当然唤醒不一定会分发事件,还有看haveCommandsLocked判断。

dispatchOnce调用了dispatchOnceInnerLocked,dispatchOnceInnerLocked调用了dispatchMotionLocked

dispatchMotionLocked调用了findTouchedWindowTargetsLocked和dispatchEventLocked方法

dispatchEventLocked调用了prepareDispatchCycleLocked方法,眼熟InputTarget,InputChannel和Connection,后面再讲它们。

prepareDispatchCycleLocked调用enqueueDispatchEntriesLocked方法

enqueueDispatchEntriesLocked调用startDispatchCycleLocked方法

startDispatchCycleLocked调用了Connection中的InputPublisher对象的publishMotionEvent方法

publishMotionEvent方法调用了InputChannel的sendMessage方法

sendMessage就是往socket流中写数据了

到这里InputDispatcher的流程也结束了,这个Socket到底写数据写到哪里去了,这个之后再讲。

我们先总结一下:数据到了InputDispatcher通过一系列的调用写入了Socket流中,其中的流程其实非常的多,其中notifyMotion就有interceptMotionBeforeQueueing和filterInputEvent过滤,它跟PhoneWindowManager.java有关,还有pokeUserActivityLocked,它添加command到 mCommandQueue,最终会调用PowerManagerService.java的方法,还有根据事件类型去选择分支的流程等,为了避免情况变得复杂,还是先不要细究了。其中findTouchedWindowTargetsLocked是重要的方法,它会获得Socket通道,晚点我们再分析它。

服务端的事件处理先看到这里,接下来该看看客户端了。事件最终会发送到应用程序……

 

应用端建立通道部分

应用端就是我们一般的app了。应用启动之后,ActivityThread的main方法开始运行,完成application的创建后,它会接收AMS的realStartActivityLocked的scheduleLaunchActivity的调用,scheduleLaunchActivity发送Handler消息转到handleLaunchActivity调用,我们从handleLaunchActivity开始跟踪。当然不要忘记我们的目的,我们的目的是了解事件是如何传到Activity的。

    1、Activity,Window,DecorView以及mContentParent的建立

handleLaunchActivity调用了performLaunchActivity以及handleResumeActivity方法

performLaunchActivity利用反射生成了Activity对象,然后调用了它的attach方法,然后调用onCreate和onStart方法

attach方法创建了PhoneWindow,PhoneWindow继承Window,这里可以看到PhoneWindow的Callback是的实现对象是Activity。

我们在onCreate中会setContentView,来看看setContentView做了什么,它调用了PhoneWindow的

PhoneWindow的setContentView调用了installDecor

installDecor调用了generateDecor生成DecorView(DecorView继承FrameLayout),同时创建mContentParent,它是一个ViewGroup

generateDecor是以直接new的方式创建了DecorView,并且把this即PhoneWindow对象传入。

generateLayout则是以findViewById的方式得到contentParent,说明mContentParent是DecorView的一个子View,它的id是content。

PhoneWindow的setContentView还执行了以下这句,这说明我们在onCreate中的setContentView传入的layout其实是添加到了mContentParent中。

以下整理一下Activity,Window,DecorView以及mContentParent(相当于我们自己的布局)的关系

Activity在创建之后onCreate回调之前创建了一个PhoneWindow,onCreate时,PhoneWindow内部创建了DecorView,然后获取它的子ViewmContentParent,将我们的布局添加到这个子View中。

简单来说,Activity与Window互相持有引用,    Window持有DecorView与mContentParent, mContentParent是DecorView的一个子View。

这里再说明一下,Activity实现了Window.Callback接口,对于Window来说,它只是以接口方式持有了Activity的引用,对于Window来说,当Window分发事件的时候,不关callback的具体实现是谁,它都是一个流程分发,所以不关你是Activity好,Dialog也好,只要你实现了Window.Callback那就行,就可以给你分发。Window.Callback接口有以下方法。

其中就有我们熟悉的dispatchKeyEvent,dispatchTouchEvent等方法。

 

    2、ViewRootImpl的创建

onCreate只是对布局做了初始化,onResume之后才进一步添加View。添加View则会引起ViewRootImpl的创建。回到ActivityThread的handleResumeActivity方法,它会回调Activity的onResume方法,然后调用ViewManager的addView方法。

ViewManager是一个接口,它的实现是WindowManagerImpl,以下代码可以体现

Activity的getWindowManager方法

Activity中mWindowManager的来源

Window的getWindowManager方法

Window的getWindowManager方法

Window中mWindowManager的来源

WindowManagerImpl的createLocalWindowManager返回值

继续。

WindowManagerImpl的addView调用了WindowManagerGlobal的addView

WindowManagerGlobal的addView创建了一个重要的对象ViewRootImpl,然后调用它的setView方法,参数中的view是DecorView

看一眼ViewRootImpl的构造方法

继续。

ViewRootImpl类作用非常多,既跟绘图有关也与事件处理密切相关,setView正是它完成这两个功能的核心方法

requestLayout就是与视图绘制相关,我们不看。比较让我们兴奋的是我们看到了InputChannel,还记得服务端的时间是通过什么往Socket中写数据的吗,就是InputChannel的sendMessage方法。

    3、InputChannel的创建

以上创建的InputChannel会通过WindowManagerService与服务端的InputChannel进行连通。

IWindowSession的addToDisplay用到了InputChannel,new WindowInputEventReceiver用到了InputChannel,我们一个一个来跟踪,先看IWindowSession的addToDisplay方法。

由ViewRootImpl的构造方法可知,IWindowSession来自WindowManagerGlobal的getWindowSession方法

WindowManagerGlobal的IWindowSession来源自IWindowManager的openSession方法

IWindowManager是什么,它来自于getWindowManagerService方法

再看一下ServiceManager的getService方法

看到IBinder我们就知道跨进程调用了,getService传入字符串window是一个跨进程得到WindowManagerService的IBinder对象的过程,具体是怎么得到的,很难找,它的实现好像在server_manager.c中,不要细究,Binder调用是点对点的,一般直接找服务端的对应方法即可。

所以openSession其实就是调用的WindowManagerService的openSession。

ViewRootImpl的setView中我们调用了IWindowSession的addToDisplay方法将InputChannel传入,我们来看看addToDisplay方法,它调用了WindowManagerService的addWindow方法

addWindow方法调用了WindowState的openInputChannel方法,参数outInputChannel就是setView中创建的InputChannel,还有后续的updateInputWindowsLw方法

updateInputWindowsLw方法后面再看,openInputChannel方法如下

openInputChannel方法有重要的三个步骤:

1、InputChannel.openInputChannelPair;

2、mClientChannel.transferTo; 

3、mService.mInputManager.registerInputChannel。

第一步创建Socket得到两个InputChannel通道;第二步将传过来的InputChannel与其中一个InputChannel进行绑定;第三步将另一个InputChannel传给InputManagerService。

很显然,给到InputManagerService的InputChannel就是用来完成前面分析的服务端的事件的传递的。三步完成后客户端和服务端各持有Socket通道的一段,这样就可以通信了。这也是为什么事件可以准确的分配到当前窗口,因为每个窗口添加时都创建并且替换了原来的Socket通道,事件从InputManagerService出来就这样准确的传到了当前窗口了。而传到Activity则是窗口给Activity发送的,如果当前窗口是Dialog,窗口则不是给Activity发送,而是给Dialog发送。

 

    4、Socket对的创建

来看看openInputChannelPair是如何创建Socket的

调用了jni方法,根据jni文件命名规则,我们很容易找到对应的c++文件android_view_InputChannel.cpp,nativeOpenInputChannelPair调用了openInputChannelPair方法。

openInputChannelPiar方法

什么都不用说了,创建socket的流程一目了然。

再来看transferTo过程

应该是一个赋值Socket描述符的过程,没看懂。

    5、往服务端注册生成的InputChannel

再重点来看一下InputManagerService的registerInputChannel方法,调了native方法

nativeRegisterInputChannel调用了NativeInputManager的registerInputChannel

NativeInputManager的registerInputChannel调用了InputDispatcher的registerInputChannel。

InputDispatcher的registerInputChannel,创建Connection对象,将描述符作为key,connection作为value放入到mConnectionsByFd中。

这里只要记住InputChannel在这里转成了Connection对象就可以了。这个时候我们再来看看我们之前没有看的findTouchedWindowTargetsLocked方法,看看它是怎么得到当前窗口

 

  6、addWindow更新Window信息到服务端

findTouchedWindowTargetsLocked有很多addOrUpdateWindow操作,他应该表示如果TouchedWindow集合(即windows)中有对应的InputWindowHandle就根据值更新它的flags,如果windows中没有就生成一个并且添加进去,这个InputWindowHandle是什么,它是WindowManagerService设置进来的,在我们addWindow中

前面我们提到在openInputChannel后,addWindow还有后续操作,大概是判断窗口能否接受按键,判断窗口焦点是不是已经改变,然后调用mInputMonitor的updateInputWindowLw()

updateInputWindowLw重要的语句是

InputManagerService的setInputWindows方法

往InputManagerService中设置InputWindowHandle,其实就是调用的InputDispatcher的setInputWindows方法。InputDispatcher的setInputWindows方法将InputWindowHandle保存到mWindowHandles中了

InputMonitor的updateInputWindowLw方法是在窗口变化时给InputDispatcher更新变化情况的重要方法,它有很多地方会调用,大概是

几个比较常见的情况大概是:

    7、服务端根据Window信息得到InputChannel

回到findTouchedWindowTargetsLocked方法,通过一些列addOrUpdateWindows后确定了窗口的层级,窗口的状态的,并且保存到windows中,然后调用addWindowTargetLocked将数据保存到inputTargets中,同时mTouchStateByDisplay缓存当前mTempTouchState。

再回顾dispatchEventsLocked方法,它从inputTarget中取出inputChannel,在根据InputChannel得到Connection对象。InputTarget就表示接收输入事件的目标,它可能有多个。

接下来走的prepareDispatchCycleLocked就是前面提到的InputDispatcher分发利用InputChannel分发事件的过程了。

 

客户端接收和分发事件部分

    1、接收Socket消息

接下里该是客户端对事件的接收和分发过程了,还记的ViewRootImpl的setView方法把另一端的InputChannel传给了WindowInputEventReceiver吗。

WindowInputEventReceiver的构造方法调用了InputEventReceiver父类的构造方法,InputEventReceiver的构造方法调用了nativeInit方法

nativeInit创建了NativeInputEventReceiver对象,然后调用它的initialize方法

initialize主要调用setFdEvents方法

setFdEvents将客户端InputChannel的socket文件描述符加入到looper中

Looper不仅提供了Message机制,还提供了监听文件描述符的机制,addFd的第四个参数this表示一个LooperCallback对象,当描述符读取到事件后,looper会回调这个LooperCallback的handleEvent方法,NativeInputEventReceiver就实现了这个接口,来看看它的handleEvent方法

它调用了comsumeEvents方法

consumeEvents方法调用了InputConsumer的consume方法,还回调了java层的方法

InputConsumer的consume方法调用InputChannel的receiverMessage,然后组建MotionEvent

InputChannel的receiverMessage就是读socket操作啦,跟sendMessage是呼应的。

    2、事件到了Java层

事件来到了java层WindowInputEventReceiver的dispatchInputEvent方法,这个方法实现在父类InputEventReceiver

再看它onInputEventEvent方法

接着是doProcessInputEvents

接着是deliverInputEvent

接着是InputStage deliver

接着是InputStage实现类的onProcess方法

InputStage的实现类很多,它们的初始化在ViewRootImpl的setView方法中

一个套一个,感觉像装饰者模式,其实是一个链表,看InputStage的构造方法能看到next指针

所以链表的头结点是NativePreImeInputStage,链表顺序与初始化顺序相反。

回到deliverInputEvent方法

PreIme和PostIme意思应该是在输入法之前与在输入法之后,各个InputStage的作用我也不是很懂,像这种分支判断,什么时候执行什么真的是看源码最疑惑的地方了,看看源码中对于各个InputStage的注释,自己体会吧。

前三个跟pointer events无关,第四个是预处理,第五个是native activity,第五个可以认为才是将点击事件分发到View树的InputStage,第六个处理unhandled input events。

来看ViewPostImeInputStage的process方法,它调用了自己的processPointerEvent

processPointerEvent调用了eventTarget的dispatchPointerEvent

eventTarget是ViewRootImpl setView时设置进来的view,即DecorView。DecorView没有搜到对应方法,实现在它的父类View中,View的dispatchPointerEvent调用了dispatchTouchEvent方法

DecorView的dispatchPointerEvent调用了Callback的dispatchTouchEvent


    3、熟悉的dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent来啦

对于Activity setContentView的情况,cb就是Activity,所以事件就到了Activity的dispatchTouchEvent了

getWindow().superDispatchTouchEvent是将事件继续分发给视图的ViewGroup,如果ViewGroup处理返回true了,Activity的onTouchEvent就不处理了。

我们可以实现重写Activity的dispatchTouchEvent,但必须super一下父类的方法,不然,不管你返回true还是返回false,事件都就此结束,ViewGroup的dispatchTouchEvent也不会调用,Activity的onTouchEvent也不会调用,这个方法能让我们看到原因。

getWindow().superDispatchTouchEvent如何再分发事件,来看PhoneWindow的superDispatchTouchEvent,调用DecorView的superDispatchTouchEvent

DecorView的superDispatchTouchEvent调用了父类的对应方法即ViewGroup的对应方法

ViewGroup的dispatchTouchEvent有很丰富的处理,如果返回true,就没有它子View的dispatchTouchEvent什么事了

如果返回false,或者默认处理,则会有遍历子View的操作,默认处理是这个

遍历子View

dipatchTransformedTouchEvent方法

如果传进来的child为空,表示没有子View,会调用父类的dispatchTouchEvent,ViewGroup的父类是View。如果child不为空,那么调用它的dispatchTouchEvent方法,表示child为ViewGroup的时候又走一遍这个流程,为View的时候走一遍View的dispatchTouchEvent方法,在View的dispatchTouchEvent方法中我们可以看到对onTouchEvent的调用。

super.dispatchTouchEvent是对View的dispatchTouchEvent的调用

事件到达Activity之后的流程是:Activity的dispatchTouchEvent,然后是ViewGroup的dispatchTouchEvent,onInterceptTouchEvent,然后是ViewGroup的子View的dispatchTouchEvent方法,如果子View是ViewGroup,则继续dispatchTouchEvent,onInterceptTouchEvent,如果是View,则dispatchTouchEvent后到onTouchEvent。

可以这样理解,ViewGroup的事件来源取决于Activity的diapatchTouchEvent。ViewGroup的onInterceptTouchEvent和onTouchEvent取决于自身的dispatchTouchEvent,你自己的ViewGroup不super调用的话,自己的onInterceptTouchEvent和onTouch都不会得到执行。 onInterceptTouchEvent可以决定传不传到子View,因为onInterceptTouchEvent返回true,不会有遍历子View的流程,而是直接调用自身onTouchEvent方法。View没有 onInterceptTouchEvent方法,View自己的dispatchTouchEvent也可以决定自己的onTouch调不调用。

dispatchTouchEvent返回true,事件不会再向下传递,但该方法可以持续收到事件,dispatchTouchEvent返回false,则回调父层级View的onTouchEvent,该方法不再接收事件。

     4、事件处理结束通知服务端

最后一个知识点,在InputStage链表遍历结束之后,会调用finishInputEvent方法告诉服务端已经处理完成事件,他最终会调用到InputDispatcher中去,然后它再进行下一次事件分发。

为什么事件处理结束还需要回调到服务端呢,不能服务端来事件了就往外丢,然后什么都不管吗,有没有其他重要原因不知道,其中一个重要的原因是确定的,那就是ANR机制,处理完成通知服务端是让服务端能够统计客户端处理事件用了多久,如果客户端因为一个事件卡死,而服务端完全不知情那就会出很大的问题。

所以一个一个事件的分发是串行的咯?

答案应该是对的,可以尝试个小例子,在Activity的dipatchTouchEvent睡20s,这个时候就算你发送back键home键,界面也不会有反应,这应该可以作为一个依据了。

 

 

总结

事件处理高度概括可以看成:硬件到设备节点,设备节点到系统服务,系统服务到应用端。事件处理整个流程很复杂,很多细节并没有弄清,不过至少解决了从屏幕触摸到界面响应是怎么一回事的问题。Android源码确实非常庞大,里面的内容包罗万象,所以当你牛逼了,懂得如何看源码,你就会相信从源码中找答案这句话是多么的正确。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值