写点什么

事件分发三连问:事件是如何从屏幕点击最终到达 -Activity- 的?CANCEL- 事件什么时候会触发

用户头像
Android架构
关注
发布于: 刚刚

}


主要做的两件事:


  1. 初始化 EventHub


EventHub::EventHub(void) {// ...mINotifyFd = inotify_init();int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);}


EventHub 的作用是用来监控设备节点是否有更新。2. 初始化 InputManager


void InputManager::initialize() {mReaderThread = new InputReaderThread(mReader);mDispatcherThread = new InputDispatcherThread(mDispatcher);}


InputManager 里初始化了 InputReaderThread 和 InputDispatcherThread 两个线程,一个用来读取事件,一个用来派发事件。

2.2.3 InputReaderThread 做的事情

bool InputReaderThread::threadLoop() {mReader->loopOnce();return true;}


void InputReader::loopOnce() {// 从 EventHub 获取事件 size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);// 处理事件 processEventsLocked(mEventBuffer, count);// 事件发送给 InputDispatcher 去做分发 mQueuedListener->flush();}


这里代码比较多,做一些省略。InputReaderThread 里做了三件事情:


  1. 从 EventHub 获取事件

  2. 处理事件,这里事件有不同的类型,会做不同的处理和封装

  3. 把事件发送给 InputDispatcher

2.2.4 InputDispatcherThread 做的事情

bool InputDispatcherThread::threadLoop() {mDispatcher->dispatchOnce(); // 内部调用 dispatchOnceInnerLockedreturn true;}


void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {// 从队列中取出一个事件 mPendingEvent = mInboundQueue.dequeueAtHead();// 根据不同的事件类型,进行不同的操作 switch (mPendingEvent->type) {case EventEntry::TYPE_CONFIGURATION_CHANGED: {// ...case EventEntry::TYPE_DEVICE_RESET: {// ...case EventEntry::TYPE_KEY: {// ...case EventEntry::TYPE_MOTION: {// 派发事件 done = dispatchMotionLocked(currentTime, typedEntry,&dropReason, nextWakeupTime);break;}}


上面通过 dispatchMotionLocked 方法派发事件,具体的函数调用过程省略如下:


dispatchMotionLocked -> dispatchEventLocked -> prepareDispatchCycleLocked -> enqueueDispatchEntriesLocked -> startDispatchCycleLocked -> publishMotionEvent -> InputChannel.sendMessage


其中会找到当前合适的 Window,然后调用 InputChannel 去发送事件。


这里的 InputChannel 对应的是 ViewRootImpl 里的 InputChannel。至于中间的怎么做的关联,这里就先不做分析,整个代码比较长,而且对于流程的掌握影响不大。

2.2.5 WindowInputEventReceiver 接受事件并进行分发

在 ViewRootImpl 里有一个 WindowInputEventReceiver 用来接


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


受事件并进行分发。InputChannel 发送的事件最终都是通过 WindowInputEventReceiver 进行接受。WindowInputEventReceiver 是在 ViewRootImpl.setView 里面初始化的,setView 的调用是在 ActivityThread.handleResumeActivity -> WindowManagerGlobal.addView。


public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {// ...if (mInputChannel != null) {if (mInputQueueCallback != null) {mInputQueue = new InputQueue();mInputQueueCallback.onInputQueueCreated(mInputQueue);}mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,Looper.myLooper());}}


public abstract class InputEventReceiver {// native 侧代码调用这个方法,把事件派发过来 private void dispatchInputEvent(int seq, InputEvent event, int displayId) {mSeqMap.put(event.getSequenceNumber(), seq);onInputEvent(event, displayId);}}


final class WindowInputEventReceiver extends InputEventReceiver {@Overridepublic void onInputEvent(InputEvent event, int displayId) {// 事件接受 enqueueInputEvent(event, this, 0, true);}// ...}


void enqueueInputEvent(InputEvent event,InputEventReceiver receiver, int flags, boolean processImmediately) {// 是否要立即处理事件 if (processImmediately) {doProcessInputEvents();} else {scheduleProcessInputEvents();}}


void doProcessInputEvents() {// ...while (mPendingInputEventHead != null) {deliverInputEvent(q);}// ...}


private void deliverInputEvent(QueuedInputEvent q) {// ...InputStage stage;if (q.shouldSendToSynthesizer()) {stage = mSyntheticInputStage;} else {stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;}


// 分发事件 stage.deliver(q);}


从上面的代码流程中,事件最终走到 InputStage.deliver 里。


abstract class InputStage {public final void deliver(QueuedInputEvent q) {if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {forward(q);} else if (shouldDropInputEvent(q)) {finish(q, false);} else {apply(q, onProcess(q));}}}


在 deliver 里,最终调用 onProcess,实现是在 ViewPostImeInputStage。


final class ViewPostImeInputStage extends InputStage {@Overrideprotected int onProcess(QueuedInputEvent q) {if (q.mEvent instanceof KeyEvent) {return processKeyEvent(q);} else {final int source = q.mEvent.getSource();if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {return processPointerEvent(q);} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {return processTrackballEvent(q);} else {return processGenericMotionEvent(q);}}}


private int processPointerEvent(QueuedInputEvent q) {// 这里 mView 是 DecorView,调用到 DecorView.dispatchPointerEventboolean handled = mView.dispatchPointerEvent(event);// ...return handled ? FINISH_HANDLED : FORWARD;}}


// View.javapublic final boolean dispatchPointerEvent(MotionEvent event) {if (event.isTouchEvent()) {return dispatchTouchEvent(event);} else {return dispatchGenericMotionEvent(event);}}


// DecorView.javapublic boolean dispatchTouchEvent(MotionEvent ev) {// 这里的 Callback 就是 Activity,是在 Activity.attach 里调用 mWindow.setCallback(this); 设置的 final Window.Callback cb = mWindow.getCallback();return cb != null && !mWindow.isDestroyed() && mFeatureId < 0? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);}


通过上面一系列流程,最终就调用到 Activity.dispatchTouchEvent 里,也就是开始的流程了。


通过上面的分析,我们基本上知道了事件从用户点击屏幕到 View 处理的过程了,就是下面这张图。


2.3 CANCEL 事件什么时候会触发

这个如果仔细看 dispatchTouchEvent 的代码的话,可以看到一些时机:


  1. View 收到 ACTION_DOWN 事件以后,上一个事件还没有结束(可能因为 APP 的切换、ANR 等导致系统扔掉了后续的事件),这个时候会先执行一次 ACTION_CANCEL


// ViewGroup.dispatchTouchEvent()public boolean dispatchTouchEvent(MotionEvent ev) {if (actionMasked == MotionEvent.ACTION_DOWN) {// Throw away all previous state when starting a new touch gesture.// The framework may have dropped the up or cancel event for the previous gesture// due to an app switch, ANR, or some other state change.cancelAndClearTouchTargets(ev);resetTouchState();}}


  1. 子 View 之前拦截了事件,但是后面父 View 重新拦截了事件,这个时候会给子 View 发送 ACTION_CANCEL 事件


// ViewGroup.dispatchTouchEvent()public boolean dispatchTouchEvent(MotionEvent ev) {if (mFirstTouchTarget == null) {} else {// 有子 View 获取了事件 TouchTarget target = mFirstTouchTarget;while (target != null) {

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
事件分发三连问:事件是如何从屏幕点击最终到达-Activity-的?CANCEL-事件什么时候会触发