前言
上篇“Android事件分发机制源码解析”分析了从Activity获取到触摸事件之后开始的分发流程,本篇就承接该篇介绍在触摸事件到达Activity之前的一些流程。
当点击屏幕产生一个触摸行为时,这个触摸行为则是通过底层硬件捕获传递到ViewRootImpl
内的。由于底层相关的涉及到native相关的知识,这里就简单摘取传到Java层的主要部分,详细的过程感兴趣的同学可以查看这篇文章“Input系统—事件处理全过程”,而本篇就直接从ViewRootImpl
接受到事件处开始展开。
时序图
分发流程的主题部分如下时序图所示:
下面将详细介绍其中的每个步骤。
Native
frameworks/base/core/jni/android_view_InputEventReceiver.cpp (点击查看完整源码)
1 | status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, |
可以看到最后是调用到了Java层的InputEventReceiver.dispachInputEvent()方法
InputEventReceiver
frameworks/base/core/java/android/view/InputEventReceiver.java(点击查看完整源码)
1 | /** |
dispatchInputEvent()
方法内部主要是调用了onInputEvent(event)
方法。
而这里InputEventReceiver
的实现类就是ViewRootImpl
的内部类WindowInputEventReceiver
。
ViewRootImpl
从这里开始开始的流程就都是在Java层了。
WindowInputEventReceiver
frameworks/base/core/java/android/view/ViewRootImpl.java (点击查看完整源码)
1 | final class WindowInputEventReceiver extends InputEventReceiver { |
在子类WindowInputEventReceiver.onInputEvent()
方法内调用了ViewRootImpl.enqueueInputEvent()
1 | void enqueueInputEvent(InputEvent event, |
enqueueInputEvent()方法内将输入事件封装成QueuedInputEvent后加入队列末尾,之后再进行事件处理。
QueuedInputEvent回收池
其中QueuedInputEvent也是ViewRootImpl的内部类,成员变量mNext是用来链接下一个成员,从而组成一个单向链表。
其中mFlag是一个int型的标志位,在下方InputStage分发逻辑中有重要作用。可以看到WindowInputEventReceiver.onInputEvent()
方法内调用enqueueInputEvent()
方法时传入的flags行参值为0。
1 | private static final class QueuedInputEvent { |
obtainQueuedInputEvent()
和recycleQueuedInputEvent()
方法则是配合ViewRootImpl的成员变量mQueuedInputEventPoolSize
、mQueuedInputEventPool
设计成了一个输入事件包装类QueuedInputEvent的回收池,便于QueuedInputEvent的重复使用。
1 | /** |
doProcessInputEvents
再回到enqueueInputEvent()方法内,分析最后几行
1 | //处理pending队列内的事件 |
processImmediately表示是否立即同步执行,在WindowInputEventReceiver回调内传入的是true
,执行doProcessInputEvents()。而当false异步时,则调用到scheduleProcessInputEvents()
1 | private void scheduleProcessInputEvents() { |
可以看到最后还是调用到了doProcessInputEvents()方法。
1 | void doProcessInputEvents() { |
doProcessInputEvents()方法内就是通过一个while循环遍历链表中的输入事件,调用deliverInputEvent()进行事件分发处理。
这里的mPendingInputEventHead链表就是上面的enqueueInputEvent()方法将内输入事件包装类插入的链表。
最后处理完所有输入事件后,清除标志位。
1 | private void deliverInputEvent(QueuedInputEvent q) { |
deliverInputEvent
deliverInputEvent()方法从方法名就可以知道它的作用是分发事件。
方法内最主要的就是InputStage,最终要么是调用了stage.deliver(q),要么就是stage为空直接结束事件分发。
而stage赋值处的几个InputStage子类是在ViewRootImpl.setView()方法内实例化的,这个方法在窗口管理和view绘制方面起到非常重要的作用,这部分之后会抽空再单独详细讲解。所以在View绘制之后所触发的事件,这里的7个InputStage子类都是不为空的。
而从这7个InputStage子类的构造器来看,就是典型的责任链模式,即自己能处理就自己处理,若不能处理则交给下一个处理,从下面InputStage的源码也能证实责任链模式。
1 | public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, |
- NativePreImeInputStage: 主要是为了将消息放到NativeActivity中去处理, NativeActivity和普通Acitivty的功能区别不大,只是很多代码都在native层去实现,这样执行效率更高,并且NativeActivity在游戏开发中很实用。
- ViewPreImeInputStage: 从名字中就可得知,最后会调用Acitivity的所有view的onkeyPreIme方法,这样就给View在输入法处理key事件之前先得到消息并处理的机会。
- ImeInputStage: ImeInputStage的onProcess方法会调用InputMethodManager的dispatchInputEvent方法处理消息。
- EarlyPostImeInputStage: 屏幕上有焦点的View会高亮显示,用来提示用户焦点所在。
- NativePostImeInputStage: 为了让IME处理完消息后能先于普通的Activity处理消息。
- ViewPostImeInputStage: Acitivity和view处理各种消息。
- SyntheticInputStage: 流水线的最后一级,经过层层过滤之后,到达这里的消息已经不多了,例如手机上的虚拟按键消息。
那么Activity和View的事件处理主要对应的InputStage是ViewPostImeInputStage。
类图:
InputStage详解
源码
1 | /** |
流程图
说明
InputStage主要方法有这几个:
void deliver(QueuedInputEvent q):这是deliverInputEvent()方法内的satge直接调用的入口。
void forward(QueuedInputEvent q)、onDeliverToNext(QueuedInputEvent q):forword内直接调用了onDeliverToNext。后者方法内判断当前stage是否为stage链上的最后一项(即:mNext == null)。若是,则调用finishInputEvent(q)将处理结果返回给mReceiver,否则就继续传递给下一个stage判断处理(mNext.deliver(q))
boolean shouldDropInputEvent(QueuedInputEvent q): 判断事件是否需要丢弃
int onProcess(QueuedInputEvent q) :具体处理逻辑处,默认是无实现,直接返回FORWARD值,交给下个stage处理
void apply(QueuedInputEvent q, int result) :根据onProcess()返回的状态值result判断是传递给下个stage处理还是结束,这里的结束并不是直接跳出stage链内的调用,而是在链内逐个stage遍历时每个stage都不处理,直到责任链尾时(mNext == null)调用finishInputEvent方法返回本次执行结果。
void finish(QueuedInputEvent q, boolean handled) :给QueuedInputEvent的mFlags标志位设置FLAG_FINISHED状态,且当handled = true时,设置FLAG_FINISHED_HANDLED标志,以表示事件已被处理。当事件在stage内传递完毕后会调用finishInputEvent(),该方法内就会取出FLAG_FINISHED_HANDLED标志状态并回调给mReceiver
onProcess()返回的三种类型:
- FORWARD = 0 :当前stage不处理,会将事件传递给下个stage处理
- FINISH_HANDLED = 1 : 当前stage已处理,会调用finish,stage链上剩余的都不会处理该事件
- FINISH_NOT_HANDLED = 2 : 当前stage未处理,会调用finish,stage链上剩余的都不会处理该事件
其中FINISH_HANDLED 和FINISH_NOT_HANDLED在stage责任链模式上传递时的表现行为是完全一致的,即之后的stage都不会处理。只是在finishInputEvent内会将该值取出(表示该事件是否已被消费)回调给mReceiver
1 | private void finishInputEvent(QueuedInputEvent q) { |
finishInputEvent方法就是将执行结果handled返回给mReceiver,最后调用recycleQueuedInputEvent(q)将QueuedInputEvent对象回收。
ViewPostImeInputStage#onProcess()
InputStage内的具体执行流程分析完了,接下来就可以去看看ViewPostImeInputStage.onProcess()具体执行了什么。
1 | final class ViewPostImeInputStage extends InputStage { |
ViewPostImeInputStage.onProcess()内有判断当前事件类型,我们这边分析触摸事件,所以直接看processPointerEvent(q)
。对其他事件感兴趣的可以自行查看源码分析。
1 | private int processPointerEvent(QueuedInputEvent q) { |
这里就可以看到ViewPostImeInputStage.onProgress()
返回的具体值handled ? FINISH_HANDLED : FORWARD,
FINISH_HANDLED:当前触摸事件由ViewPostImeInputStage处理;FORWARD:ViewPostImeInputStage未处理,继续传给SyntheticInputStage处理。
当然,processPointerEvent内的主角是mView.dispatchPointerEvent(event)。mView是ViewRootImpl的成员变量,该变量是在ViewRootImpl.setView()内赋值的。ViewRootImpl.setView()方法在上面介绍deliverInputEvent()方法内的InputStage初始化实例时就提到过。
ViewRootImpl#setView行参view具体类型确定
frameworks/base/core/java/android/view/ViewRootImpl.java (点击查看源码)
1 | public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, |
这里view的入参类型是View,所以还得看看ViewRootImpl.setView()方法具体是哪里调用的,且view的具体类型是什么。
这里就直接给出结论:DecorView
调用过程:ActivityThread.handleResumeActivity() -> WindowManagerImpl.addView() -> WindowManagerGlobal.addView() -> ViewRootImpl.setView()
这里的涉及到的具体细节将在下篇介绍Window相关知识的时候再详细展开说明。
frameworks/base/core/java/android/app/ActivityThread.java(点击查看源码)
1 | public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, |
frameworks/base/core/java/android/view/WindowManagerImpl.java(点击查看源码)
1
2
3
4
5
6
7 private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}
frameworks/base/core/java/android/view/WindowManagerGlobal.java(点击查看源码)
1 | public void addView(View view, ViewGroup.LayoutParams params, |
View#dispatchPointerEvent、DecorView#dispatchTouchEvent()
在确认了view的类型是DecorView后,在DecorView源码内搜索时确没有找到dispatchPointerEvent方法,那就从父类中查找(DecorView -> FrameLayout -> ViewGroup -> View),直到在View内才找到该方法。
1 | public final boolean dispatchPointerEvent(MotionEvent event) { |
可以看到,这里会判断当前是否为触摸事件,如果是则调用dispatchTouchEvent(event)
。
frameworks/base/core/java/com/android/internal/policy/DecorView.java(点击查看源码)
1 | public boolean dispatchTouchEvent(MotionEvent ev) { |
回到DecorView.dispatchTouchEvent(),这里会调用cb.dispatchTouchEvent(ev)
,cb为Window.Callback
类型。
1 | public interface Callback { |
而Activity类实现了这个回调,并且在Activity.attach()方法内给PhoneWindow设置CallBack回调。
1 | public class Activity extends ContextThemeWrapper |
1 | final void attach(Context context, ActivityThread aThread, |
1 | public boolean dispatchTouchEvent(MotionEvent ev) { |
分析到这里就回到平时从Activity开始分析的事件分发流程的起点了。从Activity.dispatchTouchEvent()开始分析事件分发的过程就可以看小编之前的文章“Android事件分发机制源码解析”了。