一、事件来源
图中的箭头表示Queue的方向FIFO。
二、事件分发
我们的主要目的是往Activity方向靠近, 所以暂时就不往InputDeviceReader方向研究, 接下来我们研究InputDisputcherThread到底是如何分发事件, 我们看源码:
private final class InputDispatcherThread extends Thread { public InputDispatcherThread() { super("InputDispatcher"); } @Override public void run() { while (true) { process(); } } private void process() { while (true) { ... // 从Queue中读取下一个事件 QueuedEvent ev = mQueue.getEvent((int)((!configChanged && curTime < nextKeyTime) ? (nextKeyTime-curTime) : 0));
switch (ev.classType) { ... case RawInputEvent.CLASS_KEYBOARD: // 分发按键事件 dispatchKey((KeyEvent)ev.event, 0, 0); break; case RawInputEvent.CLASS_TOUCHSCREEN: // 分发触摸事件 dispatchPointer(ev, (MotionEvent)ev.event, 0, 0); break; case RawInputEvent.CLASS_TRACKBALL: // 分发滚轮事件 dispatchTrackball(ev, (MotionEvent)ev.event, 0, 0); break; ... } } } }
上面的InputDispatcherThread主要工作是根据event的类型选择分发路劲, 我们主要分析dispatchPointer这一条路径,其实分发的步骤都差不多,所以选择最有代表性的触摸事件分发。接下来看dispatchPointer实现代码:
private int dispatchPointer(QueuedEvent qev, MotionEvent ev, int pid, int uid) { ... Object targetObj = mKeyWaiter.waitForNextEventTarget(null, qev, ev, true, false, pid, uid); ... WindowState target = (WindowState)targetObj; ... // 向Top Activity发送事件 target.mClient.dispatchPointer(ev, eventTime, true); ... }
dispatchPointer代码比较复杂,里面是通过进程通讯机制传送事件的, 那么target.mClient.dispatchPointer(ev, eventTime, true);这句又怎么分辨是发送给Activity的呢? 其中target是WindowState类型
mClient是IWindow类型的,在WindowState初始化的时候传入:
private final class WindowState implements WindowManagerPolicy.WindowState { final IWindow mClient; ... WindowState(Session s, IWindow c, WindowToken token, WindowState attachedWindow, WindowManager.LayoutParams a, int viewVisibility) { ... mClient = c; ... } }
所以在KeyWaiter中的waitForNextEventTarget会得到这一个IWindow代理对象IWindow.Proxy,它对应的代理类是ViewRoot.W,通过远程调用把事件发送到Activity对应的PhoneWindow中。关于PhoneWindow如何与WindowManagerService通讯到时候会另写一个博客,现在只要知道ViewRoot是Activity的Window即PhoneWindow与WindowManagerService连接的桥梁,它们之间的通讯是通过Binder机制实现的。ViewRoot并不是一个android.view.View而是一个Handler。下面一幅图形象的描述一下WindowManagerService与ViewRoot之间的关系:
接下来就看ViewRoot代码了, 我们只选dispatchPointer来研究, 在dispatchPointer方法中ViewRoot是交给handleMessage方法来处理触摸事件的:
@Override public void handleMessage(Message msg) { ... switch (msg.what) { case DISPATCH_POINTER: { ... handled = mView.dispatchTouchEvent(event); ... } break; ... } }
这个mView其实是一个DecorView, 下面来讲一下PhoneWindow, ViewRoot, DecorView以及我们长用的setContentView中设置的内容View的关系:
其中PhoneWindow, DecorView, Layout.xml, content, 以及activity layout的作用请看UI管理系统, 所以触摸事件最终传递到了DecorView中,从上图可以看到,DecorView是窗口中真正地Root, 而RootView只是DecorView的代理来接收WindowManagerService发过来的消息,DecorView才是activity Window的展示内容的平台, DecorView是FrameLayout的子类, 在此事件已经传递到PhoneWindow.DecorView中,其中PhoneWindow在base\policy\src\com\android\internal\policy\impl\PhoneWindow.java, 下面来看一下DecorView.dispatchTouchEvent
@Override public boolean dispatchTouchEvent(MotionEvent ev) { final Callback cb = getCallback(); return cb != null && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev); }
其中的Callback是PhoneWindow绑定的Activity, 在代码中注释mFeatureId是这样的:
/** The feature ID of the panel, or -1 if this is the application's DecorView */ private final int mFeatureId;
如果DecorView是application的DecorView即Activity的DecorView, 它的值就是-1, 所以此时会调用cv.dispatchTouchEvent(ev), 即Activity.dispatchTouchEvent(ev):
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
这里强调一下, 一个触摸事件是由ACTION_DOWN-->ACTION_MOVE-->...-->ACTION_UP组成的,可以看到当ACTION_DOWN到来的时候,会触发onUserInteraction, 这个点不是我们care的, getWindow()方法获取的是Activity的PhoneWindow, 从上图中我们可以看到它与Activity的关系, 接下来我们看一下PhoneWindow.superDispatchTouchEvent(ev):
@Override public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); }
这次我们跟踪到了DecorView, 继续,结果马上揭晓:
public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); }
前面讲到DecorView是FrameLayout, 从上Activity窗口图中可以看到,其实DecorView是Activity真正意义上的Root View, 那么super.dispatchTouchEvent(ev)其实就是ViewGroup触摸事件的分发,这个部分在下一个博客中讨论。
现在回到Activity.dispatchTouchEvent(ev):这一段:
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
其实:
if (getWindow().superDispatchTouchEvent(ev)) { return true; }
这段代码是对View树进行事件分发,如果View树消耗了事件,则不执行Activity.onTouchEvent(ev);给Activity新手提个醒:View比Activity先接收到触摸事件。差不多把所有的图连接起来就比较好看出触摸事件的走向和脉络。大家也可以去做实验,验证一下Activity和View谁先接收到TouchEvent, 看看View是否能够拦截Touch事件,使Activity不能够接收到。
摘抄和参考:
【1】http://blog.csdn.net/stonecao/article/details/6759189