Kotlin的协程,延时、超时(7秒后超时,并中断执行的任务)

Kotlin协程

简介:

优点:写法很简单,轻量级,挂起几乎不消耗内存,速度上优于java的线程,性能损耗小,能大幅度提高并发性能,本人推荐使用协程,而不用传统的线程

  • GlobalScope是生命周期是process级别的,即使Activity或Fragment已经被销毁,协程仍然在执行。所以需要绑定生命周期。
  • lifecycleScope只能在Activity、Fragment中使用,会绑定Activity和Fragment的生命周期
  • viewModelScope只能在ViewModel中使用,绑定ViewModel的生命周期

Kotlin 提供了三个调度程序,以用于指定应在何处运行协程:
Dispatchers.Main - 使用此调度程序可在 Android 主线程上运行协程。此调度程序只能用于与界面交互和执行快速工作。示例包括调用 suspend 函数,运行 Android界面框架操作,以及更新LiveData对象。
Dispatchers.IO - 此调度程序经过了专门优化,适合在主线程之外执行磁盘或网络I/O。示例包括使用Room组件、从文件中读取数据或向文件中写入数据,以及运行任何网络操作。
Dispatchers.Default - 此调度程序经过了专门优化,适合在主线程之外执行占用大量 CPU 资源的工作。用例示例包括对列表排序和解析 JSON。

一、协程

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'//lifecycleScope
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'//viewModelScope
1、创建一个协程

延时delay(1000L)

CoroutineScope(Dispatchers.IO).launch {
            //delay(1000L)
            Log.d("wang","===3====")
        }

也可以在方法里这么写

fun main() = runBlocking {
        launch {
            delay(2000)
            Log.d("wang","==2==")
        }
        Log.d("wang","==3==")
    }
2、异步任务的同步写法

代码:

	suspend fun fetchDocs() {
        val result = get("https://developer.android.com") // get方法里延迟一秒返回结果
        showUI(result.toString())  //此处会等上一行代码异步结束了才运行
    }

    /**
     * 模拟网络请求
     */
    suspend fun get(url: String) = withContext(Dispatchers.IO) {
        delay(1000L)
        Log.d("WANG", "===>get结果")
    }
    /**
     * 更新UI
     */
    fun showUI(result: String) {
        Log.d("WANG", "showUI===>$result")
    }

调用:

CoroutineScope(Dispatchers.IO).launch {
            fetchDocs()
        }
避免回调地狱

1、异步登录

