ITPub博客

首页 > IT职业 > IT图书 > 职责链模式之Android事件分发源码分析

职责链模式之Android事件分发源码分析

IT图书 作者:maggiefengyu 时间:2021-09-09 09:32:46 0 删除 编辑

1、职责链模式概述:

职责链模式(Chain of Responsibility  Pattern):避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。职责链模式是一种对象行为型模式。《设计模式的艺术》

使用场景:

有企业OA系统开发经验的同学,会对职责链模式有比较深刻的理解。在多数OA系统中都有请假、物资申请等各种审批流程,这些流程比较复杂,一个请求对应着多个处理者,而且不同角色的处理者对应的层级不同。因此大多数设计人员都会采用职责链模式来进行流程设计,降低系统耦合性,提升扩展性。

2、策略模式UML类图:

图片描述

image.png

Handler(抽象处理类):作为所有处理者的父类,定义处理者的公共接口,并且持有一个后继节点处理者Handler的对象引用;
ConcreteHandler(具体处理类):继承抽象处理类,实现处理请求的方法。

Android事件分发剖析

图片描述

image.png

上图展示了一个事件从输入到最终处理的一个基本流程,其中省略了window这一层,事实上事件的分发顺序为:Activity--->PhoneWindow--->DecorView---->具体布局。图中出现了3个方法:dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent,这三个方法负责完成一个事件的分发。Android系统通过职责链模式实现事件分发功能,自顶向下遍历所有的处理者,直至找到一个处理者处理事件,或者都不处理。Activity、Window、ViewGroup、叶子View存在层次,事件会按照上述层次进行传递。当然,View体系也运用了组合模式进行设计,本篇文章只分析事件分发机制。下面分析上述3个方法的源码:

View类:

public boolean dispatchTouchEvent(MotionEvent event) {        // If the event should be handled by accessibility focus first.
        if (event.isTargetAccessibilityFocus()) {            // We don't have focus or no virtual descendant has it, do not handle the event.
            if (!isAccessibilityFocusedViewOrHost()) {                return false;
            }            // We have focus and got the event, then use normal event dispatch.
            event.setTargetAccessibilityFocus(false);
        }        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;
            }            //View的dispatchTouchEvent方法若是正常流程一定会调用onTouchEvent方法
            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;
    }

ViewGroup类:

 public boolean dispatchTouchEvent(MotionEvent ev) {        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }        // If the event targets the accessibility focused view and this is it, start
        // normal event dispatch. Maybe a descendant is what will handle the click.
       //对于辅助功能的事件处理 
        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }        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) {                //down事件处理
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;                if (!disallowIntercept) {                    //disallowIntercept默认是false,意味着肯定会调用onInterceptTouchEvent方法
                    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;
            }            // 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.
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null;            boolean alreadyDispatchedToNewTouchTarget = false;            //事件未被取消、拦截
            if (!canceled && !intercepted) {                // If the event is targeting accessiiblity focus we give it to the
                // view that has accessibility focus and if it does not handle it
                // we clear the flag and dispatch the event to all children as usual.
                // We are looking up the accessibility focused host to avoid keeping
                // state since these events are very rare.
                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;                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  preorderedList = buildOrderedChildList();                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();                        final View[] children = mChildren;                        //遍历所有的子View,寻找事件处理者
                        for (int i = childrenCount - 1; i >= 0; i--) {                            final int childIndex = customOrder
                                    ? getChildDrawingOrder(childrenCount, i) : i;                            final View child = (preorderedList == null)
                                    ? children[childIndex] : preorderedList.get(childIndex);                            // If there is a view that has accessibility focus we want it
                            // to get the event first and if not handled we will perform a
                            // normal dispatch. We may do a double iteration but this is
                            // safer given the timeframe.
                            if (childWithAccessibilityFocus != null) {                                if (childWithAccessibilityFocus != child) {                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {                                //该子View无法处理,跳出本次循环继续遍历
                                ev.setTargetAccessibilityFocus(false);                                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);                           //真正分发事件的方法,若子View为ViewGroup则继续则递归调用,重复此过程
                           //若子View就是叶子View那么就调用该View的dispatchTouchEvent
                            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 

上述源码可以看出,View事件分发机制,对于View而言,onTouch、onTouchEvent、onClick优先级依次递减。

4、优缺点分析:

优点:

1)将请求与处理者分离,请求事件无须知道具体由哪个处理者进行处理,降低系统耦合;
2)当系统需要增加新的处理者类时,无须需改原系统代码,符合开闭原则;

缺点:

1)由于请求者并不知道职责链配置规则,有可能出现请求不被任何处理者处理;
2)职责链建立不当,容易出现死循环;

结束语

职责链模式在软件设计中经常被用到,其使用场景特征比较明显。在使用职责链模式时,要划分好各个处理类的条件,切记不要出现死循环。



作者:Tifkingsly
链接:https://www.jianshu.com/p/88fc33c2807c

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/925/viewspace-2804532/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2019-04-15

  • 博文量
    68
  • 访问量
    581