Android中kotlin协程的使用

Android中kotlin协程

协程是一种并发设计模式,可以在 Android 平台上使用它来简化异步执行的代码

在 Android 上,协程有助于管理长时间运行的任务,如果管理不当,这些任务可能会阻塞主线程并导致应用无响应。

特点:

  • 轻量

    线程

image-20231007104847062

子线程与主线程都是独立的,可以创建多个子线程,创建和销毁线程都会消耗资源,

协程:可以在单个线程上运行多个协程,因为协程支持挂起,不会使正在运行协程的线程阻塞。挂起比阻塞节省内存,且支持多个并行操作。

image-20231007105925489

依赖项信息

若需要在Android项目中使用,需要在build.gradle中添加以下依赖(版本可能会有变化)

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
}
协程域CoroutineScope

suspend修饰的函数方法,即"挂起函数",这个函数耗时比较长,不希望阻塞主线程

挂起函数代码块必须在CoroutineScope中执行

下面写了一个count()方法,用于每200ms修改mTextView的文字

suspend fun count() {
        for (i in 1..100) {
            mTextView.text = "$i"
            Thread.sleep(200)
        }
    }

协程的使用

创建协程域

通过CoroutineScope方法创建协程域

协程上下文CoroutineContext: 这个协程在哪个线程上执行

Didpatchers.IO 子线程

Dispatchers.Main UI主线程

dispatchers.Default 默认

下面的代码就是在主线程中创建了一个协程域

val scope = CoroutineScope(Dispatchers.Main)
开启任务
launch同步任务

使用launch开启一个同步任务: a -> b -> c 按顺序执行任务

launch方法中可以传入CoroutineScope参数, 声明任务在哪个线程执行,不传入则默认在scope的区域执行

scope.launch() { //任务默认就在scope所在区域执行
  count()
}

查看当前线程:Thread.currentThread().name

运行上面的代码后会报错

注意报错:Only the original thread that created a view hierachy can touch its views

在主线程中操作时还是会阻塞线程, 而在子线程执行时,是不能操作UI的,那应该怎么解决呢?可以通过withContext()切换线程 ,即切换代码执行时的协程域

count方法修改为:

suspend fun count() {
  for (i in 1..100) {
    withContext(Dispatchers.Main) { //切换上下文
      mTextView.text = "$i" //在主线程
    }
    delay(1000) //Thread.sleep(1000) 子线程
  }
}

这里只有改变mTextView.text是在主线程中操作的,其他操作都是在子线程,既防止了阻塞主线程,又解决了不能在子线程对UI进行操作的问题,这里还用了delay() 方法来进行延时操作,注意这也是一个挂起函数,需要在协程域中执行

launch同步执行,会有一个类型为Job的返回值,

val job = scope.launch(Dispatchers.IO) {
  count()
}

如果当有多个任务,需要一个任务做完再做下一个任务,该怎么办呢?

  • Jobjoin方法可以将协程挂起,直到任务结束

看下面的代码:

val job1 = scope.launch(Dispatchers.IO){ //任务默认就在scope所在的区域执行
                count1()
            }
            val job2 = scope.launch(Dispatchers.IO){
                job1.join()
                count2()
            }

    suspend fun count1(){
        for (i in 1..5){
            withContext(Dispatchers.Main){//切换上下文  切换代码执行的scope 切换线程
                mTextView.text = "$i"
            }
            delay(500)
        }
    }
    suspend fun count2(){
        for (i in 11..15){
            withContext(Dispatchers.Main){//切换上下文  切换代码执行的scope 切换线程
                mTextView.text = "$i"
            }
            delay(1000)
        }
    }

通过Jobjoin方法,将协程挂起,直到任务完成,这样job1执行后,job2才会执行

由于资源的抢夺,每次运行的结果可能会不同

async异步任务

使用async开启一个异步任务:a b c 同时做

有返回值 Deferred,当执行任务后需要返回值,使用async

val sum1 =  scope.async { //开启一个异步任务
                var sum = 0
                for (i in 1..100){
                    sum += i
                }
                sum
            }

image-20231009081857023

async中最后一行代码就是返回值如果是T,其返回值为Defferred<T>,可通过.await方法获取原来类型的值

看下面的代码,这里通过async开启了一个异步任务,通过await获取sum1执行后的结果,再进行后面的操作

val sum2 = scope.async { //开启一个异步任务
                var sum = sum1.await()
                for (i in 1..100) {
                    sum += i
                }
                sum
            }

注意:await方法也是挂起函数,需要在协程域中执行

scope.launch { 
                Log.v("test", "sum2: ${sum2.await()}")
            }

Flow数据流

当调用一个方法,在不同的时间/情况(异步),可能需要放回多个值,在之前,我们可以通过List等方法回调,但在挂起函数中,往往不能很快获得所有结果,这里就可以使用Flow回调数据

看下面的代码:

withContext 代码块,block最后一句即是返回值

这里调用loadDataFromDB方法,将返回值给TextView显示,如果想要异步返回多个值时,怎么办呢?

CoroutineScope(Dispatchers.Main).launch {
                val value = loadDataFromDB()
                mTextView.text = "$value"
            }

            //这个方法是在IO线程执行
            suspend fun loadDataFromDB(): Int = withContext(Dispatchers.IO) {
                //....
                10
            }

这里就需要Flow数据流,FLow与枪类比,枪里面有很多子弹,但是统一时刻,只能打出来一颗,通过不断开枪将所有的子弹打出来,外部可以接收打出的子弹

suspend fun loadDatas(): Flow<Int> = withContext(Dispatchers.IO) {
                //将子弹放到枪中 直接开枪
                flowOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
            }
CoroutineScope(Dispatchers.Main).launch {
                val gunFlow = loadDatas()
                //接收发射出来的子弹 (订阅)
                gunFlow.collect {
                    mTextView.text = "$it"
                    delay(500)
                }
            }

在上面的代码中,通过conllect订阅数据流,将每个数据流显示在textView中,由于flowOf开枪太快,这里使用delay进行了延时操作,方便看到textView的变化

演示

看一下flowOf的源代码

public fun <T> flowOf(vararg elements: T): Flow<T> = flow {
    for (element in elements) {
        emit(element)
    }
}

flowOF内部,已经通过 emit将每个数据发出了,即"自动开枪"

  • emit() 发射新的数据
  • collect 订阅/接收数据
  • flow{}创建flow对象
suspend fun loadDatas(): Flow<Int> = withContext(Dispatchers.IO) {
                //将子弹放到枪中
                flow {
                    for (i in 1..10) {
                        emit(i) //发射
                        delay(500)
                    }
                }
            }

CoroutineScope(Dispatchers.Main).launch {
                val gunFlow = loadDatas()
                //接收发射出来的子弹 (订阅)
                gunFlow.collect {
                    mTextView.text = "$it"
                }
            }

Activity和Fragment的协程域

自己创建的协程域需要自己管理这个协程的生命周期(创建 -> 销毁)

需要自己释放,在Activity和Fragment中提供了协程域,不需要我们自己释放,用起来更加方便

导入依赖

implementation('androidx.lifecycle:lifecycle-runtime-ktx:2.6.1')

在activity和fragment等LifecycleOwner中,可以直接通过lifecycleScope获取提供的协程域

lifecycleScope.launch(Dispatchers.IO) { 
    withContext(Dispatchers.Main) {
        
    }
}

ViewModel的协程域

和Activity类似,ViewModel也提供了协程域

导入依赖

 implementation('androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1')

通过viewModelScope获取协程域

viewModelScope.lauch {
  
}

协程的使

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值