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.csdn.net/stonecao/article/details/6759189

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

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

Python网络爬虫与推荐算法新闻推荐平台:网络爬虫:通过Python实现新浪新闻的爬取,可爬取新闻页面上的标题、文本、图片、视频链接(保留排版) 推荐算法:权重衰减+标签推荐+区域推荐+热点推荐.zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值