zoukankan      html  css  js  c++  java
  • AsyncTask被废弃了,换Coroutine吧

    本文主要是学习笔记,有版权问题还请告知删文

    鸣谢:guolin@第一行代码(第三版)

    你是否也在最近的代码中看见了 AsyncTask 被一条横杠划掉了
    这表明——他要被Google放弃了

    Google说让我们换成协程,也就是Coroutine,我们来看看怎么无缝切换

    1. 添加依赖

    首先,他并不是在标准库里,所以我们要添加依赖

    dependencies {
          implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7"
          implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7"
    }
    

    2. 使用协程

    2.1 GlobalScope.launch

    最简单的方法就是调用GlobalScope.launch
    这里每创建一个协程都是顶层协程!对!没错!协程是分层级

    GlobalScope.launch {
          //do something
    }
    

    这个函数的特点就是:自己闷头执行,主线程结束,他也结束(虽然有可能没执行完)

    2.2 runBlocking

    runBlocking从字面意思就可以理解,运行并阻塞,意思是运行协程,并在携程未结束时阻塞当前线程

    runBlocking{
          //do something            
    }
    

    这个函数的特点就是:执行时阻塞当前线程
    此方法一般用在测试环境,实际使用中容易有性能问题

    2.3 多协程

    多协程只要我们这么写就可以

    runBlocking{
          launch{
                //do something#1
          }
          launch{
                //do something#2
          }
          launch{
                //do something#3
          }
    }
    

    这样我们就创建了3个子协程
    注意,我们这里的launch和GlobalScope.launch有一些区别,GlobalScope创建的是顶层协程
    另外,我们的多协程还是运行在一个线程里面的,也就是3个协程运行在一个线程里面
    到这里,相信你们都有了一种奇怪的感觉,一个线程里实现并发???
    我要说的是:没错!
    因此它不需要操作系统的参与,像是子协程有一百万个,它也不会产生OOM
    我测试了1000000个协程总共消耗了5722ms,也就是5秒
    高并发效率非常高

    2.4 suspend关键字(能在协程里调用的函数)

    别担心,这跟Java废弃的那个强制挂起线程的函数没什么关系,只是恰巧长得一样而已@Java suspend()

    当你的代码过多导致可读性很差的时候,就需要将部分代码写成一个函数,这时候就需要用到suspend,这样我们就可以在协程作用域里面调用该函数
    用guolin大神的话说,就是“suspend可以将我们的函数声明成挂起函数”
    我理解的是,挂起函数可以在其内部调用其他的挂起函数,比如:delay()

    suspend fun 函数名(参数列表){
          //do something
    }
    

    但是suspend并不会让我们的函数就像在协程作用域里写的一样,比如:无法调用launch创建子协程
    这时我们就需要调用corountineScope函数

    suspend fun 函数名(参数列表) = corountineScope{
          launch{
                //do something
          }
    }
    

    corountineScope 是会在其作用域里的程序执行完成前阻塞当前协程的,但它不会影响其他协程,也不会影响线程
    而且它只能在 协程作用域 或 挂起函数 下调用

    runBlocking {
            launch {
                println("即将被阻塞")
                coroutineScope{
                    for (i in 1..3){
                        println("阻塞中")
                        delay(10)
                    }
                }
                println("阻塞结束")
            }
            launch {
                println("我没被阻塞")
            }
        }
    

    这个运行结果就会是这样的

    即将被阻塞
    阻塞中
    我没被阻塞
    阻塞中
    阻塞中
    阻塞结束
    

    3. 取消协程(关掉它!!!)

    launch函数会返回一个Job类型的对象
    Job类里面有一个cancel函数,调用它就可以取消协程

    val job = Job()
    val scope = CoroutineScope(job)
    scope.launch{
          //do something
    }
    job.cancel()
    

    这就是一般情况下项目里协程的写法
    这里还有一个要注意的,第二行的CoroutineScope(job)并不是CoroutineScope的构造函数,而是一个单独的函数
    至于为啥Kotlin要把这个函数名首字母大写,我也不知道...

    4. 获得协程运行结果

    4.1 async

    假设我们在需要进行一个耗时操作,我们将这个任务放到协程里,可我们不知道什么时候能获得这个结果,而launch最终也只是返回一个Job对象,这样的机制不利于我们处理结果,所以为了获得我们需要的结果,async函数就必不可少了,async会新建一个子协程并返回一个Deferred对象,Deferred里有一个await函数,他就会给我们想要的结果

    还有一点相信你已经想到了,await会阻塞当前的协程,直到获得我们想要的结果

    写法如下

    runBlocking {
            val result = async { 
                //do something
            }.await()
        }
    

    其中常量result就是我们需要的结果

    这里需要注意的是,如果你有多个结果需要获取,一定要明白其中的逻辑关系然后优化好代码
    比如:我们需要用A和B生成E,C、D生成F,E、F生成result

    代码就应该这么写

    val job = Job()
        val scope = CoroutineScope(job)
        scope.launch {
            val E = async {
                //A和B生成E
            }
            val F = async {
                //C和D生成F
            }
            val result = async {
                //E.await() 和 F.await() 生成
            }
        }
    

    4.2 withContext

    写法如下

    runBlocking {
            val result = withContext(Dispatchers.Default) { 
                //do something
            }
        }
    

    还是引用guolin大神的话:withContext可以理解为async的简化版写法。
    调用 withContext 会立刻执行代码块里的代码,同时将当前的协程阻塞。代码块执行完后,会将最后一行的结果当做返回值返回。唯一不同的是他有个参数。

    简而言之,就是没有 await 函数,没法灵活控制协程阻塞,除此之外多了个参数,其他和async区别不大

    guolin大神还说了:withContext是强制要求输入参数的。而刚才提到的除了coroutineScope,其他所有的协程作用域构建器也都可以指定参数,只不过不强制

    那么这个参数是干嘛的呢???

    答案是用来选择并发线程策略的,这种参数主要有三种可选:Dispatchers.Default、Dispatchers.IO、Dispatchers.Main
    是的,你没听错,线程策略,是线程,也就是说,他会创建线程来帮助实现并发,要知道网络请求是只能在线程里实现并发的,协程是不行的。

    Dispatchers.Default 默认的低并发线程策略
    Dispatchers.IO 高并发线程策略
    Dispatchers.Main 不开启线程

    5. 回调简写(suspendCoroutine)

    我们回调的通常写法是:

    HttpUtil.sendHttpRequest(address : String , object : HttpCallbackListener {
            override fun onFinish() {
                //do something
            }
    
            override fun onError() {
                //do something
            }
    
        })
    

    类似这样,代码就很多,动个手指头都嫌累

    Kotlin就给了我们一个解决策略

    用suspendCoroutine函数!

    suspendCoroutine必须在协程域中或挂起函数里调用
    它接受一个lambda表达式参数,主要作用是将当前协程立刻挂起,然后在一个普通线程(guolin大神的话,我理解的是非主线程就行)里执行lambda表达式中的代码。
    lambda表达式又传入一个Continuation参数,调用它的resume() 或 resumeWithException() 就可以让协程恢复执行。

    代码示例:

    suspend fun request(address: String): String {
            return suspendCoroutine { contination ->
                HttpUtil.sendHttpRequest(address, object : HttpCallbackListener {
                    override fun onFinish(Response: String) {
                        contination.resume(Response)
                    }
                    override fun onError(e: Exception) {
                        contination.resumeWithException(e)
                    }
                })
            }
        }
    

    然后你就想骂我,这TM不是更多了吗???

    你调用试试...

    suspend fun getBaiduResult(){
          try{
                val result = request ( "https://www.baidu.com" )
                //对result进行处理
          }catch{
                //异常处理
          }
    }
    

    爽不爽!?

    还没写完,忙的抽不出时间写了 T^T

  • 相关阅读:
    8 盒子模型
    7 display属性
    如何提高运维价值体系
    Python学习之OS模块初识
    7、MongoDB学习之游标
    Python 学习之文件对象的属性和方法简介
    Python集合set()操作详解
    Python中的字典介绍
    Python序列之元组
    Python序列之列表
  • 原文地址:https://www.cnblogs.com/hairless/p/13295633.html
Copyright © 2011-2022 走看看