Kotlin协程 ----- suspendCoroutine和suspendCancellableCoroutine的使用

Android_Banner.jpg

简介

  • suspendCoroutine 的使用
  • suspendCancellableCoroutine的使用
  • Retrofit是如何支持协程的

suspendCoroutine 的使用

这里我们将使用suspendCoroutine将单一方法的接口方法改造成具有返回值的方法

单一方法的回调

声明一个单一方法的接口

/**
 * @author : zhangqi
 * @time : 6/22/21
 * desc : 单一方法接口
 */
interface SingleMethodCallback {

    fun onCallBack(value: String)
}

接着模拟一个耗时的操作,当操作完毕我们把结果回调给实现类

  /**
   * 模拟一个耗时操作
   */
private fun runTask(callback: SingleMethodCallback) {
        thread {
            Thread.sleep(1000)
            callback.onCallBack("result")
        }
    }

最后我们调用runTask方法,传入SingleMethodCallback的实现

private fun runTaskDefault() {
        runTask(object : SingleMethodCallback {
            override fun onCallBack(value: String) {
                TODO("Not yet implemented")
            }
        })
    }

接着我们使用Kotlin协程提供的 suspendCoroutine 让runTaskDefault具有返回值;

改造一下runTaskDefault ---> runTaskWithSuspend

 suspend fun runTaskWithSuspend(): String {
        // suspendCoroutine是一个挂起函数
        return suspendCoroutine { continuation ->
            runTask(object : SingleMethodCallback {
                override fun onCallBack(value: String) {
                    continuation.resume(value)
                }
            })
        }
    }

这里 suspendCoroutine是一个挂起函数,挂起函数只能在协程或者其他挂起函数中被调用,同时我们在回调中将结果值传入到Coutination的resume方法中;

经过我们上述的操作将回调方法具有返回值了;

suspendCancellableCoroutine 的使用

Success And Failure 类别的接口

声明 success and failure 类型的接口

/**
 * @author : zhangqi
 * @time : 6/22/21
 * desc :
 */
interface ICallBack {
    fun onSuccess(data: String)
    fun onFailure(t: Throwable)
}

同样我们模拟一个耗时操作,在获取结果的时候 调用 onSuccess()将结果回调给实现,出现错误调用onFailure将错误交给实现处理

 /**
   * 模拟一个耗时操作
   */
 private fun request(callback: ICallBack) {
   thread {
     try {
       callback.onSuccess("success")
     } catch (e: Exception) {
       callback.onFailure(e)
     }
   }
 }

最后我们调用requet方法,传入接口的实现,

private fun requestDefault() {
  request(object : ICallBack {
    override fun onSuccess(data: String) {
      // doSomething
    }

    override fun onFailure(t: Throwable) {
      // handle Exception
    }

  })
}

同样我们使用Kotlin协程提供的挂起函数将 requestDefault()改造成 具有返回值的函数 requestWithSuspend()

只不过我们这里使用了 suspendCancellablkeCoroutine ,代码上见吧!

 private suspend fun requestWithSuspend(): String {
        return suspendCancellableCoroutine { cancellableContinuation->
            request(object : ICallBack {
                override fun onSuccess(data: String) {
                    cancellableContinuation.resume(data)
                }

                override fun onFailure(t: Throwable) {
                    cancellableContinuation.resumeWithException(t)
                }
            })
        }
    }

suspendCancellableCoroutine 是一个挂起函数,我们将requestWithSuspend声明称挂起函数

在onSucess()中我们我们调用CancellableContinue # resume 方法将结果返回,在onFailure调用CancellableContinuation # resumeWithException 将异常传入进去;

调用requestWithSuspend()

private fun runRequestSuspend() {
  try {
    viewModelScope.launch {
      val value = requestWithSuspend()
    }
  } catch (e: Exception) {
    e.printStackTrace()
  }
}

在ViewModel中Kotlin协程提供了 viewModelScope 来开启一个协程,改协程是具有声明周期的与当前ViewModel保持一致;

这里我们使用了try{}catch 将我们开启的协程处理了下,调用成功获取到value值,出现错误我们在catch块中除了一下;

