Kotlin之协程coroutine使用

Kotlin 已经成为Android开发的主打语言好些年了。但是我们在使用Kotlin时,不要仅限于使用Kotlin的语法糖。还有要使用其更有技术含量的API。比如这篇文章要讲的内容--协程

在介绍协程之前,先说一下 Android 里面开启异步功能(开启新线程)的方法,要么开启一个新的Thread,要么创建一个线程池,要么创建HandlerThread,要么使用 IntentService。其实这几种方法,归根结底,都是开启了一个新的线程。新开一个线程,会消耗比较大的内存。频繁的开启线程的话还会导致内存抖动。而且,开启的线程,要销毁和监听何时结束和执行结果,也是需要比较大的工作量。

比如下面,开启线程何时结束,何时有结果我们无法感知。而且开启一个线程所需要的内存也是很大。

Thread {
    // do something
}.start()

 

还有,当新开启线程执行功能时,需要线程的切换,线程A交出CPU执行权和线程B获取CPU执行权,这就涉及到线程之间的停止和开启,消耗内存太大了。

在这样的情况,这篇文章的主角 协程  出现了,协程既解决开启线程内存消耗大的问题,也在使用上提供销毁,何时结束等API,使用起来很方便...

 

首先在定义上说明一下,

线程是依赖于系统的,一个系统有多个线程。

协程是依赖于线程的,一个线程有多个协程。

 

也就是,协程在哪个线程开启,它就依赖于这个线程,这个线程就是他的宿主线程

 

首先新开启的 协程  为什么能大大节省内存呢?先从原理上讲,每一个协程,就相当于一个功能块(Code Block) ,这些功能块可以指定在哪一个线程执行。如果指定在主线程执行,那就把这个功能块放在主线程里面执行,如果指定在IO线程执行,那就放在IO线程里面去执行。如果多个功能块放在IO线程里面,那就一个等待一个排队去执行,如果某个协程需要睡眠,线程就会跳过该协程去执行下一个协程,直到该协程唤醒再执行。所以,协程在创建时,并没有新建起新的线程,它只是在共享线程池中取出。所以在内存消耗上很小。如下图,Thread A 添加了多个协程,一个挨着一个去执行...

 

Kotlin之协程coroutine使用_第1张图片

 

在Android 官方文档里标注,开启一个线程消耗内存远大于开启一个协程。

 

使用之前,先配置一下Gradle

dependencies {
    //                                        依赖协程核心库
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.1"
    //                                        依赖当前平台所对应的平台库
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.1"
}

 

话不多说,开启协程的方式有很多种,先上第一种

==========================================================================

                            第一种:  阻塞型 runBlocking(不推荐)

==========================================================================

这是一种不推荐的开启协程的方式,因为这种会阻塞线程。启动一个新的协程,并阻塞它的调用线程,直到里面的代码执行完毕。

其实在业务开发过程中,也没有多少这种需求需要阻塞线程的。

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_coroutines)
        val startTime = System.currentTimeMillis()
        Log.d(TAG, "开始获取网络数据...time: $startTime")
        runBlocking {
            val text = getText()
            Log.d(TAG, "网络数据...$text   time: ${System.currentTimeMillis() - startTime}  currentThread:${Thread.currentThread().name}")
        }
        Log.d(TAG, "执行完毕... time: ${System.currentTimeMillis() - startTime}")
    }


    private suspend fun getText(): String {
        Thread.sleep(3000)
        return "网络数据..."
    }

 

看日志分析:

很明显,在主线程回调,runBlocking 阻塞了线程。这是一种不推荐开启协程的方式

其实这种方式,和不开启协程是没有去区别,因为上述功能直接写在协程外面同样阻塞主线程。

当然,runBlocking  是可以指定线程,不过同样会阻塞其依赖的线程

 

===========================================================================

                              第二种:launch 和 async(不推荐)

===========================================================================

launch 和 async 是必须在 协程里面开启  才能编译过去,在协程之外开启,编译会报错

Kotlin之协程coroutine使用_第2张图片

 

只有写在协程里面才能编译过去

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_coroutines)
        runBlocking {
            Log.d(TAG, "runBlocking...  currentThread:${Thread.currentThread().name}")
            launch {
                Log.d(TAG, "launch...  currentThread:${Thread.currentThread().name}")
                val text = getText()
            }
        }
    }

 

看下日志

Kotlin之协程coroutine使用_第3张图片

