kotlin 协程 应用于android场景

之前在网上找关于kotlin协程的教程,基本都是1.0及以下的版本。(现在的版本是1.3.2)很多api都被改掉了,或者是过时了。

后面也有一些优秀的文章,使自己结合着官方的文档,才渐渐搞懂

感谢:https://www.jianshu.com/p/32a2066b8710(如果你还完全不懂协程,请先看这个,很详细)

           https://www.kotlincn.net/docs/reference/coroutines/basics.html(看完上面的,再看看这个官网,最新写法,略有不同)

           https://www.jianshu.com/p/6e6835573a9c(这位大佬,玩转retorfit+协程,打造网络请求,深受启发,我借鉴改造了一下,写了文章后面的网络请求)

           https://www.jianshu.com/p/b0b9e04d8793

 

官方github地址:https://github.com/Kotlin/kotlinx.coroutines

 

emm。。使用协程有什么好处呢?

       我们可以用顺序编程的方式实现异步以及并发任务,这比响应式流的链式调用更加直观,给了我们同步编程的体验;其次,在 IO 密集型任务中,协程的挂起操作相比老式的线程阻塞操作大大提高了性能,这成为了协程的杀手级特性之一。协程挂起几乎无代价,无需上下文切换或涉及OS。

协程并不是为了取代线程,协程对线程进行抽象,你可以看成协程是一个异步调用的框架,解决了之前线程间协作代码繁琐的问题

 

前奏

在app下的build.gradle添加依赖。并且保证ext.kotlin_version = '1.3.50'为这个版本

dependencies {
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2'
}

然后就可以在代码里这样写啦,其中可以将launch换成async。这个两个的区别是async有返回值

 GlobalScope.launch {
      Log.d("GlobalScope : ", "thread:: ${Thread.currentThread().name}")
 }
 
CoroutineScope(Dispatchers.Main).launch {
      Log.d("CoroutineScope : ", "thread:: ${Thread.currentThread().name}")

 }
MainScope().launch {
      Log.d("MainScope ", "thread:: ${Thread.currentThread().name}")
}

log是这样打印的。可以看到CoroutineScope 我们是指定在Main里的,所以他所属的线程是在我们的主线程里;而MainScope也是如此;而GlobalScope顾名思义全局的,运行在后台共享的线程中。当然这都是上下文决定的。 可以看看 MainScope看看源码, 里面指定了上下文Dispatchers.Main。CoroutineScope 或者GlobalScope,不指定的话,都是默认Dispatchers.Default

协程上下文包含一个 协程调度器 (参见 CoroutineDispatcher)它确定了哪些线程或与线程相对应的协程执行。协程调度器可以将协程限制在一个特定的线程执行,或将它分派到一个线程池,亦或是让它不受限地运行。所有的协程构建器诸如 launch 和 async 接收一个可选的 CoroutineContext 参数,它可以被用来显式的为一个新协程或其它上下文元素指定一个调度器。(容我引用官网的一描述)

21:42:07.250 12446-12471/com.nativeproject D/GlobalScope :: thread:: DefaultDispatcher-worker-119-11-03

21:42:07.330 12446-12446/com.nativeproject D/CoroutineScope :: thread:: main

2019-11-03 21:42:07.330 12446-12446/com.nativeproject D/MainScope: thread:: main

 

协程执行耗时操作,完毕后更新ui

       知道了这些有啥用呢?当然是有用啦,比如我们要做某些耗时操作,比如网络或者io等,就可以在协程里操作啦。以前我们可能会new Thread开线程就干,各种回调。现在看看协程优雅操作并更新UI的吧

  CoroutineScope(Dispatchers.Main).launch {
            val result = async(Dispatchers.IO) {
                  //使用okhttp使用同步请求,完事将response返回
                  val request = Request.Builder().url("http://www.baidu.com").build()
                  val response= OkHttpClient().newCall(request).execute()
                  response
            }
            //等待异步执行的结果
            val response = result.await()
            //返回的结果,直接显示在sample_text这个textview上,也就是更新UI
            sample_text.text = response.body()?.string()
            Log.e("----", "CoroutineScope : ${Thread.currentThread().name}")
        }

一气呵成,没有半点不妥。当然这里使用了async开启了新的协程,也可以使用withContext直接切换上下文。效果也是一样的

  CoroutineScope(Dispatchers.Main).launch {
            val result = withContext(Dispatchers.IO) {
                val request = Request.Builder().url("http://www.baidu.com").build()
                val call = OkHttpClient().newCall(request)
                val execute = call.execute()
                return@withContext execute.body()?.string()
            }
            sample_text.text = result
            Log.e("----", "CoroutineScope : ${Thread.currentThread().name}")
        }

至于这两个怎么选择使用,看以看看这篇文章async vs withContext 有大佬已经用实验比较了一波

需要并发时使用async,不需要则用withContext更高效

 

 

