Android事件分发机制

简介: 说在开头,之前项目中使用到了ListView和Button的组合,由于两者都有click事件,也意识到应该是Android的事件分发机制的原因。面试时也特意去恶补过,不过也是一知半解,此次因在项目中遇到该问题特意去详细了解一下。

说在开头,之前项目中使用到了ListView和Button的组合,由于两者都有click事件,也意识到应该是Android的事件分发机制的原因。面试时也特意去恶补过,不过也是一知半解,此次因在项目中遇到该问题特意去详细了解一下。

引言

点击事件的分发机制由于主要发生在界面中,需要先了解Android系统的UI架构,如下图所示。


我们都知道Android程序的UI是由Activity这个组件构成的,而实际中是使用setContentView这个方法设置一个自定义布局的,这里的ContentView就是存放这个自定义布局的。而ContentView和TitleView组成了顶级View,即DecorView,这样就可以看成Activity-Window-View的关系。一个Activity包含一个Window,而Window类是一个抽象类,PhoneWindow实现了该类,PhoneWindow类将一个DecorView设置为应用窗口的根View。点击事件就是从Activity开始,通过PhoneWindow传递到DecorView中。这个分发的流程可以认为一个点击事件一层一层的传递,这一层级不去需要就传递给子层去消费(custom),消费的话告知父层已经消费,没有消费同样告知父层然后父层去是否消费这个事件。

Android点击事件分发流程

Activity首先获取UI的点击事件,点击事件通过dispatchTouchEvent方法继续分发,该方法的源码如下:
    /**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.
     *
     * @param ev The touch screen event.
     *
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
这个方法是个布尔类型的方法,如果事件被消费就返回true,getWindow().superDispatchTouch(ev)是调用的Window类中的 superDispatchTouch方法,到此Activity将点击事件传递给Window中。在引言中说过,Window类是个抽象类本身不能实例化,是由PhoneWindow类来实现的,不过我们可以看一眼Window类(主要就是官方的解释),省略掉其他方法。
/**
 * Abstract base class for a top-level window look and behavior policy.  An
 * instance of this class should be used as the top-level view added to the
 * window manager. It provides standard UI policies such as a background, title
 * area, default key processing, etc.
 *
 * <p>The only existing implementation of this abstract class is
 * android.policy.PhoneWindow, which you should instantiate when needing a
 * Window.  Eventually that class will be refactored and a factory method
 * added for creating Window instances without knowing about a particular
 * implementation.
 */
public abstract class Window {
......
   /**
     * Used by custom windows, such as Dialog, to pass the key shortcut press event
     * further down the view hierarchy. Application developers should
     * not need to implement or call this.
     *
     */
    public abstract boolean superDispatchTouchEvent(MotionEvent event);

......
}
类的官方说明中说到仅有的实现这个抽象类的就是android.policy.PhoneWindow类,而superDispatchEvent方法也是个抽象布尔类型的方法,将事件传递到view层,特别明确说到程序开发者不需要实现或者调用这个方法。既然是PhoneWindow类实现的这个方法,下面就要转到PhoneWindow类中。实现代码只有一句:
    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }
mDecor是DecorView的一个实例,可以看到PhoneWindow将事件分发到DecorView(一个final类),至此点击事件终于来到了根View中。而DecorView中包括了ContentView,一般ContentView就是我们常用到的View,而它往往是一个ViewGroup(如LinearLayout),可以直接看ViewGroup中对点击事件的处理,由于实现代码太多,这里只摘取部分关键代码做解释用。
public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            ......

            // Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }

            ......

            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                // Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }

        }

        if (!handled && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;
    }

截取的代码中第二个if中做了两个事情,第一个是检查是否需要拦截,如果在这一层需要拦截消耗,如果onInterceptTouchEvent返回true,说明要拦截这个事件,随后会调用onTouchEvent方法去消费这个事件,这里要注意ViewGroup是没有onTouchEvent方法的,这个方法存在于View中,ViewGroup中是默认不拦截事件的,会先分发到子View中进行消费。第二个方法调用了ViewGroup对点击事件处理的方法dispatchTransformedTouchEvent。篇幅影响就先不看这个类了(其实也没怎么看懂。。。)

