ViewRootImpl源码解析 - 事件分发

Posted 许佳佳233

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ViewRootImpl源码解析 - 事件分发相关的知识,希望对你有一定的参考价值。

概述

前文链接:ViewRootImpl源码解析 (一) - View的更新

前文讲了ViewRootImpl中View更新相关的逻辑,此文将讨论下ViewRootImpl中的事件分发逻辑,或者说是事件回传逻辑。

此处先列出笔者的个人对ViewRootImpl的理解,如有问题欢迎评论指正。

View的所有更新UI的操作最终都必须经过操作系统在系统进程的处理,才能够通过硬件展示到用户面前。
ViewRootImpl担任了window与view的中间人的角色,View可以通过ViewRootImpl将更新UI的操作告知操作系统,而widnow也可以将操作系统层面的触摸等操作反馈给View。

ViewRootImpl事件分发入口

ViewRootImpl.WindowInputEventReceiver.onInputEvent()这个方法是ViewRootImpl中接收事件的入口。

    final class WindowInputEventReceiver extends InputEventReceiver {
        public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
            super(inputChannel, looper);
        }

        @Override
        public void onInputEvent(InputEvent event, int displayId) {
            enqueueInputEvent(event, this, 0, true);
        }

        @Override
        public void onBatchedInputEventPending() {
            if (mUnbufferedInputDispatch) {
                super.onBatchedInputEventPending();
            } else {
                scheduleConsumeBatchedInput();
            }
        }

        @Override
        public void dispose() {
            unscheduleConsumeBatchedInput();
            super.dispose();
        }
    }

事件的类型,MotionEvent与KeyEvent

InputEvent这个类是传递过来的事件。
InputEvent这个类有两个子类:MotionEvent与KeyEvent

  • MotionEvent:触摸事件
  • KeyEvent:键盘点击事件

触摸事件的调用栈

enqueueInputEvent()起,一直到view的dispatchTouchEvent(),传递的流程比较简单,因此直接列出调用栈:

  • ViewRootImpl.WindowInputEventReceiver.onInputEvent()
  • ViewRootImpl.enqueueInputEvent()
  • ViewRootImpl.doProcessInputEvents()
  • ViewRootImpl.deliverInputEvent()
  • ViewPostImeInputStage.deliver()
  • ViewPostImeInputStage.onProcess()
  • ViewPostImeInputStage.processPointerEvent()
  • View.dispatchPointerEvent()
  • View.dispatchTouchEvent()

键盘点击事件的调用栈

键盘点击事件和触摸事件的调用栈大部分都是一样的,主要是在ViewPostImeInputStage.onProcess()中会区分处理,调用栈如下:

  • ViewRootImpl.WindowInputEventReceiver.onInputEvent()
  • ViewRootImpl.enqueueInputEvent()
  • ViewRootImpl.doProcessInputEvents()
  • ViewRootImpl.deliverInputEvent()
  • ViewPostImeInputStage.deliver()
  • ViewPostImeInputStage.onProcess()
  • ViewPostImeInputStage.processKeyEvent()

键盘事件的这块知识点笔者不是很熟悉,有兴趣的可以自行研究下源码,也欢迎在评论中分享讨论。

Decorview中的事件传递

在activity中,ViewRootImpl的mView实际上就是DecorView。
对具体原理有兴趣的读者可以看下笔者的前文:
todo

DecorView中对dispatchTouchEvent()进行了重写,因此并不是我们熟悉的直接将事件分发给子View。

DecorView.dispatchTouchEvent()

dispatchTouchEvent()中最终调用的Window.Callback的dispatchTouchEvent(),那么此处mWindow.getCallback()获得的对象是什么呢?继续往下看。

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }

Activity.attach()

在Activity.attach()中可以看到,window的callback其实就是它对应的activity。
于是我们可以直接看下activity的dispatchTouchEvent()方法。

    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
————————————————————省略
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
————————————————————省略
            }

Activity.dispatchTouchEvent()

此处的逻辑如下:

  1. 如果用户触摸的屏幕,那么会触发onUserInteraction()逻辑。(此逻辑与本文内容无关,有兴趣的可以自行看下源码)
  2. 调用到window的superDispatchTouchEvent(),如果已经处理的事件,那么久直接return。
  3. 如果window没有处理事件,那么会走到activity的onTouchEvent()逻辑。
  4. onTouchEvent()逻辑中就是判断window的shouldCloseOnTouch()是否返回true,如果是true,就直接关闭activity,并且return true表示该事件已经处理过。反之就return false,表示该事件没有处理过。
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
    
    public boolean onTouchEvent(MotionEvent event) {
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }

        return false;
    }

PhoneWindow.superDispatchTouchEvent()

widnow的superDispatchTouchEvent()方法实际上就是调用的DecorView的superDispatchTouchEvent()方法。

    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

DecorView.superDispatchTouchEvent()

DecorView的superDispatchTouchEvent()方法,实际上调用的是View的dispatchTouchEvent()方法。

    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

DecorView事件传递调用栈

  • DecorView.dispatchTouchEvent()
  • Activity…dispatchTouchEvent()
  • PhoneWindow.superDispatchTouchEvent()
  • DecorView.superDispatchTouchEvent()
  • View.dispatchTouchEvent()

为什么DecorView的事件传递要如此麻烦,最终不还是调用到View.dispatchTouchEvent()?
回答:
根据调用栈可以看到,事件传给了activity与window,这表示activity与window都可以对事件进行拦截,或者做特殊处理。
比如这几个方法:

  • activity.onUserInteraction()
  • window.superDispatchTouchEvent()
  • widnow.shouldCloseOnTouch()

以上是关于ViewRootImpl源码解析 - 事件分发的主要内容,如果未能解决你的问题,请参考以下文章

ViewRootImpl源码解析 - 事件分发

Android TV 焦点原理源码解析

Android View框架总结KeyEvent事件分发机制

反思|Android 事件分发机制的设计与实现

Android 源码解析View的touch事件分发机制

Android View体系从源码解析View的事件分发机制