retrofit2-kotlin-coroutines-adapter|retrofit2-kotlin-coroutines-adapter 超时引起的崩溃

最近项目中突然爆发了一波由网络超时造成的崩溃问题(之前也有过几次,但是没有引起足够的重视).花费了一天的时间终于解决了[开心]
事情是这样的: 我在项目中使用kotlin作为开发语言,同时也引入了coroutnies(协程),使用协程替代了线程池.想要在api层使用协程,于是Github一波决定引入 JakeWharton/retrofit2-kotlin-coroutines-adapter

  1. 一开始还觉得很诡异,因为我在网络请求的外围写了try cache 捕获异常然后交给个上层做处理,而且不是所有的网络超时异常都捕获不到
  2. 通过打印日志发现只有在页面销毁调的时候会引发异常
  3. 继续跟踪发现在页面销毁的时候调用coroutinesjob取消协程时网络请求并没用被取消. 上一段代码:
private class BodyCallAdapter( private val responseType: Type ) : CallAdapter> {override fun responseType() = responseTypeoverride fun adapt(call: Call): Deferred { val deferred = CompletableDeferred()deferred.invokeOnCompletion { if (deferred.isCancelled) { // 这里打印日志 call.cancel() } }call.enqueue(object : Callback { override fun onFailure(call: Call, t: Throwable) { // 这里打印日志 deferred.completeExceptionally(t) }override fun onResponse(call: Call, response: Response) { if (response.isSuccessful) { deferred.complete(response.body()!!) } else { deferred.completeExceptionally(HttpException(response)) } } })return deferred } }

发现cancle的日志在onFailuer后面,debug去看才发现原来deferred.isCancelled 返回true是因为deferred.completeExceptionally(t) 触发的. 如果是这种原因才触发取消网络请求那取消就没有意义了
下面这两个是这个库的issues:
[1] [Question] Coroutine cancellation is not handled, right? #7
[2] Is there any way to catch timeout exception using your coroutines?
想了半天也没办法把job或者coroutinesContext传给api返回的Deferred对象,而且suspend方法也不能获取 coroutinesContext对象所以最终只能在调用的时候把job
对象作为参数传到下层了...虽然不优雅但是能解决问题了
如果你也遇到了这样的问题希望我的解决方法能给你一个思路,如果你有更好的解决方法请你也告诉我一声 :D
写一下伪代码:
// api service fun api(): Deferred = CompletableDeferred()class Activity(): CoroutineScope { val job = Job() override val coroutineContext: CoroutineContext = Dispatchers.Default + jobfun runApi(){ launch { val deferred = api() job.invokeOnCompletion { if (job.isCancelled) // 在检测到job取消的时候取消网络请求 deferred.cancel() } try{ val result = deferred.await() //todo } catch (t: Throwable){ // exception control } } }fun destory(){ job.cancel() }}

emmm 还是打算插一句为什么要使用这个库: 【retrofit2-kotlin-coroutines-adapter|retrofit2-kotlin-coroutines-adapter 超时引起的崩溃】由于网络请求是超时操作,而安卓的页面[activity, fragment]什么时候销毁一般是由用户操作决定的.所以会有生命周期不一致的问题,网络请求又会持有页面的索引[内部类,会持有外部类的索引].所以在设计的时候一般会在页面销毁的时候取消网络请求,最开始使用的是RxJava + CompositeDisposable 在页面销毁的时候通过调用 CompositeDisposable 的 close 方法取消网络请求,之后使用LiveData + RxJava + AutoDispose 后来觉得既然引入了LiveData 在引入 RxJava有些多余了(因为大部分RxJava的使用场景都在网络请求上),同时也接触了 koltincoroutnies 找到了大神写的库竟然不用关心网络请求的取消[嗯,大神就是大神]于是就引入了...

    推荐阅读