最后来看View的dispatchTouchEvent方法
/**
     * Pass the touch screen motion event down to the target view, or this
     * view if it is the target.
     *
     * @param event The motion event to be dispatched.
     * @return True if the event was handled by the view, false otherwise.
     */
    public boolean dispatchTouchEvent(MotionEvent event) {
        boolean result = false;

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }

        if (onFilterTouchEventForSecurity(event)) {
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

        if (!result && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }

        // Clean up after nested scrolls if this is the end of a gesture;
        // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
        // of the gesture.
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }

        return result;
    }
在这里dispatchTouchEvent首先会调用onTouch方法,当然如果没有OnTouchListener就会直接调用onTouchEvent。如果dispatchTouchEvent或者onTouchEvent返回true,证明点击事件被消费,不再往子View中分发;而如果onTouchEvent返回false,则点击事件又传递给父View,由父View去消费以此类推,直到ViewGroup。如果ViewGroup也无法处理,就会调用Activity的onTouchEvent方法来消费这个点击事件了。

开头的案例

开头说过遇到的ListView和Button的点击事件冲突问题,其实在布局文件中添加两行代码即可,在Button的属性中添加
android:focusable="false"
而在ListView所在布局文件的根布局中,如顶层的LinearLayout中添加
android:descendantFocusability="blocksDescendants"
即可
这样可以即实现Button的OnClick方法,也可以使用ListView的OnItemClick方法了。

写在最后

很久没有写博客了,尤其是稍微有点技术含量的就更少了,不足之处还是有很多的,希望能继续完善自己的技能了和写作的方式。写这篇文章也借鉴了不少网络上的资源,这里用的Android源码是5.0的,没有用到比较新的6.x和7.x,不过这个模块应该都差不多。





目录
相关文章
|
1天前
|
存储 缓存 Android开发
Android RecyclerView 缓存机制深度解析与面试题
本文首发于公众号“AntDream”,详细解析了 `RecyclerView` 的缓存机制,包括多级缓存的原理与流程,并提供了常见面试题及答案。通过本文,你将深入了解 `RecyclerView` 的高性能秘诀,提升列表和网格的开发技能。
16 8
|
1月前
|
消息中间件 存储 Java
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
Android 消息处理机制估计都被写烂了,但是依然还是要写一下,因为Android应用程序是通过消息来驱动的,Android某种意义上也可以说成是一个以消息驱动的系统,UI、事件、生命周期都和消息处理机制息息相关,并且消息处理机制在整个Android知识体系中也是尤其重要,在太多的源码分析的文章讲得比较繁琐,很多人对整个消息处理机制依然是懵懵懂懂,这篇文章通过一些问答的模式结合Android主线程(UI线程)的工作原理来讲解,源码注释很全,还有结合流程图,如果你对Android 消息处理机制还不是很理解,我相信只要你静下心来耐心的看,肯定会有不少的收获的。
105 3
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
|
1月前
|
图形学 Android开发
小功能⭐️Unity调用Android常用事件
小功能⭐️Unity调用Android常用事件
|
1月前
|
Android开发
Android面试高频知识点(1) 图解 Android 事件分发机制
在Android开发中,事件分发机制是一块Android比较重要的知识体系,了解并熟悉整套的分发机制有助于更好的分析各种点击滑动失效问题,更好去扩展控件的事件功能和开发自定义控件,同时事件分发机制也是Android面试必问考点之一,如果你能把下面的一些事件分发图当场画出来肯定加分不少。废话不多说,总结一句:事件分发机制很重要。
99 9
|
30天前
|
开发工具 Android开发
Android项目架构设计问题之组件A通知组件B某个事件的发生如何解决
Android项目架构设计问题之组件A通知组件B某个事件的发生如何解决
28 0
|
3月前
|
安全 算法 数据安全/隐私保护
探索iOS与Android的隐私保护机制
【6月更文挑战第5天】在数字时代,隐私保护已成为用户最关心的问题之一。iOS和Android作为两大主流操作系统,各自发展出了独特的隐私保护技术。本文将深入探讨这两个平台在隐私保护方面的策略、技术和挑战。
82 3
|
3月前
|
Android开发
39. 【Android教程】触摸事件分发
39. 【Android教程】触摸事件分发
32 2
|
3月前
|
Android开发
38. 【Android教程】Handler 消息传递机制
38. 【Android教程】Handler 消息传递机制
42 2
|
3月前
|
XML Android开发 数据格式
37. 【Android教程】基于监听的事件处理机制
37. 【Android教程】基于监听的事件处理机制
59 2
|
3月前
|
Android开发 虚拟化 异构计算
一文搞定Android VSync机制来龙去脉
一文搞定Android VSync机制来龙去脉
132 0