zoukankan      html  css  js  c++  java
  • Android 事件分发机制

    转载自:http://blog.csdn.net/singwhatiwanna/article/details/17339857

    概述

    一直想写篇关于Android事件派发机制的文章,却一直没写。这两天刚好是周末。有时间了,想想写一篇吧。不然总是仅仅停留在会用的层次上可是无法了解其内部机制。

    我用的是4.4源代码,打开看看,挺复杂的,尤其是事件是怎么从Activity派发出来的。太费解了。了解Windows消息机制的人会发现。认为Android的事件派发机制和Windows的消息派发机制挺像的,事实上这是一种典型的消息“冒泡”机制,非常多平台採用这个机制,消息最先到达最底层View,然后它先进行推断是不是它所须要的,否则就将消息传递给它的子View。这样一来,消息就从水底的气泡一样向上浮了一点距离。以此类推。气泡达到顶部和空气接触,破了(消息被处理了)。当然也有气泡浮出到顶层了。还没破(消息无人处理)。这个消息将由系统来处理。对于Android来说。会由Activity来处理。

    Android点击事件的派发机制

    1. 从Activity传递究竟层View

    点击事件用MotionEvent来表示。当一个点击操作发生时,事件最先传递给当前Activity,由Activity的dispatchTouchEvent来进行事件派发,详细的工作是由Activity内部的Window来完毕的,Window会将事件传递给decor view,decor view一般就是当前界面的底层容器(即setContentView所设置的View的父容器),通过Activity.getWindow.getDecorView()能够获得。另外。看以下代码的的时候。主要看我凝视的地方。代码非常多非常复杂,我无法一一说明。可是我凝视的地方都是关键点,是博主细致读代码总结出来的。

    源代码解读:

    事件是由哪里传递给Activity的。这个我还不清楚。可是不要紧。我们从activity開始分析,已经足够我们了解它的内部实现了。

    Code:Activity#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();
            }
            //这里事件開始交给Activity所附属的Window进行派发,假设返回true。整个事件循环就结束了
            //返回false意味着事件没人处理。全部人的onTouchEvent都返回了false,那么Activity就要来做最后的收场。
            if (getWindow().superDispatchTouchEvent(ev)) {
                return true;
            }
            //这里,Activity来收场了,Activity的onTouchEvent被调用
            return onTouchEvent(ev);
        }

    Window是怎样将事件传递给ViewGroup的

    Code:Window#superDispatchTouchEvent

        /**
         * Used by custom windows, such as Dialog, to pass the touch screen event
         * further down the view hierarchy. Application developers should
         * not need to implement or call this.
         *
         */
        public abstract boolean superDispatchTouchEvent(MotionEvent event);
    这居然是一个抽象函数,还注明了应用开发人员不要实现它或者调用它。这是什么情况?再看看例如以下类的说明,大意是说:这个类能够控制顶级View的外观和行为策略。并且还说这个类的唯一一个实现位于android.policy.PhoneWindow。当你要实例化这个Window类的时候。你并不知道它的细节,由于这个类会被重构,仅仅有一个工厂方法能够使用。

    好吧。还是非常模糊啊,不太懂,只是我们能够看一下android.policy.PhoneWindow这个类。虽然实例化的时候此类会被重构,可是重构而已,功能是类似的。

    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.

    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. 

    Code:PhoneWindow#superDispatchTouchEvent

        @Override
        public boolean superDispatchTouchEvent(MotionEvent event) {
            return mDecor.superDispatchTouchEvent(event);
        }
    这个逻辑非常清晰了。PhoneWindow将事件传递给DecorView了。这个DecorView是啥呢,请看以下

        private final class DecorView extends FrameLayout implements RootViewSurfaceTaker
    
        // This is the top-level view of the window, containing the window decor.
        private DecorView mDecor;
    
        @Override
        public final View getDecorView() {
            if (mDecor == null) {
                installDecor();
            }
            return mDecor;
        }

    顺便说一下。平时Window用的最多的就是((ViewGroup)getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0)即通过Activity来得到内部的View。

    这个mDecor显然就是getWindow().getDecorView()返回的View。而我们通过setContentView设置的View是它的一个子View。

    眼下事件传递到了DecorView 这里。因为DecorView 继承自FrameLayout且是我们的父View。所以终于事件会传递给我们的View,原因先无论了。换句话来说,事件肯定会传递到我们的View,不然我们的应用怎样响应点击事件呢。

    只是这不是我们的重点,重点是事件到了我们的View以后应该怎样传递。这是对我们更实用的。从这里開始。事件已经传递到我们的顶级View了,注意:顶级View实际上是最底层View,也叫根View。

    2.底层View对事件的分发过程

    点击事件究竟层View(通常是一个ViewGroup)以后,会调用ViewGroup的dispatchTouchEvent方法,然后的逻辑是这种:假设底层ViewGroup拦截事件即onInterceptTouchEvent返回true。则事件由ViewGroup处理。这个时候。假设ViewGroup的mOnTouchListener被设置,则会onTouch会被调用,否则,onTouchEvent会被调用。也就是说,假设都提供的话,onTouch会屏蔽掉onTouchEvent。在onTouchEvent中。假设设置了mOnClickListener,则onClick会被调用。

    假设顶层ViewGroup不拦截事件,则事件会传递给它的在点击事件链上的子View,这个时候,子View的dispatchTouchEvent会被调用。到此为止,事件已经从最底层View传递给了上一层View。接下来的行为和其底层View一致。如此循环,完毕整个事件派发。另外要说明的是,ViewGroup默认是不拦截点击事件的,其onInterceptTouchEvent返回false。

    源代码解读:

    Code:ViewGroup#dispatchTouchEvent

        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            if (mInputEventConsistencyVerifier != null) {
                mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
            }
    
            boolean handled = false;
            if (onFilterTouchEventForSecurity(ev)) {
                final int action = ev.getAction();
                final int actionMasked = action & MotionEvent.ACTION_MASK;
    
                // Handle an initial down.
                if (actionMasked == MotionEvent.ACTION_DOWN) {
                    // Throw away all previous state when starting a new touch gesture.
                    // The framework may have dropped the up or cancel event for the previous gesture
                    // due to an app switch, ANR, or some other state change.
                    cancelAndClearTouchTargets(ev);
                    resetTouchState();
                }
    
                // Check for interception.
                final boolean intercepted;
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || mFirstTouchTarget != null) {
                    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                    if (!disallowIntercept) {
    		            //这里推断是否拦截点击事件,假设拦截。则intercepted=true
                        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;
                }
    
                // Check for cancelation.
                final boolean canceled = resetCancelNextUpFlag(this)
                        || actionMasked == MotionEvent.ACTION_CANCEL;
    
                // Update list of touch targets for pointer down, if needed.
                final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
                TouchTarget newTouchTarget = null;
                boolean alreadyDispatchedToNewTouchTarget = false;
    	            //这里面一大堆是派发事件到子View,假设intercepted是true,则直接跳过
                if (!canceled && !intercepted) {
                    if (actionMasked == MotionEvent.ACTION_DOWN
                            || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                        final int actionIndex = ev.getActionIndex(); // always 0 for down
                        final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                                : TouchTarget.ALL_POINTER_IDS;
    
                        // Clean up earlier touch targets for this pointer id in case they
                        // have become out of sync.
                        removePointersFromTouchTargets(idBitsToAssign);
    
                        final int childrenCount = mChildrenCount;
                        if (newTouchTarget == null && childrenCount != 0) {
                            final float x = ev.getX(actionIndex);
                            final float y = ev.getY(actionIndex);
                            // Find a child that can receive the event.
                            // Scan children from front to back.
                            final View[] children = mChildren;
    
                            final boolean customOrder = isChildrenDrawingOrderEnabled();
                            for (int i = childrenCount - 1; i >= 0; i--) {
                                final int childIndex = customOrder ?

    getChildDrawingOrder(childrenCount, i) : i; final View child = children[childIndex]; if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; } newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { // Child is already receiving touch within its bounds. // Give it the new pointer in addition to the ones it is handling. newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); mLastTouchDownIndex = childIndex; mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); //注意以下两句。假设有子View处理了点击事件。则newTouchTarget会被赋值。 //同一时候alreadyDispatchedToNewTouchTarget也会为true。这两个变量是直接影响以下的代码逻辑的。 newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } } } if (newTouchTarget == null && mFirstTouchTarget != null) { // Did not find a child to receive the event. // Assign the pointer to the least recently added target. newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } } } // Dispatch to touch targets. //这里假设当前ViewGroup拦截了事件,或者其子View的onTouchEvent都返回了false。则事件会由ViewGroup处理 if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. //这里就是ViewGroup对点击事件的处理 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; } } // Update list of touch targets for pointer up or cancel, if needed. if (canceled || actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { resetTouchState(); } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) { final int actionIndex = ev.getActionIndex(); final int idBitsToRemove = 1 << ev.getPointerId(actionIndex); removePointersFromTouchTargets(idBitsToRemove); } } if (!handled && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1); } return handled; }

    以下再看ViewGroup对点击事件的处理

    Code:ViewGroup#dispatchTransformedTouchEvent

        /**
         * Transforms a motion event into the coordinate space of a particular child view,
         * filters out irrelevant pointer ids, and overrides its action if necessary.
         * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
         */
        private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                View child, int desiredPointerIdBits) {
            final boolean handled;
    
            // Canceling motions is a special case.  We don't need to perform any transformations
            // or filtering.  The important part is the action, not the contents.
            final int oldAction = event.getAction();
            if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
                event.setAction(MotionEvent.ACTION_CANCEL);
                if (child == null) {
    		        //这里就是ViewGroup对点击事件的处理,其调用了View的dispatchTouchEvent方法
                    handled = super.dispatchTouchEvent(event);
                } else {
                    handled = child.dispatchTouchEvent(event);
                }
                event.setAction(oldAction);
                return handled;
            }
    
            // Calculate the number of pointers to deliver.
            final int oldPointerIdBits = event.getPointerIdBits();
            final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
    
            // If for some reason we ended up in an inconsistent state where it looks like we
            // might produce a motion event with no pointers in it, then drop the event.
            if (newPointerIdBits == 0) {
                return false;
            }
    
            // If the number of pointers is the same and we don't need to perform any fancy
            // irreversible transformations, then we can reuse the motion event for this
            // dispatch as long as we are careful to revert any changes we make.
            // Otherwise we need to make a copy.
            final MotionEvent transformedEvent;
            if (newPointerIdBits == oldPointerIdBits) {
                if (child == null || child.hasIdentityMatrix()) {
                    if (child == null) {
                        handled = super.dispatchTouchEvent(event);
                    } else {
                        final float offsetX = mScrollX - child.mLeft;
                        final float offsetY = mScrollY - child.mTop;
                        event.offsetLocation(offsetX, offsetY);
    
                        handled = child.dispatchTouchEvent(event);
    
                        event.offsetLocation(-offsetX, -offsetY);
                    }
                    return handled;
                }
                transformedEvent = MotionEvent.obtain(event);
            } else {
                transformedEvent = event.split(newPointerIdBits);
            }
    
            // Perform any necessary transformations and dispatch.
            if (child == null) {
                handled = super.dispatchTouchEvent(transformedEvent);
            } else {
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                transformedEvent.offsetLocation(offsetX, offsetY);
                if (! child.hasIdentityMatrix()) {
                    transformedEvent.transform(child.getInverseMatrix());
                }
    
                handled = child.dispatchTouchEvent(transformedEvent);
            }
    
            // Done.
            transformedEvent.recycle();
            return handled;
        }
    再看

    Code: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) {
            if (mInputEventConsistencyVerifier != null) {
                mInputEventConsistencyVerifier.onTouchEvent(event, 0);
            }
    
            if (onFilterTouchEventForSecurity(event)) {
                //noinspection SimplifiableIfStatement
                ListenerInfo li = mListenerInfo;
                if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                        && li.mOnTouchListener.onTouch(this, event)) {
                    return true;
                }
    
                if (onTouchEvent(event)) {
                    return true;
                }
            }
    
            if (mInputEventConsistencyVerifier != null) {
                mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
            }
            return false;
        }
    这段代码比較简单,View对事件的处理是这种:假设设置了OnTouchListener就调用onTouch,否则就直接调用onTouchEvent,而onClick是在onTouchEvent内部通过performClick触发的。简单来说。事件假设被ViewGroup拦截或者子View的onTouchEvent都返回了false,则事件终于由ViewGroup处理。

    3.无人处理的点击事件

    假设一个点击事件。子View的onTouchEvent返回了false,则父View的onTouchEvent会被直接调用,以此类推。假设全部的View都不处理,则终于会由Activity来处理。这个时候,Activity的onTouchEvent会被调用。

    这个问题已经在1和2中做了说明。


  • 相关阅读:
    Atitit sql计划任务与查询优化器统计信息模块
    Atitit  数据库的事件机制触发器与定时任务attilax总结
    Atitit 图像处理知识点体系知识图谱 路线图attilax总结 v4 qcb.xlsx
    Atitit 图像处理 深刻理解梯度原理计算.v1 qc8
    Atiti 数据库系统原理 与数据库方面的书籍 attilax总结 v3 .docx
    Atitit Mysql查询优化器 存取类型 范围存取类型 索引存取类型 AND or的分析
    Atitit View事件分发机制
    Atitit 基于sql编程语言的oo面向对象大规模应用解决方案attilax总结
    Atitti 存储引擎支持的国内点与特性attilax总结
    Atitit 深入理解软件的本质 attilax总结 软件三原则"三次原则"是DRY原则和YAGNI原则的折
  • 原文地址:https://www.cnblogs.com/wzjhoutai/p/7153584.html
Copyright © 2011-2022 走看看