不多说,进入正题:
关于Android中的Touch事件分发, 分为 服务端WindowManagerService(WmS,以下都以WmS简称), 负责采集与分发. 经过一些列调用, 会分发到client端 viewrootimpl中, 然后viewrootimpl进行client端的touch事件分发
首先我们要知道的是,在WmS启动后,经过逐层调用,会在native层启动InputReaderThread,InputDispatchThread,这两个线程负责touch事件的采集和分发,前者用来读取输入事件,后者则用来分发事件. 经过native的层层调用,最后会传递到ViewRootImpl的内部类 WindowInputEventReceiver中.
那么WmS是如何传递到ViewRootImpl的呢? 我们来分析..
我们需要知道的是,Touch事件从WmS传递到client 用的并不是IPC机制, 而是内存管道和共享. 上面图中 只有WmS、InputQueue、InputManager、ViewRootImpl 是在framework层中实现的. 其他则是在native层.
在WmS中,InputReaderThread 会不断从EventHub 中取出touch事件,InputDispatchThread分发事件, 然而分发事件实际操作的是InputPublisher.它内部保存了两个指针. 一个是指向服务端的 InputChannel指针,一个是指向ShareMemory(共享内存)的指针,当有事件要分发时,会把事件写入到ShareMemory中. 并向InputChannel传递一个特定的字符串,由InputChannel 将该字符串写入到管道中.
一旦client端的InputChannel从管道中读取到有事件过来.就会通知InputConsumer从ShareMemory中取出事件.并传递到InputQueue中,最后进入ViewRootImpl. 当事件消费完毕,client会回传通知WmS.
以上就是从WmS到client端的大体流程,下面我们就来重点说ViewRootImpl内的事件传递.
当事件传递过来后会被 WindowInputEventReceiver接口到 并进行 一系列的调用, onInputEvent -> enqueueInputEvent -> doProcessInputEvents -> deliverInputEvent 我们可以看到,最终会调用deliverInputEvent(QueuedInputEvent q) 这个方法.
在说到deliverInputEvent这个方法前, 我们首先要知道一个概念:
在ViewRootImpl中,有一系列InputStage概念, 每种InputStage可以处理一定的事件类型,它有很多子类 如AsyncInputStage、ViewPreImgInputStage、ViewPostImeInputStage等.当一个InputEvent到来时,ViewRootImpl会找合适的stage来处理. 对于点击事件来说,ViewPostImeInputStage 可以处理. 这时我们来看deliverInputEvent方法.
private void deliverInputEvent(QueuedInputEvent q) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
q.mEvent.getSequenceNumber());
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
}
//在ViewRootImpl中,有一系列InputStage(输入舞台事件)概念.
//每种InputStage可以处理一定的事件类型.
// 如:AsyncInputStage、ViewPreImeInputStage、ViewPostImeInputStage等.
// 当一个InputEvent到来时,ViewRootImpl会寻找合适的InputStage来处理.
//对于点击事件来说, ViewPostImeInputStage可以处理.
//ViewPostImeInputStage可以处理 中有一个processPointerEvent方法.
//在该方法中会调用 mView的dispatchPointerEvent方法, mView 就是DecorView.
InputStage stage;
if (q.shouldSendToSynthesizer()) {
stage = mSyntheticInputStage;
} else {
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}
if (stage != null) {
stage.deliver(q);
} else {
finishInputEvent(q);
}
}
可以看到.最终会调用 stage.deliver(q)
/**
* Delivers an event to be processed.
*/
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经过一系列调用后, 会调用onPrucess这个方法. 而 ViewRootImeInputStage 重写了这个方法. 我们来看看它里面是怎么实现的.
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);
} else {
// If delivering a new non-key event, make sure the window is
// now allowed to start updating.
handleDispatchWindowAnimationStopped();
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);
}
}
}
可以看到, 在这里最后会调用 processPointerEvent(q) 这个方法.
在processPointerEvent(q) 中, 会调用ViewRootImpl 内部变量 mView.dispatchPointerEvent(event) 这个方法, 看到这里会有很多人问了, mView是什么 ? 它是个DecorView, 是在Activity setContentView后 创建的,最后经过ActivityThread内的 handleResumeActivity 方法 通过 wm.addView, 最后调用了WindowManagerGlobal 内的addView, 在这里会创建ViewRootImpl, 之后会调用它的 setView方法,将DecorView传递进去,并赋值给mView变量. 关于这方面内容,不在详细介绍~
经过上面说明我们知道,mView 是DecorView, 由于DecorView内没有dispatchPointerEvent方法, 所以会调用它的超父类View 内部的 dispatchPointerEvent方法.
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}
在这里,先判断是否是touchEvent,该方法会调用natvice层的代码. 不在叙述. 我们要知道的是, 这里会调用dispatchTouchEvent方法,而DecorView重写了该方法. 所以会调用到DecorView的dispatchTouchEvent,我们来看看它
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Callback cb = getCallback();
return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
: super.dispatchTouchEvent(ev);
}
在该方法内 首先会调用getCallback()对象. 它实际就是一个Activity, 在ActivityThread内经过handleLaunchActivity ->performLaunchActivity, 在这里会创建activity,并调用activity的attach方法, 在attach方法内. 会创建个 PhoneWindow,并 调用phoneWindow.setCallback(this);然后这里会经过 三个判断, 全部满足则调用 activity的 dispatchToucherEvent, 否则调用 父类的dispatchTouchEvent. 这里其实最终都会调用到 super.dispatchTouchEvent方法.
我们来看看Activity内的 dispatchTouchEvent方法
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
//最终会执行到ViewGroup的 dispatchTouchEvent, 如果该方法返回true,则不会执行到自身的onTouchEvent
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
第一个if内的方法,onUserInteraction 是个空实现. 我们来看下个if. 如果该方法返回false, 则会调用它自身的onTouchEvent方法.
/**
* Called when a touch screen event was not handled by any of the views
* under it. This is most useful to process touch events that happen
* outside of your window bounds, where there is no view to receive it.
*
* @param event The touch screen event being processed.
*
* @return Return true if you have consumed the event, false if you haven't.
* The default implementation always returns false.
*/
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
/** @hide */
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
//如果mCloseOnTouchOutside(对应xml中 android:windowCloseOnTouchOutside) 为true 并且是当事件, 并且down事件在activity范围之外, 返回true
//该情况同于Dialog
if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
&& isOutOfBounds(context, event) && peekDecorView() != null) {
return true;
}
return false;
}
由注释可知,实际dialog 就是这种形式存在的. 会到Activity的 dispatchTouchEvent内, 如果第二个if返回true,那么结果就为true, 我们看看if内的方法. 会调用PhoneWindow内的superDispatchTouchEvent, 然后会调用DecorView的superDispatchTouchEvent 最终调用到ViewGroup的dispatchTouchEvent(event).
以上.就是事件传递到ViewGroup内的过程.