[Kotlin协程] 快速上手协程

263 阅读3分钟

背景

搜索了很多资料,花了几天时间看完,却发现 依然不知道怎么在我的项目中使用协程.

原因有两个:

1.学习的时候目标不明确,没有带着问题去学.

2.一个陌生的知识点,和Java的编程思维还不太一样,了解到熟练有一个过程.

解决办法

1. 快速启动协程

目标: 用协程替换一个子线程执行的场景.

5S后子线程发送一个消息给主线程.

        Thread() {
            mHandler.sendEmptyMessageDelayed(SKIP_MAIN, 5 * 1000.toLong())
        }.start()

改写后:

       CoroutineScope(Dispatchers.Default).launch {
            delay(5000)
            mHandler.sendMessage(Message.obtain().apply { what = SKIP_MAIN })
        }
  1. 启动协程的示例有三个runBlocking GlobalScope CoroutineScope 选哪一个?

选CoroutineScope,因为runBlocking阻塞 阻塞了为什么还要用协程呢. GlobalScope全局.全局意味着存在不可控的风险.

  1. Dispatchers的三个模式IO,Default,Main怎么用?

Dispatchers.Main - 使用此调度程序可在 Android 主线程上运行协程。此调度程序只能用于与界面交互和执行快速工作。示例包括调用 suspend 函数、运行 Android 界面框架操作,以及更新 LiveData 对象。

Dispatchers.IO - 此调度程序经过了专门优化,适合在主线程之外执行磁盘或网络 I/O。示例包括使用 Room 组件、从文件中读取数据或向文件中写入数据,以及运行任何网络操作。

Dispatchers.Default - 此调度程序经过了专门优化,适合在主线程之外执行占用大量 CPU 资源的工作。用例示例包括对列表排序和解析 JSON。

2. 替换回调

目标: 用协程替换一个回调执行的场景

参考这儿: [Kotlin协程] 回调地狱的一种解决思路

3. 解决内存泄露

目标: 用协程解决子线程造成内存泄露的一个场景

在一个Activity中

    class MyHandler : Handler {
        var context: WeakReference<Context>

        constructor(context: Context) {
            this.context = WeakReference(context)
        }

        override fun handleMessage(msg: Message) {
            when (msg.what) {
                SKIP_MAIN -> {
                    context.get()?.apply {
                        startActivity(Intent(this, MainActivity::class.java))
                    }
                }
                else -> throw IllegalArgumentException()
            }
            super.handleMessage(msg)
        }
    }

    private val mHandler = MyHandler(this)
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Thread() {
            mHandler.sendEmptyMessageDelayed(SKIP_MAIN, 5 * 1000.toLong())
        }.start()
        
//        CoroutineScope(Dispatchers.Default).launch {
//            delay(5000)
//            mHandler.sendMessage(Message.obtain().apply { what = SKIP_MAIN })
//        }
    }

很容易看出,如果5S内我退出了App,5S后还是会打开另一个Activity;

  1. 修改方式1 直接给mHander在onDestory的时候赋空. 阻止其发送消息
  2. 修改方式2 在mHander的handleMessage方法中判断当前Activity的状态. 如果是isFinishing 就不执行跳转

上诉两种方式修改跳转的问题是OK的,但是并没有取消thread的执行. 如果使用协程(也就是上诉我注释掉的代码)并且在onDestory执行取消方法即可解决.

  1. 修改方式3. 使用 CoroutineScope()方法
    val coroutineScope = CoroutineScope(Dispatchers.Default);
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        coroutineScope.launch {
            delay(5000)
            mHandler.sendMessage(Message.obtain().apply { what = SKIP_MAIN })
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        coroutineScope.cancel()
    }

4. 解决业务耦合

这个Activity用到协程,需要在destory中取消,那么其他Activity中肯定也需要用到. 将代码抽取到BaseActivity中.

abstract class BaseActivity : AppCompatActivity(), CoroutineScope {

    protected lateinit var job: Job
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        job = Job()
        setContentView(LayoutInflater.from(this).inflate(bindLayoutId(), null))
        initData(savedInstanceState)
    }

    override fun onDestroy() {
        job.cancel()
        super.onDestroy()
    }

    protected abstract fun initData(savedInstanceState: Bundle?)
}

那么子Activity只需要直接执行,不要关心创建和取消任务的逻辑.

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        launch {
            delay(5000)
            mHandler.sendMessage(Message.obtain().apply { what = SKIP_MAIN })
        }
    }

总结

带着问题去学习,思考下这个的作用是什么?可以解决实际项目中的哪些问题? 你也可以试试这个方法,尝试写下这四个demo,可能比枯燥看书来得更适用一些.

  1. 用协程替换new Thead执行
  2. 用协程的通道替换异步回调
  3. 用协程的cancel()解决一个异步任务造成内存泄露的case
  4. 用CoroutineScope接口 + Job剥离协程和业务代码的耦合