Kotlin协程(一)初识协程

133 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天,点击查看活动详情

(一)初识协程

协程是什么?

  • 协程基于线程,它是轻量级线程
  • coroutine是cooperation(协作)和routine(日常)的简写
  • 协程让异步逻辑同步化,杜绝回调地狱
  • 协程的核心是:函数或一段程序被挂起,之后再在挂起的位置恢复
  • 协程的实现分为基础设施层和业务框架层,类似java中的NIO和Netty
  • 业务框架层指的是我们常用的协程函数

基础设施层(原生api)实现例子

    private fun doContinuation() {
        //协程体
        val continuation = suspend {
            println("协程执行中...")
            "协程的返回值"
        }.createCoroutine(object : Continuation<String> {
            override fun resumeWith(result: Result<String>) {
                //回调
                println("协程执行结束: $result")
            }

            override val context: CoroutineContext = EmptyCoroutineContext

        })
        continuation.resume(Unit)
    }

如上代码使用的是:import kotlin.coroutines.* 而kotlin协程业务框架层使用的是:import kotlinx.coroutines.*

Android中协程解决了什么问题?

  • 处理耗时任务,这种任务常常会阻塞主线程
  • 保证主线程安全,确保安全的在主线程调用suspend函数

在Android 11 谷歌建议使用协程来替代异步任务(asynctask)

协程的挂起与恢复

常规函数包括:invoke(call)和return,协程新增了suspend和resume

suspend:挂起或暂停,表示暂停执行当前协程,并保存所有局部变量 resume:让已暂停的协程从暂停处恢复执行

使用 suspend关键字修饰的函数叫挂起函数 挂起函数只能在协程体内或其他挂起函数内调用

挂起和阻塞

挂起:挂起点先记录下来,然后去做耗时任务,做完以后再恢复 阻塞:不做别的事情,一直等待

    private fun doGlobalScope() {
        //如果没有指定调度器,那么默认就是使用:Dispatchers.Default(default也是非主线程)
        GlobalScope.launch(Dispatchers.Main) {
            //挂起(不会阻塞主线程,比如按钮按下去会立马弹起来,然后6秒后打印)
            delay(6000)
            Log.v("zx", "${Thread.currentThread().name},挂起6秒后")

        }
        //阻塞(会阻塞主线程,比如按钮按下去要等5秒后才能弹起来)
        Thread.sleep(5000)
        Log.v("zx", "${Thread.currentThread().name},阻塞5秒后")
    }

主线程在遇到挂起点后,可以不用等待直接更新UI,但是遇到阻塞就必须等待阻塞完成之后

协程的调度器 Dispatchers

所有协程必须的调度器中运行

Dispatchers.Main:主线程,处理UI交互和轻量级任务(调用suspend,调用UI函数,更新liveData) Dispatchers.IO:非主线程,为磁盘和网络IO进行了优化(数据库,文件读写,网络请求) Dispatchers.Default:非主线程,专为CPU密集型任务进行了优化(数组排序,json解析,差异判断)

任务泄露

当某个协程任务丢失,导致内存,cpu资源浪费,称为任务泄露,为了避免协程泄露,Kotlin引入了结构化并发机制。 结构化并发的作用:取消任务,追踪任务,发送错误信号

结构化并发

定义协程,必须指定其CoroutineScope,它会跟踪所有协程,还可以取消由它所启动的所有协程。 常用的api有:

GlobalScope:生命周期是process级别的,即使Acitivty和fragment已经销毁,协程仍然在执行,GlobalScope是一个顶级协程,不太建议直接用 MainScope:在Activity中使用,可以在onDestroy中取消协程 viewModelScope:只能在viewModel中使用,绑定viewModel的生命周期 lifecycleScope:只能在Activity和fragment中使用,会绑定Activity和Fragment的生命周期

MainScope使用案例:
class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {

    private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
    private val mainScope = MainScope()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)
		doMainScope()
    }
    
    private fun doMainScope() {

        //retrofit不用写withcontext(Dispatchers.IO)因为retrofit会自动侦察到,
        //如果你是挂起函数会自动启用协程,创建一个异步线程去做操作
       mainScope.launch {
           //Toast.makeText(this@MainActivity, "MainScope", Toast.LENGTH_SHORT).show()
           try {
               //retrofit请求
               delay(1000)
           } catch (e: Exception) {
               //调用 mainScope.cancel()取消协程会抛出异常
               e.printStackTrace()
           }
       }

        //MainActivity 继承了委托以后CoroutineScope by MainScope(),就可以直接使用launch了
        launch {
        }
     }



    override fun onDestroy() {
        super.onDestroy()
        mainScope.cancel()
        //class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {}
        //MainActivity 继承了委托以后CoroutineScope by MainScope(),就可以直接使用cancle
        cancel()
    }
}
viewModelScope使用案例
class MyViewModel:ViewModel() {

       private val _count = MutableLiveData<Int>()
    val count: LiveData<Int>
        get() = _count
        
    fun getUser(){
        //因为继承了ViewModel,那么就可以直接使用viewModelScope
        viewModelScope.launch {
            //如果是耗时操作的话,retrofit会自动起一个io线程来执行,可以省略withContext(Dispatchers.IO)
            delay(3000)
            _count .value = 123

        }
    }
}

调用

class MainActivity : AppCompatActivity() {

 	private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
    private val myViewModel by viewModels<MyViewModel>()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)
        initViewModelAndLiveData()
      
    }

    private fun initViewModelAndLiveData() {
        myViewModel.count.observe(this, Observer {
            binding.btnRandom.text = it.toString()
        })
        
	myViewModel.getUser()
    }
}