suspend fun login(name:String,pass:String):LoginBean = suspendCancellableCoroutine { ctn->
	HttpMethods.webService.login(name,pass){
		 override fun onSuccess(bean: LoginBean) {
		 	ctn.resume(bean) //协程恢复,返回结果
		 }
		 override fun onError(e: Exception) {
		 	ctn.resumeWithException(e) //协程恢复,抛出异常
		 }
}

2、异步使用token请求

suspend fun connect(token:String):LoginBean = suspendCancellableCoroutine { ctn->
	HttpMethods.webService.connect(token){
		 override fun onSuccess(bean: LoginBean) {
		 	ctn.resume(bean) //协程恢复,返回结果
		 }
		 override fun onError(e: Exception) {
		 	ctn.resumeWithException(e) //协程恢复,抛出异常
		 }
}

3、调用

launch{
	val bean = login(name,pass) //在获取结果之前,协程是挂起状态不会执行下一步
	val result = connect(bean.token) 
	
}
3、两个异步任务合并
 fun main() = runBlocking<Unit> {
        val one = async { doOne() }
        val two = async { doTwo() }
        Log.e("TAG","测试运行") //此代码会立即运行,如果要等待异步结果返回才运行,需要添加await
        println("==两个异步相加结果为: ${one.await() + two.await()}")
    }

    suspend fun doOne(): Int {
        delay(1000L) // 假设我们在这里做了些有用的事
        return 13
    }

    suspend fun doTwo(): Int {
        delay(2000L) // 假设我们在这里也做了些有用的事
        return 29
    }

调用:main()

4、多个异步的并发执行
suspend fun getSynPermissions(userPermissList: MutableList<UserPermissBean>): Boolean {
    val list = userPermissList.map { it ->
        async {
            CabinetRepository.getInstance().getSynPermissions(it, errMsg)
        }
    }
    val results = list.awaitAll()
    return results.all { it }
}

MVVM模式中使用协程

LifecycleScope 在 AppCompatActivity 和 Fragment 中使用已经十分方便了,不过,目前很多项目都开始使用 MVVM,更多逻辑操作都是在 ViewModel 层,这时,就需要用到 ViewModelScope 了,它会自动绑定 ViewModel 的生命周期。
若需要使用 ViewModelScope,我们需要添加依赖:

注意:

1、对于Dispatchers.IO 以下调用会报错:

lifecycleScope.launch(Dispatchers.IO) {
      // 这里报错:Only the original thread that created a view hierarchy can touch its views.
      testView.text = "测试" } 

因为这在IO线程中来更新UI了。
修复方法可以尝试这样:

      // 这里报错:Only the original thread that created a view hierarchy can touch its views.
      withContext(Dispatchers.Main) {
      	testView.text = "测试"
      } } 

2、CoroutineScope是什么?如果你觉得陌生,那么GlobalScope、lifecycleScope与viewModelScope相信就很熟悉了吧(当然这个是针对于Android开发者)。它们都实现了CoroutineScope接口。
±--------------+
3、在Android中使用协程来请求数据,当接口还没有请求完成时Activity就已经退出了,这时如果不停止正在运行的协程将会造成不可预期的后果。所以在Activity中我们都推荐使用lifecycleScope来启动协程,lifecycleScope可以让协程具有与Activity一样的生命周期意识。

mvvm中不用observe方式回调到UI界面,而是使用同步写法

MainActivity中调用

lifecycleScope.launch(Dispatchers.IO) {
            val result = mainViewModel.getInfo11()
            Log.e("WY", "收到结果:$result")
        }

viewmodel中代码

suspend fun getInfo11():Boolean = suspendCancellableCoroutine { ctn->
        viewModelScope.launch {
            delay(20000) // 这里模拟网络请求
            Log.e("WY", "getInfo11===1")
            ctn.resume(true){}
//            ctn.resumeWithException(Throwable("会崩溃")) // 抛异常
        }
        Log.e("WY", "==getInfo11===2")
//        ctn.cancel() // 调用cancel,调用getInfo11的地方不会往下执行,即不会调用Log.e("WY", "收到结果:$result")
    }

这里viewModelScope会跟随生命周期的取消而取消

怎么验证viewModelScope协程是否跟随生命周期销毁而销毁

方法:在viewmodel里面的生命周期onCleared里加日志,activity按系统返回键后销毁会调用onDestroy,onCleared也会跟着调用,这时候看getInfo11方法里Log.e(“WY”, “getInfo11=1")不会输出,则说明viewModelScope协程是否跟随生命周期销毁而销毁,如果viewModelScope.launch换成一般的协程,日志Log.e(“WY”, "getInfo11=1”)是会输出

	override fun onCleared() {
        super.onCleared()
        Log.e("WY", "onCleared释放掉viewmodel")
    }

重点:如果CoroutineScope不在activity或fragment中怎么正确使用

如果在activity或fragment中我们可以使用:lifecycleScope和viewmodelScope,这样不会造成内存泄漏,但是如果不在呢,应该怎么正确使用协程?

方式一:如果你的协程不在 Activity 或 Fragment 中使用,那么你可以创建一个全局的协程作用域来管理它们。通常情况下,这个全局协程作用域应该用单例对象来实现。

以下是一个示例代码,使用协程单例来进行网络请求:

object NetworkManager {

    private val coroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())

    suspend fun fetchSomeData(): String {
        return withContext(coroutineScope.coroutineContext) {
            // 这里是协程代码,进行网络请求
            "这是请求到的数据"
        }
    }
}