异步中更新ui

 val request = Request.Builder().url("http://www.baidu.com").build()
        val call = OkHttpClient().newCall(request)
        call.enqueue(object : Callback{
            override fun onResponse(call: Call, response: Response) {
                response.body()?.string()?.let { threadToMain(it) }
            }

            override fun onFailure(call: Call, e: IOException) {

            }

})



  fun threadToMain(string: String){
        MainScope().launch {
            sample_text.text = string
        }
    } 

这里则是用异步的网络请求,这个场景,表示一些第三方的框架可能回到异步,我们也是可以用协程去直接更新UI的。(不一定是okhttp的异步请求,这里只是演示场景)

 

主菜,用协程打造通用的网络请求

先准备个基本的实体类,一般json格式都是这样的,这里用泛型封装了一下

class BaseResult<T> {

    var errorCode: Int = 0

    var errorMsg: String = ""

    var data: T? = null
    
}

再弄一个,成功或者失败,完成接口回调

class RetrofitCoroutineDSL<T> {

    var api: (Call<ResponseBody>)? = null
    internal var onSuccess: ((BaseResult<T>) -> Unit)? = null
        private set

    internal var onFail: ((msg: String, errorCode: Int) -> Unit)? = null
        private set

    internal var onComplete: (() -> Unit)? = null
        private set

    internal var onSuccessList: ((BaseResult<List<T>>) -> Unit)? = null
        private set

    /**
     * 获取数据成功,json数据是数组的情况
     * @param block (T) -> Unit
     */
    fun onSuccessList(block: (BaseResult<List<T>>) -> Unit) {
        this.onSuccessList = block
    }

    /**
     * 获取数据成功
     * @param block (T) -> Unit
     */
    fun onSuccess(block: (BaseResult<T>) -> Unit) {
        this.onSuccess = block
    }

    /**
     * 获取数据失败
     * @param block (msg: String, errorCode: Int) -> Unit
     */
    fun onFail(block: (msg: String, errorCode: Int) -> Unit) {
        this.onFail = block
    }

    /**
     * 访问完成
     * @param block () -> Unit
     */
    fun onComplete(block: () -> Unit) {
        this.onComplete = block
    }

    internal fun clean() {
        onSuccess = null
        onComplete = null
        onFail = null
    }
}

接下来就是统一请求模版

    private val scope = MainScope()

    fun <T> retrofit(clazz: Class<T>, dsl: RetrofitCoroutineDSL<T>.() -> Unit) {
        //在主线程中开启协程
        scope.launch(Dispatchers.Main) {
            val coroutine = RetrofitCoroutineDSL<T>().apply(dsl)
            coroutine.api?.let { call ->
                //async 并发执行 在IO线程中
                val deferred = async(Dispatchers.IO) {
                    try {
                        call.execute() //已经在io线程中了,所以调用Retrofit的同步方法
                    } catch (e: ConnectException) {
                        coroutine.onFail?.invoke("网络连接出错", -1)
                        null
                    } catch (e: IOException) {
                        coroutine.onFail?.invoke("未知网络错误", -1)
                        null
                    }
                }
                //当协程取消的时候,取消网络请求
                deferred.invokeOnCompletion {
                    if (deferred.isCancelled) {
                        call.cancel()
                        coroutine.clean()
                    }
                }
                //await 等待异步执行的结果
                val response = deferred.await()
                if (response == null) {
                    coroutine.onFail?.invoke("返回为空", -1)
                } else {
                    val t = response.body()?.string()
                    try {
                        //Gson解析泛型报错的处理
                        val type = ParameterizedTypeImpl(BaseResult<T>().javaClass, arrayOf<Class<*>>(clazz))
                        val data = Gson().fromJson<BaseResult<T>>(t, type)

                        coroutine.onSuccess?.invoke(data)
                    }catch (e : JsonSyntaxException){
                        coroutine.onFail?.invoke("解析数据错误",-1)
                    }

                }
                coroutine.onComplete?.invoke()
            }
        }
    }


   //当activity被销毁时,取消任务
  fun destory(){
        scope.cancel()
    }

因为我们封装了,基础的bean类,所以中间用Gson解析时要做一下处理,不然会报  java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to XXXX(参考这个处理https://www.jianshu.com/p/d62c2be60617

下面就是直接使用啦,其实也没什么特别说明的,注意activity销毁取消任务,完整的dome在git上

https://github.com/WoBuShuo/MainScope

github上已经搞成了mvp模式,喜欢的同学可以点个start哦

retrofit(RankBean::class.java) {
            api = Retrofit.Builder().baseUrl("https://www.wanandroid.com/").build()
                .create(HttpServer::class.java).rankData()
            onSuccess {
                val bean = it.data as RankBean
                Log.e("-----", "data: " + bean.total)
            }
            //失败回调
            onFail { msg, errorCode ->

            }
            //完成回调
            onComplete {

            }
}

 

 

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值