[Android设计模式之旅]——责任链模式

介绍

责任链模式(Chain of Responsibility Pattern)就是当我们发送一个请求后,沿着一个任务链执行,任务链上每个对象都能处理该请求,如果一个对象不处理,就会传递给下一个对象。这原理好像跟事件分发机制有点像啊!

意图

职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。所以责任链模式的意图主要是为了解耦,避免发送者和接收者耦合在一起

使用场景

  • 有多个对象处理同一个请求的时候,具体哪个对象处理请求由运行时决定。
  • 可动态指定一组对象处理请求
  • 在不明确接收者的情况下,想多个对象发送同一请求。

UML图

在这里插入图片描述

  • Client 客户端
  • Handler:抽象处理者角色,声明一个处理请求的方法,并保持对下一个处理节点Handler对象的引用。
  • ConcreteHandler: 具体的处理者,对请求进行处理,如果不处理就讲请求转发给下一个节点上的处理对象。

Android源码中的应用

我们前面发现责任链模式和事件分发机制有点像,那是不是事件分发机制就是运用了责任链模式昵?下面我们通过源码来看看:
1、首先我们看Activity的dispatchTouchEvent()方法:

    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

我们看到这里调用来getWindow().superDispatchTouchEvent(ev),我们知道这个getWindow()返回的是PhoneWindow对象。
2、那么我们继续看PhoneWindowsuperDispatchTouchEvent(ev)方法:

 @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

这里,它返回了mDecor.superDispatchTouchEvent(event) ,我们先看mDecor对象,它是DecorView的一个对象,DecorView是PhoneWindow的现在最上面的一层。

// This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;

所以上面mDecor.superDispatchTouchEvent(event)DecorViewsuperDispatchTouchEvent(event)方法,那我们继续往下看:
4、DecorViewsuperDispatchTouchEvent(event)方法

public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
}

看到这里我们就会发现,当我们触摸屏幕时,事件传递是:Activity–>PhoneWindow–>DecorView。
我们继续看他返回了 DecorView 父类的dispatchTouchEvent(event)。我们看DecorView的构造:

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
    private static final String TAG = "DecorView";
    //省略代码
}

