文章

Android 源码中的责任链模式

Android 源码中的责任链模式

Android 源码中的责任链模式

1、View 的 onTouchEvent()

从子 View 开始,是否消费,如果消费了,那么事件就交给该子 View 处理;如果没有消费,继续传递给其 Parent,看是否消费;这样向上传递,只要该链上有一个消费了该事件,那么事件就交给它处理。

2、OkHttp 的 Interceptor

OkHttp 中的拦截器是一条链,OkHttp 中的核心功能,都是由 Interceptor 实现,并串联在这条链上,只要其中有一个 Interceptor 处理了 Response,那么就不再往下传递了;否则一直往下传递给下一个 Interceptor。

1
2
3
4
5
6
7
8
9
10
// RealCall#execute()
@Override public Response execute() throws IOException {
    // …
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      // …
      return result;
    } 
    // …

然后走入到了 getResponseWithInterceptorChain()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
    
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());
    return chain.proceed(originalRequest);
}

Interceptor 接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface Interceptor {
  Response intercept(Chain chain) throws IOException;
  interface Chain {
    Request request();
    Response proceed(Request request) throws IOException;
    /**
     * Returns the connection the request will be executed on. This is only available in the chains
     * of network interceptors; for application interceptors this is always null.
     */
    @Nullable Connection connection();
    Call call();
    int connectTimeoutMillis();
    Chain withConnectTimeout(int timeout, TimeUnit unit);
    int readTimeoutMillis();
    Chain withReadTimeout(int timeout, TimeUnit unit);
    int writeTimeoutMillis();
    Chain withWriteTimeout(int timeout, TimeUnit unit);
  }
}

RealInterceptorChain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    // interceptor走完
    if (index >= interceptors.size()) throw new AssertionError();
    // …
    // Call the next interceptor in the chain. // 构造下一个chain,并带上interceptors,和index+1索引
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    // 获取到当前的Interceptor
    Interceptor interceptor = interceptors.get(index);
    // 调用interceptor,并传入下一个chain,如果下一个interceptor不拦截,就调用next.proceed()就可以交给下一个Interceptor处理了
    Response response = interceptor.intercept(next);
    // …
    return response;
}

应用

网络请求错误码的全局处理

ARouter 降级服务错误处理

3、ARouter 中的拦截器 增加了线程池

类、接口的定义

InterceptorService 提供拦截的服务,可有可无

ARouter 中 InterceptorService 拦截器的服务,进行路由的时候,会查找是否定义了 InterceptorService,如果定义了 InterceptorService,会调用 doInterceptions 来进行拦截

1
2
3
4
5
6
public interface InterceptorService extends IProvider {
    /**
     * Do interceptions
     */
    void doInterceptions(Postcard postcard, InterceptorCallback callback);
}

IInterceptor 所有拦截器需要实现的接口 类似 OKHttp 的 Interceptor

1
2
3
4
5
6
7
8
9
10
public interface IInterceptor {

    /**
     * The operation of this interceptor.
     *
     * @param postcard meta
     * @param callback cb
     */
    void process(Postcard postcard, InterceptorCallback callback);
}

InterceptorCallback 拦截器的 callback,类似 OkHttp 的 Interceptor.Chain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface InterceptorCallback {
    /**
     * Continue process
     *
     * @param postcard route meta
     */
    void onContinue(Postcard postcard);
    /**
     * Interrupt process, pipeline will be destroy when this method called.
     *
     * @param exception Reson of interrupt.
     */
    void onInterrupt(Throwable exception);
}

Postcard 明信片 类似 OkHttp 的 Request/Response,携带参数和返回结果

xin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public final class Postcard extends RouteMeta {
    // … 其他参数
    private Object tag;             // A tag prepare for some thing wrong.
    private int timeout = 300;      // Navigation timeout, TimeUnit.Second
    public Object getTag() {
        return tag;
    }
    public Postcard setTag(Object tag) {
        this.tag = tag;
        return this;
    }
    public int getTimeout() {
        return timeout;
    }
    /**
     * Set timeout of navigation this time.
     * @param timeout timeout
     * @return this
     */
    public Postcard setTimeout(int timeout) {
        this.timeout = timeout;
        return this;
    }
}

具体源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// _ARouter.java
private static InterceptorService interceptorService;
static void afterInit() {
    // Trigger interceptor init, use byName.
    interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
}
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    // …
    interceptorService.doInterceptions(postcard, new InterceptorCallback() {
            /**
             * Continue process
             *
             * @param postcard route meta
             */
            @Override
            public void onContinue(Postcard postcard) {
                _navigation(context, postcard, requestCode, callback);
            }

            /**
             * Interrupt process, pipeline will be destory when this method called.
             *
             * @param exception Reson of interrupt.
             */
            @Override
            public void onInterrupt(Throwable exception) {
                if (null != callback) {
                    callback.onInterrupt(postcard);
                }

                logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
            }
        });
    // …
}

