android事件机制之来龙去脉

来源:互联网 发布:东北爷们网络神曲 编辑:程序博客网 时间:2024/05/19 14:40
在android搞版本中事件的处理大部分内容移到了NDK层处理(具体从哪个版本开始没有具体调查),在NDK层由于水平不够研究起来比较吃力,也是因为按照大牛博文中的路子研究的,所以选择android2.2源码研究,主要目的是学习android事件机制,更好为写上层应用服务,也深入了解一下这里面的架构设计思想。

  一、事件来源

  机器中一般一套硬件资源对应多个应用, 那么android系统如何把按键, 触摸, 滚轮等硬件信号发送到Top Activity中呢,我们就按部就班,先来看一下事件的来源。
  在窗口管理服务WindowManagerService(android源码base/services/java/com/android/server/WindowManagerService.java)中有一个事件队列mQueue缓存着按键, 触摸, 滚轮等事件,在mQueue中有一个
InputDeviceReader线程读取事件,在WindowManagerService中有InputDispatcherThread线程向上层窗口分发事件, 具体细节看android源码, 下面绘制一张流程图:

                                           

图中的箭头表示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方法来处理触摸事件的:

@Overridepublic 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

@Overridepublic 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):

@Overridepublic 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

【2】http://blog.csdn.net/maxleng/article/details/5557758

【3】http://blog.csdn.net/windskier/article/details/6966264

原创粉丝点击