Android中MotionEvent的来源和ViewRootImpl
前言
很久没有发表文章了,今天来一篇,大家撒花~~~
本文打算分析下Android中点击事件的来源,顺便提及下ViewRootImpl。
Android中点击事件的来源
这个问题,也许你会说“这还用你说吗?我可是看过艺术探索的人”,我知道艺术探索中的确是详细介绍了点击事件的传递流程,反正大致就是点击事件从Activity传递给PhoneWindow,然后PhoneWindow再传递给DecorView,接着DecorView就进行后续的遍历式的传递。这都没错,但是点击事件是谁传递给Activity的呢?这个大家可能不清楚吧?那本文就是分析这个问题的。
首先看Activity的实现,如下,Activity实现了一个特殊的接口:Window.Callback。
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback {
private static final String TAG = "Activity";
private static final boolean DEBUG_LIFECYCLE = false;
那么Window.Callback到底是什么东西呢?如下:
/**
* API from a Window back to its caller. This allows the client to
* intercept key dispatching, panels and menus, etc.
*/
public interface Callback {
/**
* Called to process key events. At the very least your
* implementation must call
* [email protected] android.view.Window#superDispatchKeyEvent} to do the
* standard key processing.
*
* @param event The key event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchKeyEvent(KeyEvent event);
/**
* Called to process touch screen events. At the very least your
* implementation must call
* [email protected] android.view.Window#superDispatchTouchEvent} to do the
* standard touch screen processing.
*
* @param event The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent event);
/**
* Called to process trackball events. At the very least your
* implementation must call
* [email protected] android.view.Window#superDispatchTrackballEvent} to do the
* standard trackball processing.
*
* @param event The trackball event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTrackballEvent(MotionEvent event);
...(省略若干代码,下同)
然后我们似乎看出了一些端倪,难道这个接口和点击事件的传递有关?嗯,你猜对了。在艺术探索这本书中,并没有描述事件是如何传递给Activity的,但是这里我们可以猜测,如果外界想要传递点击事件给Activity,那么它就必须持有Activity的引用,这没错,在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) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
显然,mWindow持有了Activity的引用,它通过setCallback方法来持有Activity,因此,事件是从Window传递给了Activity。也许你会说:“我不信,这理由不充分!”,没关系,我们继续分析。
我们知道,Activity启动以后,在它的onResume以后,DecorView才开始attach给WindowManager从而显示出来。(什么?你不知道?回去看艺术探索第8章),请看Activity的makeVisible方法,代码如下:
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
接着,系统就会完成添加Window的过程,看WindowManagerGlobal的addView方法,如下:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
ViewRootImpl root;
View panelParentView = null;
...这里省略了一堆代码
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
可以看到,ViewRootImpl创建了,在ViewRootImpl的setView方法(此方法运行在UI线程)中,会通过跨进程的方式向WMS(WindowManagerService)发起一个调用,从而将DecorView最终添加到Window上,在这个过程中,ViewRootImpl、DecorView和WMS会彼此向关联,同时会创建InputChannel、InputQueue和WindowInputEventReceiver来接受点击事件的消息。
好了,言归正传,下面来说,点击事件到底怎么传递给Activity的。首先要明白,点击事件是由用户的触摸行为所产生的,因此它必须要通过硬件来捕获,然后点击事件会交给WMS来处理。
在ViewRootImpl中,有一个方法,叫做dispatchInputEvent,如下:
public void dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = event;
args.arg2 = receiver;
Message msg = mHandler.obtainMessage(MSG_DISPATCH_INPUT_EVENT, args);
msg.setAsynchronous(true);
mHandler.sendMessage(msg);
}
那么什么是InputEvent呢?InputEvent有2个子类:KeyEvent和MotionEvent,其中KeyEvent表示键盘事件,而MotionEvent表示点击事件。在上面的代码中,mHandler是一个在UI线程创建的Handder,所以它会把执行逻辑切换到UI线程中。
final ViewRootHandler mHandler = new ViewRootHandler();
这个消息的处理如下:
case MSG_DISPATCH_INPUT_EVENT: {
SomeArgs args = (SomeArgs)msg.obj;
InputEvent event = (InputEvent)args.arg1;
InputEventReceiver receiver = (InputEventReceiver)args.arg2;
enqueueInputEvent(event, receiver, 0, true);
args.recycle();
}
除此之外,WindowInputEventReceiver也可以来接收点击事件的消息,同样它也有一个dispatchInputEvent方法,注意,WindowInputEventReceiver中的Looper为UI线程的Looper。
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper());
// Called from native code.
@SuppressWarnings("unused")
private void dispatchInputEvent(int seq, InputEvent event) {
mSeqMap.put(event.getSequenceNumber(), seq);
onInputEvent(event);
}
@Override
public void onInputEvent(InputEvent event) {
enqueueInputEvent(event, this, 0, true);
}
可以发现,不管是ViewRootImpl的dispatchInputEv