接着看 InterceptorServiceImpl:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
public class InterceptorServiceImpl implements InterceptorService {
    private static boolean interceptorHasInit;
    private static final Object interceptorInitLock = new Object();

    @Override
    public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
        if (null != Warehouse.interceptors && Warehouse.interceptors.size() > 0) {

            checkInterceptorsInitStatus();

            if (!interceptorHasInit) {
                callback.onInterrupt(new HandlerException("Interceptors initialization takes too much time."));
                return;
            }

            LogisticsCenter.executor.execute(new Runnable() {
                @Override
                public void run() {
                    CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
                    try {
                        _execute(0, interceptorCounter, postcard);
                        interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
                        if (interceptorCounter.getCount() > 0) {    // Cancel the navigation this time, if it hasn't return anythings.
                            callback.onInterrupt(new HandlerException("The interceptor processing timed out."));
                        } else if (null != postcard.getTag()) {    // Maybe some exception in the tag.
                            callback.onInterrupt(new HandlerException(postcard.getTag().toString()));
                        } else {
                            callback.onContinue(postcard);
                        }
                    } catch (Exception e) {
                        callback.onInterrupt(e);
                    }
                }
            });
        } else {
            callback.onContinue(postcard);
        }
    }

    /**
     * Excute interceptor
     *
     * @param index    current interceptor index
     * @param counter  interceptor counter
     * @param postcard routeMeta
     */
    private static void _execute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
        if (index < Warehouse.interceptors.size()) {
            IInterceptor iInterceptor = Warehouse.interceptors.get(index);
            iInterceptor.process(postcard, new InterceptorCallback() {
                @Override
                public void onContinue(Postcard postcard) {
                    // Last interceptor excute over with no exception.
                    counter.countDown();
                    _execute(index + 1, counter, postcard);  // When counter is down, it will be execute continue ,but index bigger than interceptors size, then U know.
                }

                @Override
                public void onInterrupt(Throwable exception) {
                    // Last interceptor excute over with fatal exception.

                    postcard.setTag(null == exception ? new HandlerException("No message.") : exception.getMessage());    // save the exception message for backup.
                    counter.cancel();
                    // Be attention, maybe the thread in callback has been changed,
                    // then the catch block(L207) will be invalid.
                    // The worst is the thread changed to main thread, then the app will be crash, if you throw this exception!
//                    if (!Looper.getMainLooper().equals(Looper.myLooper())) {    // You shouldn't throw the exception if the thread is main thread.
//                        throw new HandlerException(exception.getMessage());
//                    }
                }
            });
        }
    }

    @Override
    public void init(final Context context) {
        LogisticsCenter.executor.execute(new Runnable() {
            @Override
            public void run() {
                if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
                    for (Map.Entry<Integer, Class<? extends IInterceptor>> entry : Warehouse.interceptorsIndex.entrySet()) {
                        Class<? extends IInterceptor> interceptorClass = entry.getValue();
                        try {
                            IInterceptor iInterceptor = interceptorClass.getConstructor().newInstance();
                            iInterceptor.init(context);
                            Warehouse.interceptors.add(iInterceptor);
                        } catch (Exception ex) {
                            throw new HandlerException(TAG + "ARouter init interceptor error! name = [" + interceptorClass.getName() + "], reason = [" + ex.getMessage() + "]");
                        }
                    }

                    interceptorHasInit = true;

                    logger.info(TAG, "ARouter interceptors init over.");

                    synchronized (interceptorInitLock) {
                        interceptorInitLock.notifyAll();
                    }
                }
            }
        });
    }

    private static void checkInterceptorsInitStatus() {
        synchronized (interceptorInitLock) {
            while (!interceptorHasInit) {
                try {
                    interceptorInitLock.wait(10 * 1000);
                } catch (InterruptedException e) {
                    throw new HandlerException(TAG + "Interceptor init cost too much time error! reason = [" + e.getMessage() + "]");
                }
            }
        }
    }
}

责任链在 Android 中的应用

多媒体文件批量后台上传功能 remix

多媒体文件在后台上传,需要获取七牛的 token,压缩文件,上传自己的后台服务器去,这些拦截器都需要运行在子线程中

可以增加一个阻塞队列,后台单线程一个个上传文件

IM/WebSocket 的消息发送接收功能

网络框架 错误码全局处理

Android ARouter 路由框架全局降级服务处理

公共部分

定义 Processor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
interface IARouterDegradeProcessor {

    /**
     * 是否要处理该path
     * @return true处理;false不处理,调用下一个processor处理
     */
    fun interesting(postcard: Postcard): Boolean

    /**
     * interesting()返回true后调用该方法处理
     */
    @Throws(Exception::class)
    fun onLost(chain: Chain)

    /**
     * 输入参数都一个方法,返回值为输入参数
     */
    interface Chain {

