Kotlin MVVM协程分享(一)

一、前置条件

  1. Kotlin中函数是顶层对象

    Kotlin中函数是一等公民。

​ Kotlin中的函数可以写在任何以.kt后缀的文件中。

Kotlin MVVM协程分享(一)_第1张图片

  1. 使用工具反编译Kotlin代码

​ (1)Tools->Kotlin->Show kotlin byteCode->Decompile 如下图所示:

Kotlin MVVM协程分享(一)_第2张图片

​ (2)点击Decompile后就可以得到对应的Java代码:

Kotlin MVVM协程分享(一)_第3张图片

​ Kotlin中的顶层函数其实就是将对应的函数包裹上一层类,并将函数转成对应类的静态方法,如myFun()等,而匿名函数对象mFun则会转成静态属性对象,类型则根据函数参数自动生成 Function0…22 等内置函数对象类。

kotlin byteCode中并没有具体显示内置函数对象的实现,但是在 Decompile 之前是可以看到对应的函数 ByteCode 的,如下图所示:

Kotlin MVVM协程分享(一)_第4张图片

  1. 扩展函数

    Kotlin中的重要特性之一:扩展一个类的新功能而无需继承该类或者使用像装饰者这样的设计模式。

​ 扩展函数使用方法:

Kotlin MVVM协程分享(一)_第5张图片

​ Person类中并没有定义run()方法,通过扩展可以让Person类对象具有该方法,同时可以在该方法中访问对应的非私有成员变量。Decode一下看看怎么实现的:

Kotlin MVVM协程分享(一)_第6张图片

​ 可以看到我们定义的Person类对象扩展函数反编译后并不是在对应的类中创建函数,而是在创建的文件类对象中创建静态方法run(Person $this$run),并且在无参函数中添加了一个Person类型的参数(Kotlin中很多语法如协程等都会在对应的函数中添加参数来实现某些功能),通过这个对象来访问对应的非私有成员变量及函数。

​ 为什么是非私有呢?因为Kotlin中的属性会默认生成get/set方法,但是如果声明val则只会生成get方法。而属性的可见修饰符会作用在对应的get/set方法上,私有方法其他类无法访问。

  1. 闭包

    在 Kotlin 中有这样一个语法糖:当函数的最后一个参数是 lambda 表达式时,可以将 lambda 写在括号外。这就是它的闭包原则。

二、协程

  1. 什么是协程

    「协程 Coroutines」源自 Simula 和 Modula-2 语言,这个术语早在 1958 年就被 Melvin Edward Conway 发明并用于构建汇编程序,说明协程是一种编程思想,并不局限于特定的语言。

    Kotlin 官方文档说「本质上,协程是轻量级的线程」

协程和线程的关系:

  • 我们所有的代码都是跑在线程中的,而线程是跑在进程中的。

  • 协程没有直接和操作系统关联,但它不是空中楼阁,它也是跑在线程中的,可以是单线程,也可以是多线程。

  • 单线程中的协程总的执行时间并不会比不用协程少

  • Android 系统上,如果在主线程进行网络请求,会抛出 NetworkOnMainThreadException,对于在主线程上的协程也不例外,这种场景使用协程还是要切线程的。

  1. 协程的优势

​ 怎么理解第三点呢?看下demo代码:

fun main() {
    val t1 = measureTimeMillis { funWithThread() }
    println("t1:$t1")
    val t2 = measureTimeMillis { funWithCoroutineThread() }
    println("t2:$t2")
    val t3 = measureTimeMillis { funWithCoroutineThread2() }
    println("t3:$t3")
    val t4 = measureTimeMillis { funWithCoroutineThread3() }
    println("t4:$t4")
    val t5 = measureTimeMillis { funWithCoroutine() }
    println("t5:$t5")
}

