← 返回文章流

Android源码分析:从源头分析View事件的传递

对于应用开发者的我们来说,经常会处理按钮点击,键盘输入等事件,而我们的处理一般都是在Activity中或者View中去做的。我们在上一篇文章中分析了View和Activity与Window的关系,其中的ViewRootImpl和我们的事件传递息息相关,上文未能分析,本文将对其进行分析。 事件介绍 事件是什么呢,广义上事件的发生可能在软件也可能在硬件层,在Android设备当中,我们会有可能有键盘触发,触摸触发,鼠标触发的各种事件。我们关注的通常有两种事件: 按键事件(KeyEvent): 这种色包括物理的按键,Home键,音量键,也包括软键盘触发的事件。 触摸事件(TouchEvent): 手指在屏幕上触摸触发的事件,可能是点击,也可能是拖动。 对于按键事件,一般有ACTION_DOWN和ACTION_UP两种状态,对于KeyEvent所支持的所有keyCode,我们都可以在KeyEvent当中找到。 而对于触摸事件来说,除了DOWN和UP两种状态之外,还有ACTION_MOVE,ACTION_CANCEL等状态。 应用层的事件类图如下图所示: classDiagram class Parcelable { <<interface>> } class InputEvent { <<abstract>> } class KeyEvent class MotionEvent InputEvent<|--KeyEvent InputEvent<|--MotionEvent Parcelable<|..KeyEvent Parcelable<|..MotionEvent Parcelable<|..InputEvent事件传递到View 我们一般处理View的onClick事件,而这个事件是在View的onTouchEvent中进行处理并执行的,在View中我们可以向上追溯到dispatchPointerEvent方法当中,这个方法就是外部向View传递事件的调用。我们知道Android的UI界面中的所有View是一个树形的结构,因此这些事件也就会通过dispatchTouchEvent一层一层的往下传,从而每一个View都能够接收到事件,并决定是否处理。 而dispatchPointerEvent是在ViewRootImpl当中调用,代码如下: 1 2 3 4 5 6 7 8 9 private int processPointerEvent(QueuedInputEvent q) { final MotionEvent event = (MotionEvent)q.mEvent; ... boolean handled = mView.dispatchPointerEvent(event); maybeUpdatePointerIcon(event); maybeUpdateTooltip(event); ... return handled ? FINISH_HANDLED : FORWARD; } 在Activity中,它的根视图为DecorView,ViewRootImpl在执行它的dispatchPointerEvent方法,它再向下把触摸事件依次向下传递。 除了触摸事件,按键事件也是类似,ViewRootImpl当中会调用View的dispatchKeyEvent方法,View当中会做相应的处理或者向下传递。 ViewRootImpl中对事件的处理 对于ViewRootImpl当中是如何获取事件,并且向后传递的,我们这里以触摸事件为主进行分析,其他事件也类似。 在ViewRootImpl中,定义写一些内部类,大概如下: classDiagram class InputStage { <<abstract>> +InputStage mNext; +deliver(QueuedInputEvent q) #finish(QueuedInputEvent q, boolean handled) #forward(QueuedInputEvent q) #onDeliverToNext(QueuedInputEvent q) #onProcess(QueuedInputEvent q) } class AsyncInputStage { <<abstract>> #defer(QueuedInputEvent q) } InputStage <|-- AsyncInputStage AsyncInputStage <|--NativePreImeInputStage InputStage <|-- ViewPreImeInputStage AsyncInputStage <|-- ImeInputStage InputStage <|-- EarlyPostImeInputStage AsyncInputStage <|-- NativePostImeInputStage InputStage <|-- ViewPostImeInputStage InputStage <|--SyntheticInputStage上面这几个类就ViewRootImpl中处理事件的类,其初始化代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 //ViewRootImpl.java public void setView(...) { ... CharSequence counterSuffix = attrs.getTitle(); mSyntheticInputStage = new SyntheticInputStage(); InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage); InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage, "aq:native-post-ime:" + counterSuffix); InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage); InputStage imeStage = new ImeInputStage(earlyPostImeStage, "aq:ime:" + counterSuffix); InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage); InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage, "aq:native-pre-ime:" + counterSuffix); mFirstInputStage = nativePreImeStage; mFirstPostImeInputStage = earlyPostImeStage; } 以上代码创建了多个InputStage,它们一起组成了输入事件处理的流水线。其中ViewPostImeInputStage中就会处理与触摸相关的事件,它的onProcess方法代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 protected 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); } } } 可以看到,当我们的输入源为POINTER,触摸屏和鼠标的触发都是这一类。这个时候就会执行上面我们提到的 processPointerEvent方法,之后事件也就会传递到View当中。 这里我们知道了是通过InputStage的流水线拿到的事件,但是这个事件从何处来的呢,我们需要继续向上溯源。 ViewRootImpl从何处获得事件 关于这一点,我们仍然需要关注ViewRootImpl的setView方法中的如下代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 //VieRootImpl.java InputChannel inputChannel = null; if ((mWindowAttributes.inputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) { inputChannel = new InputChannel(); } ... res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), userId, mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets, mTempControls, attachedFrame, compatScale); ... if (inputChannel != null) { mInputEventReceiver = new WindowInputEventReceiver(inputChannel, Looper.myLooper()); } 在这里,我们创建了一个InputChannel,但是我们创建的InputChannel仅仅是java层的一个类,没法去获取到事件,随后我们调用WindowSession的addToDisplayAsUser他就会获得mPtr,也就是Native层的InputChannel,具体内容随后再看相关代码。在15行,这里创建了一个WindowInputEventReceiver,它的参数为inputChannel和Looper,这里一起看一下InputEventReceiver的构造方法,代码如下: 1 2 3 4 5 6 7 8 public InputEventReceiver(InputChannel inputChannel, Looper looper) { mInputChannel = inputChannel; mMessageQueue = looper.getQueue(); mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this), inputChannel, mMessageQueue); mCloseGuard.open("InputEventReceiver.dispose"); } InputEventReceiver的初始化 这里主要是调用了nativeInit方法,并且获取到mReceivePtr,native的代码在android_view_InputEventReceiver.cpp当中

来自 码农明明桑作者主页
在原博客阅读全文 (新标签页打开)

以上内容来自博客订阅源,版权归原作者。关注作者后可以在 关注列表 第一时间查看作者文章。

一起聊聊 0

尊重不同观点

还没有评论。欢迎分享第一个想法。

RSS SUBSCRIPTIONS

选择 RSS 订阅

把博客汇的文章添加到你常用的阅读器。

关注文章 RSS 订阅本订阅需要登录后使用