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

最近项目中突然爆发了一波由网络超时造成的崩溃问题(之前也有过几次,但是没有引起足够的重视).花费了一天的时间终于解决了[开心]

事情是这样的:

我在项目中使用kotlin作为开发语言,同时也引入了coroutnies(协程),使用协程替代了线程池.想要在api层使用协程,于是Github一波决定引入 JakeWharton/retrofit2-kotlin-coroutines-adapter

  1. 一开始还觉得很诡异,因为我在网络请求的外围写了try cache 捕获异常然后交给个上层做处理,而且不是所有的网络超时异常都捕获不到
  2. 通过打印日志发现只有在页面销毁调的时候会引发异常
  3. 继续跟踪发现在页面销毁的时候调用coroutinesjob取消协程时网络请求并没用被取消. 上一段代码:

private class BodyCallAdapter<T>(
      private val responseType: Type
  ) : CallAdapter<T, Deferred<T>> {

    override fun responseType() = responseType

    override fun adapt(call: Call<T>): Deferred<T> {
      val deferred = CompletableDeferred<T>()

      deferred.invokeOnCompletion {
        if (deferred.isCancelled) {
          // 这里打印日志
          call.cancel()
        }
      }

      call.enqueue(object : Callback<T> {
        override fun onFailure(call: Call<T>, t: Throwable) {
          // 这里打印日志
          deferred.completeExceptionally(t)
        }

        override fun onResponse(call: Call<T>, response: Response<T>) {
          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<String> = CompletableDeferred()

class Activity(): CoroutineScope {
  val job = Job()
  override val coroutineContext: CoroutineContext = Dispatchers.Default + job

  fun 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 还是打算插一句为什么要使用这个库:

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 164,670评论 24 698
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 10,791评论 2 54
  • 计算机很擅长多任务操作。为了编写出好的软件我们需要对多任务操作和异步有个很好的了解。在Android上面这些包括了...
    宛丘之上兮阅读 5,393评论 2 1
  • 原文链接:https://github.com/EasyKotlin 在常用的并发模型中,多进程、多线程、分布式是...
    JackChen1024阅读 9,976评论 3 23
  • 微演讲打卡练习第115天,每天一分钟,成为最美好的自己 公交车是普通百姓出行依赖的交通工具,也是一个城市宣传...
    古月无语阅读 124评论 0 0
  • 如果一个人让你产生了愤怒的情绪,多半是他做了你想做而不敢做的事,于是你对他的妒忌转化成了愤怒。 你不愿意承认你这一...
    cd8a953f221a阅读 159评论 0 1
  • 失业的小姐姐 老板赌博输个光 员工个个心发慌 昨天还是工作狂 今天饭碗没地放 (挣钱难,难于上青天,饭碗太难端了,...
    旖旎i阅读 244评论 2 11