分析日志 :

runBlocking 开启了一个协程,在该协程内部 又用 launch  开启了一个协程,由于launch  出来的协程并没有指定在哪个线程执行,它会默认在跟随启动它的那个协程。所以runBlocking  在主线程,launch  出来的协程也在主线程。

 

当然 launch  是可以指定线程,比如:

Dispatchers.Default            默认线程
Dispatchers.IO                    IO线程,网络请求,IO流读写
Dispatchers.Main               主线程
Dispatchers.Unconfined     不指定,默认当前线程

 

我们分别试着分别指定四种,会有什么效果:

 

Dispatchers.Default    默认线程

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_coroutines)
        runBlocking {
            Log.d(TAG, "runBlocking...  currentThread:${Thread.currentThread().name}")
            launch(Dispatchers.Default) {
                Log.d(TAG, "launch...  currentThread:${Thread.currentThread().name}")
                val text = getText()
            }
        }
    }

 

看下日志:

Kotlin之协程coroutine使用_第4张图片

Default   就是协程默认在共享线程池中的一个 名为:worker(非主线程)的线程中执行

 

Dispatchers.IO   IO线程,网络请求,IO流读写

        runBlocking {
            Log.d(TAG, "runBlocking...  currentThread:${Thread.currentThread().name}")
            launch(Dispatchers.IO) {
                Log.d(TAG, "launch...  currentThread:${Thread.currentThread().name}")
                val text = getText()
            }
        }

 

看下日志:

Kotlin之协程coroutine使用_第5张图片

 

IO   和 Default   一样, 就是协程默认在共享线程池中的一个 名为:worker(非主线程)的线程中执行

 

 

Dispatchers.Main               主线程

        runBlocking {
            Log.d(TAG, "runBlocking...  currentThread:${Thread.currentThread().name}")
            launch(Dispatchers.Main) {
                Log.d(TAG, "launch...  currentThread:${Thread.currentThread().name}")
                val text = getText()
            }
        }

 

其实这个不用看就知道了,,,,,

 

Dispatchers.Unconfined     不指定,默认当前线程

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_coroutines)
        runBlocking {
            Log.d(TAG, "runBlocking...  currentThread:${Thread.currentThread().name}")
            launch(Dispatchers.Unconfined) {
                Log.d(TAG, "launch...  currentThread:${Thread.currentThread().name}")
            }
        }
    }

 

看下日志

Kotlin之协程coroutine使用_第6张图片

其实这个和不指定线程是一样。

 

async launch  和差不多,就是比 launch多了一个返回值。返回值 写在闭包 {}最后一行即可,然后通过 await()获取结果

比如:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_coroutines)
        runBlocking {
            Log.d(TAG, "runBlocking...  currentThread:${Thread.currentThread().name}")
            val job = async(Dispatchers.IO) {
                Log.d(TAG, "launch...  currentThread:${Thread.currentThread().name}")
                23
            }
            val await = job.await()
            Log.d(TAG, "async... 运行结果:$await")
        }
    }

看日志:

Kotlin之协程coroutine使用_第7张图片

 

 

===========================================================================

  第三种:GlobalScope.launch 和 GlobalScope.async 全局单例(不推荐)

===========================================================================

这种开启协程的方式,不会阻塞线程

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_coroutines)
        val startTime = System.currentTimeMillis()
        Log.d(TAG, "开始获取网络数据...time: $startTime")
        GlobalScope.launch {
            val name = Thread.currentThread().name
            val text = getText()
            Log.d(TAG, "网络数据...$text   time: ${System.currentTimeMillis() - startTime}  currentThread: $name")
        }
        Log.d(TAG, "执行完毕... time: ${System.currentTimeMillis() - startTime} currentThread:${Thread.currentThread().name}")
    }

    private suspend fun getText(): String {
        Thread.sleep(3000)
        return "网络数据..."
    }

 

分析日志:

从日志分析得:通过 GlobalScope  开启单例协程,并没有阻塞线程,但是GlobalScope  开启单例协程相比于runBlocking ,只是没有阻塞线程,并没有多大优势,因为它是全局的单例,跟随application生命周期,不能取消。

 

GlobalScope.async  相比于  GlobalScope.launch 就是多了一个返回值功能,比如:

        runBlocking {
            val job = GlobalScope.async {
                Log.d(TAG, "async...  Thread:${Thread.currentThread().name}")
                1212
            }
            val await = job.await()
            Log.d(TAG, "async... 运行结果:$await")
        }

 

