android事件机制之来龙去脉

本文详细剖析了Android系统中触摸事件的处理流程,从硬件输入设备到顶层Activity的全过程,包括事件的读取、分发及最终处理。通过源码解读的方式,帮助读者理解Android事件机制的核心原理。
在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方法来处理触摸事件的:

@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.youkuaiyun.com/stonecao/article/details/6759189

【2】http://blog.youkuaiyun.com/maxleng/article/details/5557758

【3】http://blog.youkuaiyun.com/windskier/article/details/6966264

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值