在这里插入图片描述
方式二:在 ViewModel 中使用协程
在 ViewModel 中使用协程可以帮助你在后台执行长时间运行的任务,而不会阻塞 UI 线程。

在 ViewModel 中使用协程需要引入 kotlinx.coroutines 依赖并调用 viewModelScope.launch 函数。

通过使用 viewModelScope.launch 函数,你可以在协程作用域内执行异步操作,比如网络请求或者数据库操作。

一般使用
private fun run5() {
        //1、不阻塞主线程(推荐)
        CoroutineScope(Dispatchers.IO).launch {
            //执行代码.....
        }
  
        //2、优秀的线程切换
        CoroutineScope(Dispatchers.Main).launch {
            val task1 = withContext(Dispatchers.IO) {
                Log.d("LUO","1111========${DateTimeHelper.format(Date(),"yyyy-MM-dd HH:mm:ss")}")
                delay(2000)
                "服务器返回值:json"  //服务器返回结果赋值给task1
            }
            //刷新UI,task1
            Log.d("LUO","2222========${DateTimeHelper.format(Date(),"yyyy-MM-dd HH:mm:ss")}")
            Log.d("LUO", "值===========${task1}")
        }
        Log.d("LUO","3333========${DateTimeHelper.format(Date(),"yyyy-MM-dd HH:mm:ss")}")
    }

***方式三:在 协程结束的时候进行释放 ***

val stopHandShake = CoroutineScope(Job())
stopHandShake.launch {
    try {
        
    } catch (e: Exception) {    
    }
}.invokeOnCompletion {
    stopHandShake.cancel()
}

二、超时(7秒后超时,并中断执行的任务)

我们开发时候经常遇到这样的一个需求:**秒后超时,并中断执行的任务

fun main() = runBlocking<Unit> {
        launch {
            val result1 = withTimeoutOrNull(7000L) { //7秒后超时,并且中断执行的任务

                val time = (Random().nextInt(10 - 1) + 1).toLong() //生成1到10随机数
                // 模仿网络请求,结果:成功,失败,超时
                delay(time * 1000) // 模仿耗时
                val netResult = (Random().nextInt(10 - 1) + 1).toLong() // 随机数如果大于5则成功,小于5则失败
                if (netResult > 5) ArrayList<Int>() else 1//这里可以返回任意的类型
            }
            println("执行返回的结果为: $result1") // 结果:超时(null),失败(1),成功ArrayList<Int>()
        }
    }

三、老版本的协程

引入

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:0.18"

创建一个协程

 launch(CommonPool) {
 	//这里需要运行的代码
        }

协程里面嵌套协程

launch(CommonPool) {
            //创建带返回值的协程async
            var job = async(CommonPool) {
                delay(5000L)
                return@async "nihao"
            }
            var job1 = launch(CommonPool) {
                delay(5000L)
                return@async "nihao2"
            }
           
        }

协程的取消:

job1.cancel()

四、延时

1.协程方式的延时
launch(CommonPool) { // 在一个公共线程池中创建一个协程
    delay(1000L) // 非阻塞的延迟一秒(默认单位是毫秒)
    //延时一秒后执行
}

有关协程的挂起,恢复,调度这里就不做过多的介绍!!

2.handler方式的延时
new Handler().postDelayed(new Runnable() { @Override public void run() { // 延时执行的代码 } }, 2000);

以上代码不会导致内存泄漏。因为在postDelayed()方法中传递的Runnable对象是匿名内部类,它不会持有外部类的引用。当延迟时间到达时,Runnable对象会被Handler处理并执行,然后被垃圾回收器回收。因此,不会发生内存泄漏。

  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wy313622821

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值