首先在网上看了很多文章包括郭霖大神的文章,他们都完美分析了ViewGroup和View的事件分发。可是还是很疑惑:触摸事件ViewGroup是怎么捕获到的?大神们都说Activity,Window,ViewRoot等等这些东西都是参与了事件分发,他们是怎么参加的?是谁最先接收到的触摸事件的?我是非常不解的,因此咬牙查资料分析源码学习了一波,接下来我们来一探究竟!由于为了讲得详细也为了全面,所以篇幅略长。所以分为两个部分分析。本文为上半部分,用来专门分析事件从手机硬件源头传递到我们自己写的布局之前的过程。本文源码均来自API24。
首先我先总结一下附带上一张流程图来提前剧透一下:
当触摸事件发生时,手机硬件监测到后将事件交给ViewRootImpl,然后ViewRootImpl交给DecorView,然后DecorView通过Window将事件交给Activity,然后Activity将事件交给Window,Window交给DecorView,然后DecorView开始就交给我们定义的布局啦。
(图中左边的Receiver,Handler,Stage会在下文中讲解)
接下来我们来仔细分析分析。
当我们手指触碰屏幕时,先是手机硬件会进行相应处理然后发出通知。而在源码中有一个叫做InputEventReceiver的接收器。顾名思义,输入事件接收器。这个东西在哪里用到了呢?找啊找,哈哈,发现在ViewRootImpl里用到了这个东西!
而ViewRootImpl是什么呢?简单说明一下:
他是一个用来连接Window和DecorView的纽带,也是它来触发完成View的包括measure、layout、draw绘制过程。它也起到向View分发一系列输入事件的作用,例如触摸,键盘事件等。
在ViewRootImpl中有一个WindowInputEventReceiver类继承自InputEventReceiver并且重写了onInputEvent()方法:
final class WindowInputEventReceiver extends InputEventReceiver {
public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
}
@Override
public void onInputEvent(InputEvent event) {
enqueueInputEvent(event, this, 0, true);
}
那么这个类是在哪里实例化的呢?在ViewRoot的setView()方法中有这么一段代码:
if (mInputChannel != null) {
if (mInputQueueCallback != null) {
mInputQueue = new InputQueue();
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper());
}
正是在这里进行了实例化,因此在ViewRoot将Window加入到WindowManager的时候(setView()具体在何时调用读者自行查找,可以看我的另一篇Window相关文章,这里不再深究)会创建了一个接收器。当手机硬件发出InputEvent后会调用Receiver的onInputEvent()方法,而在这里就调用了enqueueInputEvent(event, this, 0, true)(见上面的WindowInputEventReceiver源码)。
可以看到这里调用了enqueueInputEvent()方法,顾名思义,插入输入事件。我们继续跟入查看:
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
....//其他代码
if (processImmediately) {
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
}
继续贴出关键代码,首先对processImmediately进行了判断,如实为true,调用doProcessInputEvents()方法,如果为false,调用scheduleProcessInputEvents()方法,实际上scheduleProcessInputEvents()内部最终还是调用了doProcessInputEvents()方法,这里不深究,有兴趣的同志可以自行查看。
我们再跟进doProcessInputEvents()方法(跟得好累啊…):
void doProcessInputEvents() {
...//其他代码
deliverInputEvent(q);
...//其他代码
}
老规矩,我们贴出关键代码,在这里又调用了deliverInputEvent()方法,还是顾名思义,传递输入事件,我们继续跟进:
private void deliverInputEvent(QueuedInputEvent q) {
....//其他代码
InputStage stage;
if (q.shouldSendToSynthesizer()) {
stage = mSyntheticInputStage;
} else {
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}
代码标注处-----------------------------------
if (stage != null) {
stage.deliver(q);
} else {
finishInputEvent(q);
}
}
在上面中我们可以看到定义了一个InputStage,这是什么呢?在这里我们看一下官方注释:
/**
* Base class for implementing a stage in the chain of responsibility
* for processing input events.
* <p>
* Events are delivered to the stage by the {@link #deliver} method. The stage
* then has the choice of finishing the event or forwarding it to the next stage.
* </p>
*/
abstract class InputStage {
大概意思就是这就是用来处理一系列输入事件(例如触屏,键盘)的类,事件通过调用deliver方法来传递到这个类,这个类可以选择终止事件,可以选择处理。
回到刚才,在上面的上面的代码标注处调用了stage.deliver()方法,我们再进去看看:
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));
}
}
在上面代码中可以看到:他可以选择继续传递事件,也可以选择终止事件,而最后也可以调用了一个apply方法,我理解为表示处理事件。
而当一个InputEvent到来时,ViewRootImpl会寻找合适它的InputStage来处理。而ViewRootImpl中又定义了多个xxxInputStage类来继承自InputStage类,用来针对不同事件做不同处理。
对于点击事件来说,ViewPostImeInputStage可以处理它,因此调用ViewPostImeInputStage类中的onProcess方法。当onProcess被回调时,processKeyEvent、processPointerEvent、processTrackballEvent、processGenericMotionEvent至少有一个方法就会被调用,这些方法都是属于ViewPostImeInputStage的。onProgress方法如下:
@Override
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);
}
}
}
这里就看到根据输入事件的不同调用不同的处理方法,我们跟进几个processxxxEvent方法进去都会发现会调用mView.dispatchxxxEvent()方法。而这些dispatchxxxEvent()方法就是分发事件机制的实现方法了。
好了说了那么久了终于撒花撒花撒…..啊呸,啥玩意儿啊?mView是个啥啊?你这坑爹啊。啥了半天啥Window啊什么之类的都没出现。。好好好,别说了,自己挖的坑自己慢慢填。。。我们再继续看看这个mView是啥(说实话很恶心这种变量名字,只能说比test名字好一点)。
第一反应是去ViewRootImpl的构造方法去看看,结果没有找到赋值,然后就只能不停的去找,找了一会儿,诶?找到了,哈哈。在ViewRootImpl的setView()方法中发现,先上源码:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
...//其他代码
}
...//其他代码
}
...//其他代码
}
在这里进行了赋值,那么这个传入的view是啥呢?其实这个传入的View一般都是DecorView,因为ViewRoot是联系Window和根View的纽带,而根View都是DecorView。至于在哪里调用这个方法,有兴趣的同志可以自行(算了吧我去,都写到这儿了我一并写出来吧)。在我的另一篇记录里面初探Android中Window与DecorView中提到,将Window加入到WindowManager是在Activity的makeVisible()方法中调用了windowManager.addView(mDecor, getWindow().getAttributes()); 方法。而addView()方法源码如下(在WindowManagerImpl中查看):
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
我们看到是调用了 mGlobal.addView()方法,在跟进去看看:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...//其他代码
ViewRootImpl root;
View panelParentView = null;
...//其他代码
root = new ViewRootImpl(view.getContext(), display);
...//其他代码
root.setView(view, wparams, panelParentView);
...//其他代码
}
}
在这里看到,实例化了ViweRootImpl对象,调用了setView()方法。
!!!!看到没!我我我曹!终于看到这个方法了!在这里将view传入,而这个view就是传入的DecorView!
由此真相大白!事已至此我们就已经看到了ViewRoot在事件分发过程中的作用!起到了获取触摸事件,然后将事件传递给DecorView。
那么接下来很久回到之前我们说到的调用mView.dispatchxxxEvent()方法。那么接下来我们去DecorView去看看dispatchxxxEvent()方法,这里拿触摸事件(dispatchTouchEvent()方法)来分析,先上代码:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();//getCallback()返回mCallback变量
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
我们看到,在这里又调用了一个Window.Callback的dispatchTouchEvent()方法。沃德天呐,这又是啥(说实话我对这种变量名真是一览无遗无可奈何/(ㄒoㄒ)/~~)
又开始苦逼的找啊找,曹,发现在Window中没找到,然后仔细想了想,Window是在Activity的attach()方法中新建的,那么去看看呢?
结果一看,沃日啊,沃德天,我那么厉害?先上代码:
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) {
...//其他代码
mWindow.setCallback(this);
...//其他代码
}
我们看到,在这里设置了CallBack,参数为this,意味着当前Activity。恍然大悟,原来CallBack就是Activity啊!那么之前的调用的cb.dispatchTouchEvent()方法就是Activity的方法。那么接下来我们就继续去Activity看看,先上Activity的dispatchTouchEvent()源码:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();//内部实现为空
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
首先看到调用了window的superDispatchTouchEvent()方法。我们跟进PhoneWindow(Android内唯一的Winodw实现类)看看源码分析:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
我们可以看到,这里调用了DecorView的superDispatchTouchEvent()方法,我们继续跟进去看看:
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
这里又调用了父类的dispatchTouchEvent()方法,而DecorView是继承自FrameLayout的,也就是继承自ViewGroup的。因此dispatchTouchEvent()就是ViewGroup的方法。
至此我们就已经将事件分发从源头分析到了系统内部布局的DecorView。接下来可以说就是从我们自己写的布局开始传递,处理分发事件。
介于本文已经太长了,别说读,我写都写晕了。那么具体的ViewGroup和View的事件分发我将会再写一篇来进行记录。
终于完结撒花!!!!