fun funWithThread() {
    for (i in 0..10) {
        println("funWithThread...$i, ${Thread.currentThread()}")
        try {
            Thread.sleep(100)// 模拟耗时任务
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}

fun funWithCoroutineThread2() = runBlocking {
    for (i in 0..10) {
        println("funWithCoroutineThread2...$i, ${Thread.currentThread()} - ${currentCoroutineContext()}")
        try {
            Thread.sleep(100)// 模拟耗时任务
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}

fun funWithCoroutineThread3() = runBlocking {
    for (i in 0..10) {
        println("funWithCoroutineThread2...$i, ${Thread.currentThread()} - ${currentCoroutineContext()}")
        delay(100)// 模拟耗时任务
    }
}

fun funWithCoroutineThread() = runBlocking {
    for (i in 0..10) {
        launch {
            println("funWithCoroutineThread...$i, ${Thread.currentThread()} - ${currentCoroutineContext()}")
            try {
                Thread.sleep(100)// 模拟耗时任务
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }
}

fun funWithCoroutine() = runBlocking {
    var time = 0L
    for (i in 0..10) {
        launch {
            val t = measureTimeMillis {
                println("funWithCoroutineThread...$i, ${Thread.currentThread()} - ${currentCoroutineContext()}")
                delay(100)// 模拟耗时任务
            }
            time += t
            println("funWithCoroutineThread...$i,t:$t ,time:$time")
        }
    }
    return@runBlocking time
}

​ 其中的runBlocking{}是用于测试样例中创建协程作用域的内置顶层函数,它的作用是等待内部正在执行协程执行完成后结束。launch{}CoroutineScope的扩展函数,用于启动一个子协程任务,默认在线程空闲后执行。

Kotlin MVVM协程分享(一)_第7张图片

​ 运行上面代码后结果如下:

funWithThread...0, Thread[main,5,main]
funWithThread...1, Thread[main,5,main]
funWithThread...2, Thread[main,5,main]
funWithThread...3, Thread[main,5,main]
funWithThread...4, Thread[main,5,main]
funWithThread...5, Thread[main,5,main]
funWithThread...6, Thread[main,5,main]
funWithThread...7, Thread[main,5,main]
funWithThread...8, Thread[main,5,main]
funWithThread...9, Thread[main,5,main]
funWithThread...10, Thread[main,5,main]
t1:1217
funWithCoroutineThread...0, Thread[main,5,main] - [StandaloneCoroutine{Active}@15327b79, BlockingEventLoop@4f2410ac]
funWithCoroutineThread...1, Thread[main,5,main] - [StandaloneCoroutine{Active}@722c41f4, BlockingEventLoop@4f2410ac]
funWithCoroutineThread...2, Thread[main,5,main] - [StandaloneCoroutine{Active}@5b80350b, BlockingEventLoop@4f2410ac]
funWithCoroutineThread...3, Thread[main,5,main] - [StandaloneCoroutine{Active}@5d6f64b1, BlockingEventLoop@4f2410ac]
funWithCoroutineThread...4, Thread[main,5,main] - [StandaloneCoroutine{Active}@32a1bec0, BlockingEventLoop@4f2410ac]
funWithCoroutineThread...5, Thread[main,5,main] - [StandaloneCoroutine{Active}@22927a81, BlockingEventLoop@4f2410ac]
funWithCoroutineThread...6, Thread[main,5,main] - [StandaloneCoroutine{Active}@78e03bb5, BlockingEventLoop@4f2410ac]
funWithCoroutineThread...7, Thread[main,5,main] - [StandaloneCoroutine{Active}@5e8c92f4, BlockingEventLoop@4f2410ac]
funWithCoroutineThread...8, Thread[main,5,main] - [StandaloneCoroutine{Active}@61e4705b, BlockingEventLoop@4f2410ac]
funWithCoroutineThread...9, Thread[main,5,main] - [StandaloneCoroutine{Active}@50134894, BlockingEventLoop@4f2410ac]
funWithCoroutineThread...10, Thread[main,5,main] - [StandaloneCoroutine{Active}@2957fcb0, BlockingEventLoop@4f2410ac]
t2:1268
funWithCoroutineThread2...0, Thread[main,5,main] - [BlockingCoroutine{Active}@1b4fb997, BlockingEventLoop@deb6432]
funWithCoroutineThread2...1, Thread[main,5,main] - [BlockingCoroutine{Active}@1b4fb997, BlockingEventLoop@deb6432]
funWithCoroutineThread2...2, Thread[main,5,main] - [BlockingCoroutine{Active}@1b4fb997, BlockingEventLoop@deb6432]
funWithCoroutineThread2...3, Thread[main,5,main] - [BlockingCoroutine{Active}@1b4fb997, BlockingEventLoop@deb6432]
funWithCoroutineThread2...4, Thread[main,5,main] - [BlockingCoroutine{Active}@1b4fb997, BlockingEventLoop@deb6432]
funWithCoroutineThread2...5, Thread[main,5,main] - [BlockingCoroutine{Active}@1b4fb997, BlockingEventLoop@deb6432]
funWithCoroutineThread2...6, Thread[main,5,main] - [BlockingCoroutine{Active}@1b4fb997, BlockingEventLoop@deb6432]
funWithCoroutineThread2...7, Thread[main,5,main] - [BlockingCoroutine{Active}@1b4fb997, BlockingEventLoop@deb6432]
funWithCoroutineThread2...8, Thread[main,5,main] - [BlockingCoroutine{Active}@1b4fb997, BlockingEventLoop@deb6432]
funWithCoroutineThread2...9, Thread[main,5,main] - [BlockingCoroutine{Active}@1b4fb997, BlockingEventLoop@deb6432]
funWithCoroutineThread2...10, Thread[main,5,main] - [BlockingCoroutine{Active}@1b4fb997, BlockingEventLoop@deb6432]
t3:1220
funWithCoroutineThread2...0, Thread[main,5,main] - [BlockingCoroutine{Active}@28ba21f3, BlockingEventLoop@694f9431]
funWithCoroutineThread2...1, Thread[main,5,main] - [BlockingCoroutine{Active}@28ba21f3, BlockingEventLoop@694f9431]
funWithCoroutineThread2...2, Thread[main,5,main] - [BlockingCoroutine{Active}@28ba21f3, BlockingEventLoop@694f9431]
funWithCoroutineThread2...3, Thread[main,5,main] - [BlockingCoroutine{Active}@28ba21f3, BlockingEventLoop@694f9431]
funWithCoroutineThread2...4, Thread[main,5,main] - [BlockingCoroutine{Active}@28ba21f3, BlockingEventLoop@694f9431]
funWithCoroutineThread2...5, Thread[main,5,main] - [BlockingCoroutine{Active}@28ba21f3, BlockingEventLoop@694f9431]
funWithCoroutineThread2...6, Thread[main,5,main] - [BlockingCoroutine{Active}@28ba21f3, BlockingEventLoop@694f9431]
funWithCoroutineThread2...7, Thread[main,5,main] - [BlockingCoroutine{Active}@28ba21f3, BlockingEventLoop@694f9431]
funWithCoroutineThread2...8, Thread[main,5,main] - [BlockingCoroutine{Active}@28ba21f3, BlockingEventLoop@694f9431]
funWithCoroutineThread2...9, Thread[main,5,main] - [BlockingCoroutine{Active}@28ba21f3, BlockingEventLoop@694f9431]
funWithCoroutineThread2...10, Thread[main,5,main] - [BlockingCoroutine{Active}@28ba21f3, BlockingEventLoop@694f9431]
t4:1219
funWithCoroutineThread...0, Thread[main,5,main] - [StandaloneCoroutine{Active}@1c655221, BlockingEventLoop@58d25a40]
funWithCoroutineThread...1, Thread[main,5,main] - [StandaloneCoroutine{Active}@1b701da1, BlockingEventLoop@58d25a40]
funWithCoroutineThread...2, Thread[main,5,main] - [StandaloneCoroutine{Active}@726f3b58, BlockingEventLoop@58d25a40]
funWithCoroutineThread...3, Thread[main,5,main] - [StandaloneCoroutine{Active}@442d9b6e, BlockingEventLoop@58d25a40]
funWithCoroutineThread...4, Thread[main,5,main] - [StandaloneCoroutine{Active}@ee7d9f1, BlockingEventLoop@58d25a40]
funWithCoroutineThread...5, Thread[main,5,main] - [StandaloneCoroutine{Active}@15615099, BlockingEventLoop@58d25a40]
funWithCoroutineThread...6, Thread[main,5,main] - [StandaloneCoroutine{Active}@1edf1c96, BlockingEventLoop@58d25a40]
funWithCoroutineThread...7, Thread[main,5,main] - [StandaloneCoroutine{Active}@368102c8, BlockingEventLoop@58d25a40]
funWithCoroutineThread...8, Thread[main,5,main] - [StandaloneCoroutine{Active}@6996db8, BlockingEventLoop@58d25a40]
funWithCoroutineThread...9, Thread[main,5,main] - [StandaloneCoroutine{Active}@1963006a, BlockingEventLoop@58d25a40]
funWithCoroutineThread...10, Thread[main,5,main] - [StandaloneCoroutine{Active}@7fbe847c, BlockingEventLoop@58d25a40]
funWithCoroutineThread...0,t:108 ,time:108
funWithCoroutineThread...1,t:108 ,time:216
funWithCoroutineThread...2,t:108 ,time:324
funWithCoroutineThread...3,t:108 ,time:432
funWithCoroutineThread...4,t:108 ,time:540
funWithCoroutineThread...5,t:108 ,time:648
funWithCoroutineThread...6,t:107 ,time:755
funWithCoroutineThread...7,t:107 ,time:862
funWithCoroutineThread...8,t:109 ,time:971
funWithCoroutineThread...9,t:109 ,time:1080
funWithCoroutineThread...10,t:109 ,time:1189
t5:112

​ 从打印结果可以看出,上面的任务都是执行在Thread[main,5,main]这条线程上的,启动协程的都拥有同一个上下文环境(协程上下文环境默认是向下传递的)。

​ 观察耗时可以发现t1~t4耗时相差不大(其中t1没有启动协程反而耗时是最少的),但是t5的耗时明显的大幅度减少!这其实就是协程的并发处理能力!在单线程上实现多任务并发处理效果,最大化线程的执行效率,这也是协程的优势之一!

​ 那么,回到问题上,上面的第三点怎么理解呢?其实全部启动的协程执行的总时间已经打印出来了,和之前的几个差别不大(没有计算协程启动时间)!

协程的另一大优势:消除回调地狱

​ 看下对比代码(模拟用户信息请求):

​ Kotlin中可能会这么写:

coroutineScope.launch(Dispatchers.Main) {   // 在主线程开启协程
    val user = api.getUser() // IO 线程执行网络请求
    nameTv.text = user.name  // 主线程更新 UI
}

​ Java中可能会这么写:

api.getUser(new Callback<User>() {
    @Override
    public void success(User user) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                nameTv.setText(user.name);
            }
        })
    }
    
    @Override
    public void failure(Exception e) {
        ...
    }
});
  1. 协程的挂起与恢复

什么是挂起?挂起,就是一个稍后会被自动切回来的线程调度操作。

​ 在Kotlin中使用关键字suspend将函数声明成挂起函数,挂起怎么理解呢?

​ 首先,挂起的对象是协程,而不是线程。下图显示了使用 launch{}启动子协程任务的挂起执行流程:

Kotlin MVVM协程分享(一)_第8张图片

​ 那么为什么挂起函数只能在协程环境中使用呢?

​ 先随便写一个挂起函数看看:

image-20220927153752244

​ 在Decode一下看看编译器干了什么!

Kotlin MVVM协程分享(一)_第9张图片

​ 可以看到,反编译后的函数多了一个参数Continuation $completion,这个参数是什么呢?

Kotlin MVVM协程分享(一)_第10张图片

​ 这个参数其实就是一个接口,包含了一个协程上下文环境和一个方法 resumeWith(result: Result),这个方法在协程执行结束后用于将结果恢复到原线程。

​ 一般流程如下图:
Kotlin MVVM协程分享(一)_第11张图片

​ 简单写个测试Demo:

Kotlin MVVM协程分享(一)_第12张图片

​ 执行后的打印结果如下:

image-20220927172231394

​ 其中的withContextlaunch的区别是前者会挂起后续代码,等待执行结果返回后继续执行后续代码。效果等同于async

Kotlin MVVM协程分享(一)_第13张图片

image-20220927172755515

​ 上述三个函数是协程中使用频率最高的,暂时就介绍这三个。

  1. 流的使用和分析

    挂起函数可以异步的返回单个值,但是该如何异步返回多个计算好的值呢?这正是 Kotlin 流(Flow)的用武之地。

​ 一般流的使用方法:

Kotlin MVVM协程分享(一)_第14张图片

注:流的收集闭包其实是FlowCollector类的匿名对象

​ 那么,流是如何实现上游单独创建并将结果发送到下游收集处呢?

​ 先看流的上游创建方法:

Kotlin MVVM协程分享(一)_第15张图片

​ 很简单,只是创建了一个SafeFlow对象,实现了AbstractFlow,重点看一下collectSafely中,流的上游创建闭包对象是一个suspend FlowCollector.() -> Unit挂起函数对象类型,属于FlowCollector类的扩展函数!而collectSafely的入参就是FlowCollector类的对象!collectSafely中就只是调用了入参对象的block()扩展函数!

​ 现在看下AbstractFlow

Kotlin MVVM协程分享(一)_第16张图片

​ 在调用floww.collect的时候,将collect闭包对象包装成一个新的SafeCollector对象,并传给collectSafely(collector: FlowCollector)函数调用。因此,最终的调用链是调用了safeCollector.block()

​ 接着看下FlowCollector

Kotlin MVVM协程分享(一)_第17张图片

​ 很简单就是一个接口,有一个emit(value: T)挂起函数,这个函数是流的上游核心函数,数据通过这个函数发送到下游收集处。

​ 而上面的SafeCollector就是FlowCollector的子类:

Kotlin MVVM协程分享(一)_第18张图片

​ 其中的collector就是外面传进来的收集者闭包对象!

​ 详细看看它的emit(value: T)函数:

Kotlin MVVM协程分享(一)_第19张图片

​ 调用了内部的emit(uCont: Continuation, value: T)函数,并进一步调用了emitFun这个顶层函数对象函数。这个函数是将FlowCollector对象中的emit函数强转成内置函数Function3, Any?, Continuation, Any?>,其中的泛型第一个为调用者类型,最后一个为返回值类型,中间为2个参数类型。

image-20220927175946937

emitFun这个函数调用的对象传的就是collector对象,也就是说最后将value值传入了收集者中的emit函数中,实现了数据的发送!

三、项目代码实例

​ 上述协程及流相关内容在项目中均有实战使用,以CommonViewModel.requestJNINet为例简单过一遍:

Kotlin MVVM协程分享(一)_第20张图片

CommonViewModel中创建了扩展函数requestJNINet,其中使用了ViewMode自带的协程域viewModelScope,具体的IO请求如下:

Kotlin MVVM协程分享(一)_第21张图片

​ 在doJNITimeOutRequest中执行构建流和收集启动流操作:

Kotlin MVVM协程分享(一)_第22张图片

​ 构建流比较简单:

image-20220927185310683

​ 收集启动流通过withxxx挂起函数挂起并等待流结果返回:

Kotlin MVVM协程分享(一)_第23张图片

​ 至此整个requestJNINet核心流程就走完了。

你可能感兴趣的:(Android开发那些事,kotlin,android,开发语言)