介绍

责任链模式(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)方法:

 @Overridepublic 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 debuggingif (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:0final 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不在之前的触摸目标列表那么就返回nullnewTouchTarget = 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 indexfor (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,那么我们就不再分发给newTouchTargetwhile (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事件,// 移除这个childif (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

[Android设计模式之旅]——责任链模式相关推荐

  1. 简易理解设计模式之:责任链模式——OA中请假流程示例

    介绍: 责任链模式属于行为型设计模式.它的定义为:使多个对象都有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系.将这些对象连成一条链,并沿着这条链传递该请求,只到有对象处理它为止. 类图: ...

  2. 《java设计模式》之责任链模式

    在阎宏博士的<JAVA与模式>一书中开头是这样描述责任链(Chain of Responsibility)模式的: 责任链模式是一种对象的行为模式.在责任链模式里,很多对象由每一个对象对其 ...

  3. 设计模式-请假流程-责任链模式

    责任链模式的定义 ​ 首先我们可以先看一下责任链模式的定义: ​ Avoid coupling the sender of a request to its receiver by giving mo ...

  4. 设计模式探索之责任链模式

    责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链.这种模式给予请求的类型,对请求的发送者和接收者进行解耦.这种类型的设计模式属于行为型模式. ...

  5. Java描述设计模式(15):责任链模式

    本文源码:GitHub·点这里 || GitEE·点这里 一.生活场景描述 1.请假审批流程 公司常见的请假审批流程:请假天数 当 day<=3 天,项目经理审批 当 3<day<= ...

  6. [设计模式-行为型]责任链模式(Chain of Responsibility)

    概括 名称 Chain of Responsibility 结构 动机 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系.将这些对象连成一条链,并沿着这条链传递该请求,直到有一 ...

  7. 《JAVA设计模式系列》责任链模式

    文章目录 责任链模式 责任链模式优缺点 应用场景 责任链模式的结构 实现流程 责任链模式 责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链.这 ...

  8. 【手写源码-设计模式15】-责任链模式-基于人事请假单工作流场景

    1:主题拆解 ①基本介绍 ②人事请假单工作流模拟 ③责任链模式的优缺点 ④适用场景 ⑤应用实例 ⑥ASP.NET 管道模型 2:基本介绍 责任链模式很像异常的捕获和处理,当一个问题发生的时候,当前对象 ...

  9. [设计模式] javascript 之 责任链模式

    责任链模式:定义 责任链接模式又称职责链模式,是一种对象的行为模式:它是一种链式结构,每个节点都有可能两种操作,要么处理该请求停止该请求操作,要么把请求转发到下一个节点,让下一个节点来处理请求:该模式 ...

最新文章

  1. Docker学习(1)——几张图快速了解Docker
  2. 分析Android :java.lang.UnsatisfiedLinkError: dlopen failed * is 32-bit instead of 64-bit
  3. 计算机室活动实施方案,微机室活动计划
  4. matlab静态变量怎样分配内存,matlab中的静态变量
  5. HOG(方向梯度直方图)
  6. UVA - 12096:The SetStack Computer
  7. 微信开发者平台如何编写代码_编写超级清晰易读的代码的初级开发者指南
  8. nginx 代理多个服务器——多个server方式
  9. powerpc和arm_为什么我喜欢ARM和PowerPC
  10. 客户端和服务器实现全双工通信(基于线程)
  11. 数据结构与算法之PHP排序算法(桶排序)
  12. 京东聚合收银(会员码支付)接口封装C++
  13. python程序设计课程设计二级减速器_二级减速器的课程设计
  14. stc15f2k60s2单片机控制led流水灯
  15. Java操作excel自动生成水印背景
  16. 高通msm8953平台摄像头移植
  17. 零刻数据提供多地优质BGP双线接入服务
  18. tkinter浏览器组件
  19. redit mysql_从 Reddit 学到的经验,互联网营销
  20. 问题:我的xmindpro从桌面打开就弹窗发生错误

热门文章

  1. 关闭2345的热点资讯
  2. 文件共享同步5种方式-NFS、NAS、rsync、scp、ftp
  3. 天穗之咲稻姬游戏 附攻略
  4. 程序员如何追女孩(转帖)
  5. 电子科学和计算机哪个好,计算机科学与技术专业和电子科学与技术专业,哪个好些?...
  6. 用linux定时任务做项目对接,linux系统管理 计划任务
  7. 课程8 :PLC ‘不寻常指令‘详解:SCATTER--将数字转为单个位 .(工控PLC工程师入门必读,5天可上手调试)
  8. 计算机仿真cad答案,CAD与计算机仿真作业.doc
  9. 八大安防设备成高考防作弊神器
  10. Unity 3D 游戏通用系统设置页面,自定义按键设置,背景虚化,图像设置,亮度对比度饱和度音量调节,分辨率窗口化,帧率垂直同步,抗锯齿,阴影质量,纹理质量设置