看日志:

Kotlin之协程coroutine使用_第8张图片

 

最后,对比GlobalScope.launch 和 launch 的却别,前者开启的是一个全局协程,生命周期和Application一样,后者会随着外部协程的销毁而销毁。

        /**
         * 外部协程 1
         * */
        runBlocking(Dispatchers.IO) {
            /**
             * 外部协程 2
             * */
            val job = launch {

                /**
                 * 内部协程 1   GlobalScope.launch
                 * */
                GlobalScope.launch {
                    Log.d(TAG, "GlobalScope.launch...  Thread:${Thread.currentThread().name}")
                    delay(1500)
                    val text = getText()
                    Log.d(TAG, "async...  获取结果:${text}")
                }

                /**
                 * 内部协程 1   launch
                 * */
                launch {
                    Log.d(TAG, "launch...  Thread:${Thread.currentThread().name}")
                    delay(1500)
                    val text = getText()
                    Log.d(TAG, "launch...  获取结果:${text}")
                }

            }
            delay(1000)
            job.cancel()
            Log.d(TAG, "外部协程取消...")
            delay(2000)
        }

 

看日志分析:

Kotlin之协程coroutine使用_第9张图片

最开始,GlobalScope.launch 和 launch 都有执行,但是外部外部协程取消后,GlobalScope.launch 没有受外部协程影响,依然执行完协程功能。 launch 随着外部协程一同销毁了。

 

===========================================================================

                              第四种:CoroutineScope (推荐)

===========================================================================

    private fun testScope() {
        val coroutineScope = CoroutineScope(Dispatchers.Main)
        coroutineScope.launch(Dispatchers.IO) {
            val text = getText()
            coroutineScope.launch(Dispatchers.Main) {
                tv1.text = text
            }
        }
    }

    private suspend fun getText(): String {
        Thread.sleep(3000)
        return "网络数据..."
    }

这一种推荐的。由CoroutineScope 创建, 由 Dispatchers 指定在(Dispatchers.Main )主线程。

 

 

========================================

                              suspend 关键词

========================================

    private suspend fun getText(): String {
        Thread.sleep(3000)
        return "网络数据..."
    }

 

suspend 关键词 修饰的方法,只能在协程里面调用,因为suspend 修饰的方法,被调用后,就代表这个协程被挂起。挂起的意思,就是协程里面的代码会切换到指定的线程去执行。等执行完后切回到当前线程继续执行。但是协程之外的代码,会继续执行,所以不会阻塞到当前线程。

========================================

  协程的取消1:CoroutineScope.cancel()

========================================

    private fun testScope() {
        val coroutineScope = CoroutineScope(Dispatchers.Main)
        coroutineScope.launch(Dispatchers.IO) {
            val text = getText()
            coroutineScope.launch(Dispatchers.Main) {
                tv1.text = text
            }
        }
        coroutineScope.cancel()
    }

    private suspend fun getText(): String {
        Thread.sleep(3000)
        return "网络数据..."
    }

cancel() 取消当前协程。

 

========================================

  协程的取消2:绑定类的生命周期 CoroutineScope by MainScope()

========================================

class MyCoroutineActivity : AppCompatActivity(), CoroutineScope by MainScope() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my_coroutine)
        runBlocking(Dispatchers.IO) {
            launch(Dispatchers.IO) {
            }
        }
    }

    override fun onDestroy() {
        /**
         *      取消 Activity 里面的所有协程
         * */
        cancel()
        super.onDestroy()
    }
}

CoroutineScope by MainScope()  实现了该接口的Activity,onDestory()销毁时  调用 cancel()即可取消在 该Activity创建的协程。

 

========================================

  协程的取消2:自定义 MainScope()

========================================

class MyCoroutineActivity : AppCompatActivity() {

    private var mainScope = MainScope()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my_coroutine)
        mainScope.launch {
        }
        mainScope.async {
            "返回的数据"
        }
    }

    override fun onDestroy() {
        /**
         *      取消 MainScope 创建所有协程
         * */
        mainScope.cancel()
        super.onDestroy()
    }
}

 

自定义 MainScope()  ,onDestory()销毁时  调用 MainScope().cancel() 即可取消协程。

 

以上代码亲测没问题,有问题请指出,谢谢

你可能感兴趣的:(android,多线程,java,kotlin)