相信大家对Okhttp
的责任链模式应该已经很熟悉了,今天给大家普及另外一种有意思的玩法。
先把场景给大家描述下,省的各位大佬说我过度设计代码。我就以我写的谷歌支付作为例子吧。谷歌支付对大部分调用场景都是采用async
异步接口回调的方式进行封装的。
这个时候你们要说了,这不就是恶心一点点而已吗,我上我也行啊。一般会有这么几种大佬处理方式。
while true
去等待取值,就可以把所有的异步都转化成同步。
RxJava
或者协程大佬,这不就是个简单的链式操作,每个链每个function只负责做他们相关的操作,就是异步转化成Rxjava可能恶心了一点点,但是问题并不大。
抛出一个问题,RxJava
是如何实现顺序链式执行的? 有没有觉得和OkHttp
的责任链有点相似呢? 马萨卡!
一个例子理解Rxjava的事件流转换原理 , 有兴趣的同学可以看下这篇文章的分析。
我们这次的解决方案就是采用责任链的方式对这有执行顺序的代码进行一次改造。这里我首先要感谢下wmrouter
的作者,某微软大佬,大佬的代码给了我很好的启发,也就是从wmrouter
中我基本掌握了这种好玩的设计模式。
Demo的话大家可以参考下我以前写的路由项目,也是基于这种异步责任链开发的。
责任链模式是一种设计模式。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。(参考百度百科)
我偷了两张网图介绍okhttp
的责任链的,相对来说还是比较形象的。
整体拦截器的流程如下图所示。
简单的说,责任链就是按照特定的顺序向下执行的一个过程。大家了解最多的应该是okhttp
的责任链。我们先从这个讲起,比较两部分差异。
fun interface Interceptor {
@Throws(IOException::class)
fun intercept(chain: Chain): Response
interface Chain {
fun request(): Request
@Throws(IOException::class)
fun proceed(request: Request): Response
}
}
OkHttp
的责任链是由一个Interceptor
和一个Chain
的接口开始的。然后通过这个Chain
的proceed
方法执行下一个拦截器内的代码。由于proceed
方法因为有返回值,所有必须确保下一个拦截器中有返回值才行。这就是说proceed
方法必须要是一个同步的方法(当场有返回值的方法)。
这个时候你说了,那么既然要求了下一个责任链必然要有返回,那么还咋实现异步责任链啊?这部分还是用之前路由项目来介绍吧,毕竟谷歌支付是公司代码(我被开除了你们养我???), 不是特别方便开源。
interface Interceptor {
@Throws(RouteNotFoundException::class)
fun intercept(chain: Chain)
interface Chain {
val url: KRequest
val context: Context
@Throws(RouteNotFoundException::class)
fun proceed()
}
}
对比观察下,其实两个责任链最大的差别就是intercept
方法有没有具体的返回值。而proceed
则只负责告诉当前拦截器是否向下执行了。简单的说两种责任链模式最大的区别就在这个地方了。
class LogInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain) {
Log.i("LogInterceptor", "LogInterceptor")
chain.proceed()
}
}
正常情况下,我们的拦截器逻辑会和上面的代码一样,但是由于其实当前方法是没有返回值的,也就是说我们的一个异步任务只要持有了chain的实例,那么当我们的异步有结果回调了之后,再执行我们的proceed
方法继续执行下一个链式调用是不是就可以了。下面我们上一个异步的实现看看。
class DelayInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain) {
GlobalScope.launch {
// 建立谷歌链接
delay(5000)
withContext(Dispatchers.Main) {
chain.proceed()
}
}
}
}
我在这个拦截器中使用了协程的延迟,delay代表使用了一个前文中提建立谷歌链接,然后我们调度到了主线程中之后调用责任链的下一个。因为fun
是没有返回值的,而我们持有了chain
的引用,所以我们可以在任意的异步中调用责任链的下一个,这样就完成了一个可异步的责任链封装了。
以我的路由的责任链为例子,其实proceed
方法是非常简单粗暴的,通过index+1
的方式调用下下一个拦截器,然后执行他的代码就行了,如果list为空则返回。
class RealInterceptorChain internal constructor(private val interceptors: List<Interceptor>, override val url: KRequest,
override val hostParams: Map<String, HostParams>?,
private val index: Int, override
val context: Context) : Interceptor.Chain {
@Throws(RouteNotFoundException::class)
override fun proceed() {
proceed(url)
}
@Throws(RouteNotFoundException::class)
fun proceed(request: KRequest) {
if (index >= interceptors.size) {
return
}
val next = RealInterceptorChain(interceptors, request, hostParams,
index + 1, context)
val interceptor = interceptors[index]
interceptor.intercept(next)
}
}
另外一个则是需要有一个地方专门去把整个链式调用的拦截器放在一个列表内,对外使用的则是这个Call
类。
class RealCall(private val hostMap: Map<String, HostParams>, private val config: RouteConfiguration) {
private val cachedRoutes: HashMap<String, RouterParams> = hashMapOf()
@Throws(RouteNotFoundException::class)
fun open(request: KRequest, context: Context) {
getParamsWithInterceptorChain(request, context)
}
@Throws(RouteNotFoundException::class)
private fun getParamsWithInterceptorChain(request: KRequest, context: Context) {
val interceptors: MutableList<Interceptor> = ArrayList()
if (config.lazyInitializer) {
interceptors.add(LazyInitInterceptor())
}
interceptors.addAll(config.interceptors)
interceptors.add(CacheInterceptor(cachedRoutes))
interceptors.add(RouterInterceptor(cachedRoutes))
val chain: Interceptor.Chain = RealInterceptorChain(interceptors, request, hostMap, 0, context)
chain.proceed()
}
}
RealCall
通过这样一个List以及对外暴露的open
方法,我们就能确保这个责任链的顺序向下执行了。而且由于上面介绍到的Chain
是通过proceed
方法来通知下一个链被执行的。所有整个链就会被串联起来了。
在谷歌Pay中,因为他们其实并不算是个拦截器,而是一个处理器,所以这部分被我定义成了Handler
。每个Handler
负责处理自己当前所需要的那部分逻辑,当他完成之后则交给下一个Handler
继续执行。
这部分可以用伪代码给大家介绍下。
连接Handler{
async{
if(连接成功){
下一个Handler
}else{
重连试试看 3次则都失败返回
}
}
}
谷歌掉单Handler{
async{
if(没有掉单){
下一个Handler
}else{
终止
}
}
}
其他handler
...
我写了两个伪代码,代表了这部分Handler
的处理逻辑,其他的都按照类似的去处理就行了。这样写的好处就是每一个由于每一个Handler
只负责自己的逻辑,这样后续在升级维护过程中就会相对来说比较简单,而且中间可以插入一些别的处理器,或者调整顺序方便代码复用等。
而且也可以避免出现回调地狱的情况,如果代码只有成功回调就算了,万一还有异常则就是一坨稀泥了。说句最难听的万一我滚蛋了,后面的人只要按照责任链顺序查看代码逻辑就可以简单的了解其实现了。
那么这种写法还有什么不足之处吗?
因为全部都是异步操作,而异步则意味着要持有引用,所以没有和LifeCycle
绑定的情况下容易出现内存泄露的状况。举个例子,页面销毁了然后回调了结果,之后因为持有了回调,就会导致内存泄露的情况发生了。
这个时候我们提供另外一个终止的指令可以帮助我们优化泄露的情况。当当前责任链终止,则清空所有的Handler
引用。
interface Chain {
val context: Context
val lifecycleOwner: LifecycleOwner?
fun proceed()
fun onComplete()
}
class RealCall:Handler.Chain,LifecycleObserver{
override fun onComplete() {
// 清空你的责任链列表
handlers.clear()
}
}
还有就是因为全部都是异步的,所以会相对增加代码理解的难度,但是我个人觉得整体上来说还是要优于异步嵌套的。
同时由于方法并没有返回值,对于参数传递其实就并不是特别友好了,也只能通过异步的形式将结果传输给到使用方。虽然避免了大量的回调嵌套,但是还是要给使用方一个回调给予最后的处理结果的。
如果应用于类似路由这种场景的话,其实灵活性我感觉是远远比有返回值的拦截器要好很多的。
如果这部分代码能让我使用协程去写,我觉得我完全信手拈来,没有任何压力。但是因为是一个sdk方,所以在输出代码的情况下要考虑到使用方有没有类库,而且最小依赖粒度的愿意,所以只能采取这种设计模式去让代码的依赖降到更少,而且让代码后面的可维护性更高一点。
这部分代码我也不是一开始就想到的,我也是先手写撸了一遍,然后想了想,如果过一阵子让我重新来接这部分代码,我估计会想死。所以就重新设计了下这部分代码的实现,多思考活学活用,不需要生搬硬套。