        /**
         * 输入参数Postcard
         */
        fun postcard(): Postcard

        /**
         * 输入参数Context,如果是通过h5 scheme url调用的Context为null
         */
        fun context(): Context?

        /**
         * 如要让下一个Processor处理,调用该方法
         */
        @Throws(Exception::class)
        fun onContinue(postcard: Postcard)
    }
}

定义 ProcessorChain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
internal class ARouterDegradeProcessorChain(
        private val processors: List<IARouterDegradeProcessor>?,
        private val index: Int,
        private val context: Context?,
        private val postcard: Postcard) : IARouterDegradeProcessor.Chain {

    override fun postcard(): Postcard {
        return postcard
    }

    override fun context(): Context? {
        return context
    }

    override fun onContinue(postcard: Postcard) {
        requireNotNull(processors)
        if (index >= processors.size) {
            throw IllegalArgumentException("越界了:index:" + index + ",size:" + processors.size)
        }
        val next = ARouterDegradeProcessorChain(processors, index + 1, context, postcard)
        val processor = processors[index]
        if (processor.interesting(postcard)) {
            processor.onLost(next)
        } else {
            next.onContinue(postcard)
        }
    }
}

业务部分

调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Route(path = ARouterConstants.Service.ROUTER_SERVICE_DEGRADE)
class ARouterDegradeService : DegradeService {

    override fun init(context: Context?) {
        LogUtils.w(ARouterConstants.TAG, "${this.javaClass.simpleName} init()")
    }

    override fun onLost(context: Context?, postcard: Postcard?) {
        ARouterDegradeDelegate.onLost(context, postcard)
    }

}

object ARouterDegradeDelegate {
    private val processors: List<IARouterDegradeProcessor> by lazy {
        listOf(
                ARouterDegradeRelationProcessor(),
                ARouterDegradeReturnSmallRoomProcessor(),
                ARouterDegradeOpenLuckyDrawProcessor(),
                ARouterDegradeFindTopHotRoomProcessor(),
                ARouterDegradeOpenGiftPanelProcessor(),
                ARouterDegradeToastProcessor() // 这个一定要放最后
        )
    }

    fun onLost(context: Context?, postcard: Postcard?) {
        if (postcard != null) {
            val chain = ARouterDegradeProcessorChain(processors, 0, context, postcard)
            chain.onContinue(postcard)
        }
    }
}

具体的 Processor1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
 * 本地查找缓存的最近房间,打开礼物面板,并选中礼物
 */
class ARouterDegradeOpenGiftPanelProcessor : IARouterDegradeProcessor {

    @Autowired
    @JvmField
    var roomProvider: IRoomProvider? = null

    init {
        ARouter.getInstance().inject(this)
    }

    companion object {
        private val HANDLE_PATHS = arrayOf(Constants.DeepLink.OPEN_LATEST_ROOM_LUCKY_GIFT)
    }

    override fun interesting(postcard: Postcard): Boolean {
        val path = postcard.getSafePath()
        if (path !in HANDLE_PATHS) {
            return false
        }
        LogUtils.w(ARouterConstants.TAG, "${anchor("interesting")} 本地查找缓存的最近房间,打开礼物面板,并选中礼物,$postcard")
        return true
    }

    override fun onLost(chain: IARouterDegradeProcessor.Chain) {
        val postcard = chain.postcard()
        when (postcard.path ?: "") {
            Constants.DeepLink.OPEN_LATEST_ROOM_LUCKY_GIFT -> { // 本地查找缓存的最近房间,打开幸运抽奖
                val giftId = postcard.uri.getQueryParameter("gift_id")?.toLongOrNull()
                        ?: Constants.INDEX_DEFAULT
                val from = postcard.uri.getQueryParameter(Constants.IntentKey.FROM)
                val giftPanelDeepLink = roomProvider?.getGiftPanelDeepLink(giftId, from = from)
                showShortDebug("本地查找缓存的最近房间,打开礼物面板,并选中指定的幸运礼物giftId=$giftId, deeplink=$giftPanelDeepLink")
                giftPanelDeepLink?.let { ARouter.getInstance().build(it).navigation() }
            }
        }
    }
}

具体的 Processor2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * 这个放在最后面,处理deeplink找不到,toast提示文案
 */
class ARouterDegradeToastProcessor : IARouterDegradeProcessor {

    override fun interesting(postcard: Postcard): Boolean {
        LogUtils.e(ARouterConstants.TAG, "${anchor("interesting")} 处理deeplink找不到,toast提示文案 $postcard")
        return true
    }

    override fun onLost(chain: IARouterDegradeProcessor.Chain) {
        ResUtils.getStr(R.string.common_deeplink_not_found_toast_text).showLongSafe()
    }
}

Android 弹窗优先级

TODO:

本文由作者按照 CC BY 4.0 进行授权