以上就是 我们两种日常遇见频率较高的情况进行的改造(回调方法具有返回值)

Retrofit是如何支持协程的

Retrofit是在2.6版本开始支持,我们先对比下使用协程前后的区别

使用协前
/**
  * 发现页面的数据
  */
@GET("/api/v7/index/tab/discovery")
fun getDiscoveryData(): Call<OpenEyeResponse>

// 在ViewModel中调用
 /**
   * 没有使用协程做网络请求
   */
    fun getDiscoverData() {
      WidgetService.openEyeInstance.getDiscoveryData().enqueue(object : Callback<OpenEyeResponse> {
        override fun onResponse(call: Call<OpenEyeResponse>, response: Response<OpenEyeResponse>) {
          var body = response.body()
        }

        override fun onFailure(call: Call<OpenEyeResponse>, t: Throwable) {
        }
      })
    }

接着我们看下使用协程后

使用协程后
/**
  * 通过协程做本次请求
  * @return OpenEyeResponse
  */
@GET("/api/v7/index/tab/discovery")
suspend fun getDiscoveryDataCoroutine(): OpenEyeResponse

  /**
   * 使用协程做的请求
   */
fun getDiscoverDataWithCoroutine() {
  try {
    viewModelScope.launch {
      var discoveryDataCoroutine = WidgetService.openEyeInstance.getDiscoveryDataCoroutine()
    }
  } catch (e: Exception) {
  }
}

可以看见,在接口类中声明的方法声明为挂起函数,同时我们可以将我们想要的数据结构直接返回不用Call包一层;

Retrofit支持协程

Retrofit # HttpServiceMethod

 okhttp3.Call.Factory callFactory = retrofit.callFactory;
    if (!isKotlinSuspendFunction) {
      return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
      // 当是直接返回数据结构走这里
    } else if (continuationWantsResponse) {
      //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
      return (HttpServiceMethod<ResponseT, ReturnT>)
            // 执行了 SuspendForResponse
          new SuspendForResponse<>(
              requestFactory,
              callFactory,
              responseConverter,
              (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
    } else {
      //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
      return (HttpServiceMethod<ResponseT, ReturnT>)
          new SuspendForBody<>(
              requestFactory,
              callFactory,
              responseConverter,
              (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
              continuationBodyNullable);
    }

SuspendForResponse ---> KotlinExtensions.awaitResponse

SuspendForResponse(
        RequestFactory requestFactory,
        okhttp3.Call.Factory callFactory,
        Converter<ResponseBody, ResponseT> responseConverter,
        CallAdapter<ResponseT, Call<ResponseT>> callAdapter) {
      super(requestFactory, callFactory, responseConverter);
      this.callAdapter = callAdapter;
    }

    @Override
    protected Object adapt(Call<ResponseT> call, Object[] args) {
      call = callAdapter.adapt(call);
      //noinspection unchecked Checked by reflection inside RequestFactory.
      Continuation<Response<ResponseT>> continuation =
          (Continuation<Response<ResponseT>>) args[args.length - 1];

      // See SuspendForBody for explanation about this try/catch.
      try {
        // 在这里直接调用了 KotlinExtensions.awaitResponse
        return KotlinExtensions.awaitResponse(call, continuation);
      } catch (Exception e) {
        return KotlinExtensions.suspendAndThrow(e, continuation);
      }
    }

KotlinExtensions.awaitResponse

suspend fun <T> Call<T>.awaitResponse(): Response<T> {
  // 在这里使用了suspendCancellableCoroutine
  return suspendCancellableCoroutine { continuation ->
     // 当我们开启的协程开启了之后,会回调到这个方法
     // 取消当前的请求
    continuation.invokeOnCancellation {
      cancel()
    }
    enqueue(object : Callback<T> {
      override fun onResponse(call: Call<T>, response: Response<T>) {
        // 当成功拿到response之后 将response返回
        continuation.resume(response)
      }

      override fun onFailure(call: Call<T>, t: Throwable) {
        // 失败的话 直接将异常抛出
        continuation.resumeWithException(t)
      }
    })
  }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容