您的位置:首页 > 移动开发 > Android开发

android事件机制之来龙去脉

2013-01-11 15:27 239 查看
在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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: