前言

本来我想写个协程三部曲,但是查了下貌似协程x的api和协程基础讲的比较多了,但是实战讲的很少,或者讲实战也只是怎么用别人封装好的三方库对应的支持(retrofit,ViewModel,room等),这种还是只能算是对api的应用,如果让自己写一套,也是比较困难,于是直接写实战了

我觉得比较好的讲协程原理和协程基础api的文章(协程x的api可以看官网或搜博客),也推荐看<<深入理解kotlin协程>> 嘿嘿:

https://aisia.moe/2018/02/08/kotlin-coroutine-kepa/

https://blog.csdn.net/kotlinlang/article/details/89282970

https://juejin.im/post/6883652600462327821

ps:下面所说的协程专指Kotlin协程

pps:本篇文章针对有协程基础api和协程x api有使用经验的童鞋

ppps:有人说kt协程就是个线程切换框架(并且很多博客甚至也是这样写的?),但只能说ta并没有领悟到协程的真谛:抛开其实现,协程是给方法在原有的功能(call调用,return返回)上增加了suspend挂起resume恢复两个操作,而挂起和恢复的能力也给协程提供了异步转同步的能力,而总有人说协程是个线程切换框架,只不过是用了协程的拦截器附加的一部分功能,其核心能力还是函数挂起和恢复

正文

封装功能

需求1:封装一下动态权限请求

假设你有一套动态申请权限的api如下(可能是你用的三方的或者自己写的,我这个是自己写的,不过和框架耦合度比较高,就不发出来了)

    //安卓申请动态权限,并以回调的形式返回结果fun requestPermission(activity: Activity, mCallBack: OnRequestPermissionCallBack, vararg permissions: String){...}interface OnRequestPermissionCallBack {fun onPermissionOk()//权限全部申请成功fun onPermissionError(permission: String)//有权限申请失败fun onPermissionNotAsking(permission: String)//申请的权限有的被"不在询问"了}

然后你就可以使用协程异步转同步的形式改造如下:

    /*** 在协程中进行权限申请,如果申请成功往下走,否则直接cancel,如果传入callback就使用其失败回调,否则使用默认的*/suspend fun aRequestPermission(activity: Activity, vararg permissions: String, mCallBack: OnRequestPermissionCallBack? = null) =suspendCoroutine<Unit> {//挂起协程requestPermission(activity, object : OnOkPermissionCallBack() {override fun onPermissionOk() {it.resume(Unit)//如果成功就恢复协程(因为kt function默认返回值是Unit,所以这里返回的是Unit)}override fun onPermissionError(permission: String) {if (mCallBack == null)super.onPermissionError(permission)elsemCallBack.onPermissionError(permission)it.context.cancel()//如果没申请成功就取消当前协程(当然你也可以返回另一种返回值表示失败,这样就可以判断该方法的返回值来确定是否成功)}override fun onPermissionNotAsking(permission: String) {if (mCallBack == null)super.onPermissionNotAsking(permission)elsemCallBack.onPermissionNotAsking(permission)it.context.cancel()}}, *permissions)}

使用起来其实也比较简单,使用如下所示:

需求2:封装网络请求

如果你用的是retrofit,其已经封装了对suspend的支持,直接声明接口中的方法为suspend,返回值为对应的类即可,如下:

但是如果你觉得还需要在封装一下,或是你直接用的OkHttp或其他网络请求方案,就可以在封装一下了,如:

/*** 检查协程网络请求的返回值和当前状态是否符合要求* 如果不符合要求,会回调失败的接口,并返回null,不会取消当前协程*/
suspend fun <T : Any> Call<NetBean<T>>.getOrNull(errorListener: ((data: String, msg: String, e: Throwable?) -> Unit)? = null): T? =withIO {try {this@getOrNull.xxx().data//在这里获取并返回网络的数据,这个是同步的,如果是异步就在套一层挂起函数} catch (e: Exception) {//处理一下异常,然后在上面处理一下服务器的耦合逻辑判断checkCoroutineState()errorListener?.invoke("", "", e)//如果注册了请求失败的回调,就调用一下回调null//返回null}}/*** 检查当前协程的状态,如果是被取消了就会自动抛出取消异常*/
@OptIn(InternalCoroutinesApi::class)
suspend fun checkCoroutineState() {val job = coroutineContext[Job] ?: returnif (!job.isActive) throw job.getCancellationException()
}

这个方法是如果请求出异常或者失败,不会取消协程,只是返回了null,也可以在封装一下,如果请求失败直接取消协程:

/*** 检查协程网络请求的返回值和当前状态是否符合要求* 如果不符合要求,会回调失败的接口,并取消当前协程*/
suspend fun <T : Any> Call<NetBean<T>>.get(errorListener: ((data: String, msg: String, e: Throwable?) -> Unit)? = null): T {val orNull = getOrNull(errorListener)if (orNull == null) {checkCoroutineState()cancel()throw CancelException()}return orNull
}

这样就ok了,你如果想封装一下Dialog或者解析json,是不是也有思路了呢?

需求3:一个内容初始化很慢,别的地方可能在他还没初始化的时候就要使用,此时获取处要挂起,如果内容已经初始化过了,那就直接返回,并且只有最后一次挂起的地方能拿到初始化完成的数据(这里是为了简化,否则可以使用List<Listener>,其实是我有这方面的需求)

首先我们定义结构,很简单,就一个属性和get set

class AwaitValueNotify<T : Any> {private var t: T? = nullfun setValue(t: T) {this.t = t}suspend fun aGetValue(): T = t// 我这边定义suspend方法对应于普通方法前面会加一个a,表示await
}

然后我们分析一下,我们在get的时候挂起,并把回调存在bean里,然后在set的时候调用回调即可,其中如果重复get就覆盖之前的回调,思路很简单,代码如下

import kotlinx.coroutines.cancel
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutineclass AwaitValueNotify<T : Any> {private var t: T? = nullprivate var listener: Continuation<T>? = null //协程体,其实就是用于恢复协程的回调fun setValue(t: T) {this.t = tif (listener != null) {listener?.resume(t) //恢复协程,并置空listener = null}}suspend fun aGetValue(): T = suspendCoroutine { coroutine -> //挂起协程,其协程就是调用这个suspend方法的协程val t = tif (t != null)coroutine.resume(t) //如果有数据就直接恢复协程else {listener?.context?.cancel() //如果之前有挂起的协程,就直接取消并置空(达到同时只有一个协程能拿到数据)listener = coroutine}}fun clearListener() { //如果需要提前回收该类,需要清除并cancel其内的协程listener?.context?.cancel()listener = null}
}

代码比较简单,注释写的应该可以看明白

ps:这里为演示协程只考虑了单线程的情况,所以如果需要应对多线程的话,需要自行解决并发问题

4.挂起等到Dialog点确定后恢复,点取消或被取消则取消协程

封装后的api:

    /*** 将Dialog转成suspend的*/suspend fun <T> awaitDialog(run: (ok: (T) -> Unit) -> Dialog?): T =suspendCancellableCoroutine { continuation ->var dialog: Dialog? = nulldialog = run {continuation.resume(it)dialog?.setOnDismissListener(null)dialog?.dismiss()}if (dialog == null) {continuation.cancel()return@suspendCancellableCoroutine}continuation.invokeOnCancellation {dialog.setOnDismissListener(null)}dialog.setOnDismissListener {continuation.cancel()}}

可以再封装一下,比如tips弹窗,统一样式弹窗等,下面是实战中用到的

    private suspend fun showDialog2(): Unit = DialogUtilKt.awaitDialog { ok ->DialogUtil.showCenterDialog4(this, R.layout.xxx1) { v, d ->业务逻辑...d.tvDialogOk_2.click {ok(Unit)}}}private suspend fun showDialog1(): Unit = DialogUtilKt.awaitDialog { ok ->DialogUtil.showCenterDialog4(this, R.layout.xxx2) { v, d ->业务逻辑...d.tvDialogOk.click {ok(Unit)}}}

使用方式:先弹一个弹窗让用户确认,如果确认了第一个就弹第二个,接着调用接口和加载窗,最后接口调用成功弹toast并关闭页面

            main {showDialog1()//挂起函数showDialog2()//挂起函数iPost.xxx(xxx).checkAndGet()//挂起函数,该方法可以自动显示隐藏加载窗R.string.xxx.showToast()finish()}

写业务逻辑

理想情况下写一个斗地主游戏

使用协程的特性,我们可以写出下面的伪代码

suspend fun main(){while(true){开始游戏()}
}suspend fun 开始游戏(){val userList = 匹配玩家()洗牌(userList)val 地主 = 叫地主(userList)出牌阶段(地主 ,userList)结算分数(userList)
}suspend fun 匹配玩家():List<User> = ...//从服务器获取suspend fun 出牌阶段(地主:User, userList:List<User>){while(userList.都有牌()){地主.出牌()xx.出牌()xx.出牌()}
}...

实际项目中的应用

这是我用在项目中的一个功能,是从本地扫描音乐文件并做动画,然后处理上传音乐等的逻辑(请忽略有的地方Music写成了Image)

用协程写出来几乎没有嵌套,并且逻辑相对于回调清晰的多,而且如果用回调的话,就会变成回调地狱

至于Flow和Channel,我平时RxJava和基于数据驱动用的不多...以后有机会在聊

其实我觉得在服务端的事件驱动型框架中,可以更好的发挥协程的作用,以更少的资源处理更多的服务

如果有好的想法我在补充(或者童鞋们可以发评论)

end

Kotlin协程在项目中的实际应用相关推荐

  1. 动手实现Kotlin协程同步切换线程,以及Kotlin协程是如何实现线程切换的

    前言 突发奇想想搞一个同步切换线程的Kotlin协程,而不用各种withContext(){},可以减少嵌套且逻辑更清晰,想实现的结果如下图: 分析 实现我们想要的结果,首先需要知道协程为什么可以控制 ...

  2. Kotlin 协程,怎么开始的又是怎么结束的?原理讲解!

    九心 | 作者 承香墨影 | 校对 https://juejin.cn/post/6862548590092140558 | 原文 Hi,大家好,这里是承香墨影! 上周我们聊到 Kotlin 协程的使 ...

  3. 大型Android项目架构:基于组件化+模块化+Kotlin+协程+Flow+Retrofit+Jetpack+MVVM架构实现WanAndroid客户端

    前言:苟有恒,何必三更眠五更起:最无益,莫过一日曝十日寒. 前言 之前一直想写个 WanAndroid 项目来巩固自己对 Kotlin+Jetpack+协程 等知识的学习,但是一直没有时间.这里重新行 ...

  4. Android中使用Kotlin协程代替RxJava封装网络请求

    现在的Android项目普遍使用Retrofit+RxJava的组合实现网络接口请求与数据的展现.这一功能通过Kotlin语言的协程功能也可以很方便的实现. 相比较而言,RxJava功能过于强大,如果 ...

  5. Android中使用Kotlin协程(Coroutines)和Retrofit进行网络请求(二)之文件下载

    写在前面 下载功能是非常常用的功能,今天我们要通过kotlin协程和retrofit来是实现文件下载的功能.retorfit本身可以将请求结果以InputStream的形式返回,拿到InputStre ...

  6. 在 Android 开发中使用 Kotlin 协程 (一) -- 初识 Kotlin 协程

    前言 最近在研究 Kotlin 协程,发现功能真的超级强大,很有用,而且很好学,如果你正在或计划使用 Kotlin 开发 Android,那么 Kotlin 协程你一定不能错过! 协程是什么? 我们平 ...

  7. 【Kotlin 协程】Flow 异步流 ② ( 使用 Flow 异步流持续获取不同返回值 | Flow 异步流获取返回值方式与其它方式对比 | 在 Android 中使用 Flow 异步流下载文件 )

    文章目录 一.使用 Flow 异步流持续获取不同返回值 二.Flow 异步流获取返回值方式与其它方式对比 三.在 Android 中 使用 Flow 异步流下载文件 一.使用 Flow 异步流持续获取 ...

  8. 【Kotlin 协程】协程启动 ⑥ ( 协程生命周期状态 | 新创建 New | 活跃 Active | 完成中 Completing | 已完成 Completed | 取消中 | 已取消 )

    文章目录 一.协程标识 Job 实例对象 二.协程生命周期状态 三.协程生命周期状态改变 一.协程标识 Job 实例对象 通过 launch 或 async 协程构建器 函数 创建 协程 , 会返回 ...

  9. Kotlin 协程 + Spring webflux 开发后端

    前言 后端响应式是未来,吞吐量会更大,而资源占用更少,其用到了类似Android系统的Loop(事件循环)机制,而协程可以减少线程等待的消耗,并且同步式的编程方式使代码可读性更高,两个仿佛天生就是一对 ...

最新文章

  1. ICDM 2019最佳论文:从图片、文本到网络结构数据翻译,一种新型的多属性图翻译模型
  2. 家用笔记本电脑什么牌子好_电烤箱什么牌子好?哪个牌子的烤箱质量好?家用烤箱什么牌子质量好?...
  3. Taro+react开发(89):封装为一个函数渲染
  4. python为什么没有指针_Python 没有指针,如何解算法题?
  5. java中取系统时间_JAVA中获取当前系统时间(示例代码)
  6. Redmi K50 Pro未发先火 卢伟冰:压力好大
  7. L1-024 后天 (5 分) — 团体程序设计天梯赛
  8. Android 代码关于重构的一点体会
  9. 499服务器响应,一边制造,一边讲解http状态码502|504|499|500
  10. 【图像去模糊】SDWNet: A Straight Dilated Network with Wavelet Transformation for image Deblurring
  11. 使用ABAP批量下载有道云笔记中的图片
  12. 大数据分析软技能有哪些
  13. 字符串-Manacher算法(你知道马拉车算法吗?)
  14. python爬虫 requests+bs4爬取猫眼电影 傻瓜版教程
  15. 2021-10-06 统计学-基于R(第四版)第一章课后习题记录及总结
  16. VS小番茄插件常用快捷键
  17. JUC——JUC强大辅助类讲解
  18. obsidian安装第三方插件——无法加载插件
  19. Charles 重发、并发请求
  20. Android GMail/EMail附件读取/存储简析

热门文章

  1. 可折叠设备的桌面模式
  2. Android Study Material Design 十 再探沉浸式
  3. Web前端学习笔记(十一)---聚光灯效果
  4. 带圈的11-15如何打出
  5. Exchange 2016无法执行ActiveSync测试
  6. 186、商城业务-检索服务-页面分页数据渲染
  7. 2019天梯赛第四次训练赛
  8. SpringBoot 项目实战 ~ 9.数据缓存
  9. 2019智能手表推荐_2019年最佳Android Wear智能手表选购清单
  10. 解决:远程连接mysql:报异常,1044 - Access denied for user ‘root‘@‘%‘ to database ‘xxxxxx‘