简介
责任链模式,是行为型设计模式之一。我们将多个节点首尾相连所构成的模型称之为“链”,对于链式结构,每个节点都可以被拆开再链接,因此,链式结构就会有很大的灵活性。如果我们将每个链上的节点看作为一个对象,每一个对象有不同的处理逻辑,将一个请求从链子的第一个节点出发,沿着路径依次传递给每一个节点对象,直到有某个对象对这个请求进行了处理为止,这就是责任链模式。
定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递这个请求,直到有对象处理为止。
画个图吧
这就是责任链的UML图,其中
- AbstractHandler:抽象处理者,其中包含有next,指向下一个处理者
- Handler1、Handler2:都是AbstractHandler的子类,是具体请求者
- AbstractRequest:抽象请求者
- Request1、Request2:抽象请求者的子类,具体请求者
简单实现
- 定义抽象请求
public abstract class AbstractRequest {
private String name;
public AbstractRequest(String name){
this.name = name;
}
public Object getName(){
return name;
}
/**
* 请求的level
* @return
*/
public abstract int getRequestLevel();
- 定义抽象处理者
public abstract class AbstractHandler {
//下一个处理者的引用
protected AbstractHandler next;
public abstract int getLevel();
public abstract void handle(AbstractRequest request);
public final void handleRequest(AbstractRequest request){
//请求者和处理者的level相同时处理
if(getLevel() == request.getRequestLevel()){
handle(request);
}else{
if(next!=null){
//调用下一个节点的handleRequest方法
next.handleRequest(request);
}else{
System.out.println("no one can handle it!!");
}
}
}
}
- 具体请求者 这里可以定义多个,篇幅原因只展示其中一个请求者
public class MyRequest extends AbstractRequest{
public MyRequest(String name) {
super(name);
}
@Override
public int getRequestLevel() {
return 1;
}
}
- 具体处理者 同上,这里可以定义多个,篇幅原因只展示其中一个处理者
public class MyHandler1 extends AbstractHandler{
@Override
public int getLevel() {
return 1;
}
@Override
public void handle(AbstractRequest request) {
System.out.println("1号处理者处理"+request.getName());
}
}
- 测试
public class Test {
public static void main(String[] args) {
AbstractHandler one = new MyHandler1();
AbstractHandler two = new MyHandler2();
one.next = two;
AbstractRequest request1 = new MyRequest("request1");
AbstractRequest request2 = new MyRequest2("request2");
one.handleRequest(request1);
one.handleRequest(request2);
}
}
- 结果
1号处理者处理request1
二号处理者处理request2
可以看到,都是从one开始的,但是已经有了链式结构的调用过程了。
okhttp的拦截器责任链
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
- RetryAndFollowInterceptor:重定向拦截器,处理重试的拦截器,会处理一些异常,只要不是致命的异常就会重新发起一次请求。
- BridgeInterceptor:桥接拦截器,设置一些通用的header头信息,Cookie、Connection、Content-type等,对返回信息做一些处理,数据的压缩等
- CacheInterceptor:缓存拦截器,在缓存可用的情况下,读取本地的缓存,如果没有则直接从服务器获取,如果有,则判断是否有缓存策略,之后判断是否过期,没有过期则直接从缓存中获取,如果过期了,就添加一些头信息,服务器会判断是否有修改,然后可能会返回304,代表仍然可以从缓存中获取,否则就从服务器获取。
- ConnectInterceptor:连接拦截器,调用findHealthyConnection()方法找到一个连接,并判断是否健康,没有则创建socket连接并缓存。OkHttp是基于原生的socket+okio实现的,
- CallServerInterceptor:请求拦截器,给服务端写数据和读取数据
在上边的代码中通过调用 RealInterceptorChain 的 proceed 方法来完成责任链条的运转,我们
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
那就来看一下proceed这个方法的实现:
RealInterceptorChain.java
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
...
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
...
return response;
}
注意看,这个方法中有个index的变量,同时在这里又新建了 RealInterceptorChain 自己,不过传的参数变为 index+1,这样获取到的interceptor就变为 拦截器链表中的下一个拦截器了,然后将request传递给下一个拦截器,让其进行相应的处理。
view事件分发责任链
view的层级
当我们写Activity的时候,会调用setContentView方法来加载布局,那就先来看看setContentView方法的实现吧
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
这里调用了getWindow().setContentView(layoutResID), getWindow()方法又做了什么呢?
public Window getWindow() {
return mWindow;
}
返回了mWindow,这个mWindow是什么?我们在attach()方法中找到了答案
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback) {
attachBaseContext(context);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
...
}
原来这个mWindow就是PhoneWindow,PhoneWindow是Window的唯一实现类,所以我们看一下PhoneWindow中setContentView()是如何实现的吧
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
...
}
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
} else {
mDecor.setWindow(this);
}
...
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
....
}
}
protected DecorView generateDecor(int featureId) {
...
return new DecorView(context, featureId, this, getAttributes());
}
可以看到这里最终创建了DecorView,这个DecorView就是Activity中的根view,接着查看DecorView的源码,DecorView继承自FrameLayout。
会到installDecor这个方法中,mDecor初始化之后,执行 generateLayout(mDecor)这个方法,我们看一下它的实现
protected ViewGroup generateLayout(DecorView decor) {
...
int layoutResource;
...
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
// If no other features and not embedded, only need a title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
layoutResource = R.layout.screen_title;
}
...
return contentParent;
}
这个方法又臭又长,这里截取一部分,其主要内容就是根据不同的情况加载不同的布局给layoutResource。 可以看到 layoutResource = R.layout.screen_title 这句代码,
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:fitsSystemWindows="true">
<!-- Popout bar for action modes -->
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize"
style="?android:attr/windowTitleBackgroundStyle">
<TextView android:id="@android:id/title"
style="?android:attr/windowTitleStyle"
android:background="@null"
android:fadingEdge="horizontal"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<FrameLayout android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
这个xml文件如上所示, ViewStub是用来显示Actionbar的,下边的两个FrameLayout,一个是title用来显示标题,一个是content用来显示内容。
看到这里就应该大致了解了Activity的层级。一个Activity包含一个Window对象,这个对象就是PhoneWindow,PhoneWindow将DecorView作为整个应用窗口的跟view,而这个DecorView又将屏幕分为两个区域:TItleView和ContentView,我们平时所编写的布局就是展示在这个ContentView中。
如图所示:
事件分发
当我们点击屏幕时,就产生了点击事件,这个事件被封装为一个类:MotionEvent。当MotionEvent产生之后,系统就会将这个MotionEvent传递给View的层级,最终将事件传递给一个具体的view,这个传递的过程就是事件的分发。这通常涉及到三个比较关键的方法:
- dispatchTouchEvent(MotionEvent ev):进行事件分发,如果事件传递到当前view,那么这个方法一定会调用,返回的结果受当前view的onTouchEvent和下级view的dispatchTouchEvent方法的影响,表示是否消费这个事件。
- onInterceptTouchEvent(MotionEvent ev):进行事件的拦截,在dispatchTouchEvent方法中调用,View中是没有这个方法的。如果当前的ViewGroup拦截了此事件,那么在同一个事件序列中,此方法不会再次被调用,返回结果表示是否拦截当前事件。
- onTouchEvent(MotionEvent ev):用来处理事件,在dispatchTouchEvent方法中调用。返回结果表示是否消耗当前事件,如果不消耗,则在此事件序列中,当前view无法再次接收到事件。
ViewGroup
当点击事件发生后,事件首先会传递给当前的Activity,这会调用Activity的dispatchTouchEvent方法,Activity会将具体的事件交给PhoneWindow来完成,PhoneWindow又会将事件的处理工作交给DecorView,之后DecorView就会交给根ViewGroup。所以我们直接从ViewGroup的dispatchTouchEvent方法开始分析。由于方法比较长,所以分段进行分析
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.
if (actionMasked == MotionEvent.ACTION_DOWN) {
//重置状态
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
final boolean intercepted;
//当事件类型为ACTION_DOWN或者mFirstTouchTarget!=null时,会判断是否要拦截事件
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//判断disallowIntercept标记位,如果为true,则不拦截
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;
}
...
}
总结一下:首先判断是否为down事件,如果是,则进行初始化,并将mFirstTouchTarget的值置空,以为一个完整的事件序列是从down开始的,
ViewGroup会在一下两种情况下判断是否拦截事件actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null
。首先ACTION_DOWN事件是每次事件序列的起点,针对DOWN事件都会进行状态的重置。mFirstTouchTarget是什么呢?当事件由view的子元素成功处理时,mFirstTouchTarget就会被赋值并指向子元素,也就是说ViewGroup不拦截事件并交由子元素处理时,mFirstTouchTarget!=null,如果ViewGroup拦截了事件,那么mFirstTouchTarget就会为null,当MOVE事件或者UP事件到来时,由于mFirstTouchTarget为null,导致ViewGroup的onInterceptTouchEvent就不会被调用,并且这一序列的事件都交给它进行处理。
private static final class TouchTarget {
public View child;
public int pointerIdBits;
public TouchTarget next;
private TouchTarget() {
}
}
可以看到TouchTarget是一个链表结构,通过addTouchTarget方法对其进行赋值操作。
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
而这个链表在没有多点触控的情况下,会退化成单个TouchTarget对象,多点触控下,目标相同同样为单个TouchTarget对象,只是保存了多个pointerId信息,当目标不同时,才是链表结构。
回到之前的判断中,看到FLAG_DISALLOW_INTERCEPT
这个标记位,这个标记位是通过requestDisallowInterceptTouchEvent
方法进行设置的,一般在字view中进行调用,一旦进行设置后,ViewGroup将无法拦截除了DOWN事件之外的其他事件。这也是处理滑动冲突中常用的方法之一。
通过以上的代码可以判断:当ViewGroup决定拦截事件时,后续的点击事件将会默认的交给它处理并且不再调用它的onInterceptTouchEvent方法。
当ViewGroup不拦截事件时,事件将会向下分发交给其子view进行处理,代码如下:
public boolean dispatchTouchEvent(MotionEvent ev) {
...
boolean alreadyDispatchedToNewTouchTarget = false;
//不拦截事件进入以下判断
if (!canceled && !intercepted) {
...
//不拦截时,事件会向下分发交由它的子view进行处理
final View[] children = mChildren;
//遍历所有子元素
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;
}
//判断触摸点位置是否在子view的点击范围内或者是否在播放动画
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
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;
1. break;
}
resetCancelNextUpFlag(child);
//将事件传递给子元素处理,dispatchTransformedTouchEvent实际上就是调用子元素的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 < 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;
}
...
}
首先遍历ViewGroup的所有子元素,然后判断子元素是否可以接收点击事件:是否在播放动画,坐标是否落在子元素的区域内。如果满足这两个条件,那么事件就会传递给他处理。dispatchTransformedTouchEvent方法实际上就是调用子元素的dispatchTouchEvent方法。
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
...
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
...
}
如果子元素的dispatchTouchEvent返回true, mFirstTouchTarget就会被赋值,
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
同时跳出循环,终止了对子元素的遍历,如果子元素的dispatchTouchEvent返回false,ViewGroup就会把事件分发给下一个子元素。
如果遍历所有的子元素后事件都没有被合适的处理,有两种情况:第一种,ViewGroup没有子元素,第二种就是其子元素的dispatchTouchEvent返回了false。这两种情况ViewGroup都会自己处理事件
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
这次调用dispatchTransformedTouchEvent方法时,第三个参数child为null,在dispatchTransformedTouchEvent内部会直接调用super.dispatchTouchEvent(event),这里就转移到View的dispatchTouchEvent方法,点击事件就交个view处理了。
View
首先看View的dispatchTouchEvent方法
public boolean dispatchTouchEvent(MotionEvent event) {
...
boolean result = false;
...
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//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;
}
}
...
return result;
}
View是一个单独的元素,没有子元素,因此无法向下传递事件,只有自己处理事件。首先会判断有没有设置onTouchListener,如果OnTouchListener中的onTouch方法返回true,那么onTouchEvent方法就不会被调用。
再来看一下onTouchEvent方法
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
...
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
...
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
...
}
mIgnoreNextUpEvent = false;
break;
...
}
return true;
}
return false;
}
只要View的CLICKABLE和LONG_CLICKABLE有一个为true,那么它就会消耗这个事件,onTouchEvent方法就会返回true,当ACTION_UP事件发生时,会触发performClick方法,如果设置了OnClickListener,那么这个方法就会调用OnClick方法。
事件分发小结
到这里ViewGroup和View的时间分发就说完了,ViewGroup事件传递的递归调用就类似于一条责任链,一旦找到责任者,那么将由责任者持有并消费这个事件。否则将事件继续传递给其他责任者。
总结
责任链模式让编程更具有灵活性,我们可以看到不仅在Android的源码中有使用到责任链,在很多的框架中也会使用,实现这种链表的结构可以有多种方法,可以直接将处理者扔到链表中,也可以使用递归的方式进行事件的传递,面对不同的场景,灵活的运用,才能发挥其强大的威力。