我们发现,DecorView继承自FrameLayout,那我们继续看FrameLayout,我们会发现它里面没有dispatchTouchEvent(event)方法,那我们继续找它的父类,由于FrameLayout继承自ViewGroup,所以我们继续看看ViewGroupdispatchTouchEvent(event)
5、ViewGroupdispatchTouchEvent(event)
ViewGroupdispatchTouchEvent(event)不同SDK版本不一致,但万变不离其综,这里看Android 8.0 的源码。以下分析摘自(https://segmentfault.com/a/1190000015983576)

    public boolean dispatchTouchEvent(MotionEvent ev) {

        ... ...  // 分析关键代码
    
        boolean handled = false;  // 这个变量用于记录事件是否被处理完
        
        // 过滤掉一些不合法的事件
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;
            
            // Handle an initial down.
            // 判断是不是Down事件,如果是的话,就要做初始化操作
            if (actionMasked == MotionEvent.ACTION_DOWN) {
               /* 
                * 如果是down事件,就要清空掉之前的状态,比如:重置手势判断。
                * 比如:之前在判断是不是一个单点的滑动,但是第二个down来了,就表示不可能是单点的滑动,要重新开始判断触摸的手势
                * 清空掉 mFirstTouchTarget
                */
                cancelAndClearTouchTargets(ev);
                resetTouchState();       // mFirstTouchTarget = null;
            }

        
            // Check for interception,检查是否拦截事件.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {            // 如果当前是Down事件,或者已经有处理Touch事件的目标了
                /*
                 * disallowIntercept:是否禁用事件拦截的功能(默认是false)
                 * 可通过调用requestDisallowInterceptTouchEvent()修改
                 * 使用与运算作为判断,可以让我们在flag中,存储好几个标志
                 */
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;     ?
                if (!disallowIntercept) {
                    // ViewGroup每次事件分发时,都需调用onInterceptTouchEvent()询问是否拦截事件
                    intercepted = onInterceptTouchEvent(ev);     ?
                    /// M : add log to help debugging
                    if (intercepted == true && ViewDebugManager.DEBUG_TOUCH) {
                        Log.d(TAG, "Touch event was intercepted event = " + ev
                                + ",this = " + this);
                    }
                    // 重新恢复Action,以免action在上面的步骤被人为地改变了
                    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.
                // 如果说,事件已经初始化过了,并且没有子View被分配处理,那么就说明,这个ViewGroup已经拦截了这个事件
                intercepted = true;
            }

            // If intercepted, start normal event dispatch. Also if there is already
            // a view that is handling the gesture, do normal event dispatch.
            if (intercepted || mFirstTouchTarget != null) {
                ev.setTargetAccessibilityFocus(false);
            }

            // Check for cancelation,标志着取消事件.
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            // Update list of touch targets for pointer down, if needed.
            // 如果需要(不是取消,也没有被拦截)的话,那么在触摸down事件的时候更新触摸目标列表
            // split:代表当前的ViewGroup是不是支持分割MotionEvent到不同的View当中
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;

            // 新的触摸对象
            TouchTarget newTouchTarget = null;
            
            //是否把事件分配给了新的触摸
            boolean alreadyDispatchedToNewTouchTarget = false;
            
            ????????????????重点方法????????????????
            if (!canceled && !intercepted) {      // 如果事件不是取消事件,也没有拦截,那么进入此函数

                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;

                /*
                 * 如果是个全新的Down事件
                 * 或者是有新的触摸点
                 * 或者是光标来回移动事件(不太明白什么时候发生)
                 */
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {

                    // 事件的索引,down事件的index:0
                    final int actionIndex = ev.getActionIndex(); // always 0 for down

                    // 获取分配的ID的bit数量
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;

                    // 清理之前触摸这个指针标识,以防它们的目标变得不同步
                    removePointersFromTouchTargets(idBitsToAssign);

                    final int childrenCount = mChildrenCount;
                    
                    // 如果新的触摸对象为null & 当前ViewGroup有子元素
                    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 ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        
                        // 通过for循环,遍历了当前ViewGroup下的所有子View     ?
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            ???????????????????????????????????
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);

                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }

                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }
                            ???????????????????????????????????

                            // 获取新的触摸对象,如果当前的子View在之前的触摸目标的列表当中就返回touchTarget
                            // 子View不在之前的触摸目标列表那么就返回null
                            newTouchTarget = getTouchTarget(child);
                            
                            // 如果新的触摸目标对象不为空,那么就把这个触摸的ID赋予它
                            // 这个触摸的目标对象的id就含有了好几个pointer的ID了
                            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;
                            }

                            // 如果子View不在之前的触摸目标列表中,先重置childView的标志,去除掉CANCEL的标志
                            resetCancelNextUpFlag(child);


                            /*
                             * 调用子View的dispatchTouchEvent,并且把pointer的id赋予进去
                             * 如果说,子View接收并且处理了这个事件,那么就更新上一次触摸事件的信息,
                             * 并且创建一个新的触摸目标对象,并且绑定这个子View和Pointer的ID
                             */
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {     ?
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    // childIndex points into presorted list, find original index
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }

                            // The accessibility focus didn't handle the event, so clear
                            // the flag and do a normal dispatch to all children.
                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }

                    /*
                     * 如果newTouchTarget为null,就代表,这个事件没有找到子View去处理它,
                     * 如果之前已经有了触摸对象(比如,我点了一张图,另一个手指在外面图的外面点下去)
                     * 那么就把这个之前那个触摸目标定为第一个触摸对象,并且把这个触摸(pointer)分配给最近添加的触摸目标
                     */
                    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,如果没有触摸目标.
            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;
                // 遍历TouchTargt树.分发事件,如果我们已经分发给了新的TouchTarget,那么我们就不再分发给newTouchTarget
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        // 是否让child取消处理事件,如果为true,就会分发给child一个ACTION_CANCEL事件
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        // 派发事件
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }

                        // cancelChild:派发给了当前child一个ACTION_CANCEL事件,
                        // 移除这个child
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                // 把下一个赋予父节点的上一个,这样当前节点就被丢弃了
                                predecessor.next = next;
                            }
                            
                            // 回收内存
                            target.recycle();
                            
                            // 把下一个赋予现在
                            target = next;

                            // 下面的两行不执行了,因为我们已经做了链表的操作了。
                            // 主要是我们不能执行predecessor=target,因为删除本节点的话,父节点还是父节点
                            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) {
                // 如果是多点触摸下的手指抬起事件,就要根据idBit从TouchTarget中移除掉对应的Pointer(触摸点)
                final int actionIndex = ev.getActionIndex();
                final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
                removePointersFromTouchTargets(idBitsToRemove);
            }
        }

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

看到这里是不是晕了,??。在这里插入图片描述

下面我们捋一下大致的逻辑,总之就是如果自 View消耗事件 就将事件消耗掉否则返回。
Activity–>PhoneWindow–>DecorView–>ViewGroup–>…–>View。
这个就是一条责任链。
这里看下大致流程图:
在这里插入图片描述

总结

优点

  • 降低耦合度,便于拓展,提高代码灵活性。
  • 责任链对象互相链接,只用想头部发起请求。
    缺点
  • 如果责任链太长,或者每条链判断处理的时间太长会影响性能。特别是递归循环的时候。
  • 请求不一定能得到处理,可能会没有对象处理。

感谢

https://segmentfault.com/a/1190000015983576
http://www.runoob.com/design-pattern/chain-of-responsibility-pattern.html

发布了96 篇原创文章 · 获赞 235 · 访问量 29万+

猜你喜欢

转载自blog.csdn.net/u013132758/article/details/88761418