Android—Kotiln进阶教程(一)

前言

在前面的文章中,已经讲解了Kotlin基础相关的知识点。从这一篇开始,将开始对Kotlin对应的协程进行详解!

话不多说,直接开始!

1、Kotlin基本使用

class MainActivity : AppCompatActivity() {
    
    

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val nameTextView = findViewById<TextView>(R.id.nameTextView)
        nameTextView.text = "Jack"
        val submitButton = findViewById<Button>(R.id.submitButton)
//        submitButton.setOnClickListener(View.OnClickListener {
    
    
//            Log.d("hqk","onClick")
//        })
        //上面那种有点java化,可以通过下面这种方式设置点击事件
        submitButton.setOnClickListener{
    
    
            Log.d("hqk","onClick")
        }
    }
}

这是一个非常简单MainActivity里面包含对应的控件获取以及对应的事件添加。

到这里,相信从本专栏第一篇看到这的读者已经具备了,将原有Java项目转Kotlin项目的能力。

当然这并不是Kotlin的全部,Kotlin的最大亮点就是协程!也是一大难啃的骨头!但是香啊!

2、Kotlin协程初探

什么是协程?

官方描述:协程实际上是⼀个轻量级的线程,可以挂起并稍后恢复。协程通过挂起函数⽀持:对这样的函数的调⽤可能会挂 起协程,并启动⼀个新的协程,我们通常使⽤匿名挂起函数(即挂起 lambda 表达式)。

协程通过将复杂性放入库来简化异步编程。程序的逻辑可以在协程中顺序地表达,而底层库会为我们解决其异步性。该库可以将用户代码的相关部分包装为回调、订阅相关事件、在不同线程(甚至不同机器)上调度执行,而代码则保持如同顺序执行一样简单。

准备工作

    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2'

需要在对应的Module里面注入对应的依赖!

2.1 示例一

fun main(){
    
    
    //后台运行的新的协程
    GlobalScope.launch {
    
    
        delay(1000)
        println("Kotlin!")
    }
    println("Hello,")
    //协程已在等待时主线程还在继续,阻塞主线程2秒钟来保证JVM存活
    Thread.sleep(2000)
}

运行效果

Hello, //这里 停顿一秒后才打印的下一句 Kotlin!
Kotlin!

这里我们看到,一开始就通过GlobalScope.launch{}创建了一个协程,然后通过delay挂起1了秒,最后再执行打印!

还是上面那句话:协程实际上是⼀个轻量级的线程,所以它还是一个线程,那使用java的thread试试?

import kotlinx.coroutines.*

fun main(){
    
    
    thread{
    
    
        Thread.sleep(1000)
        println("Kotlin!")
    }
    println("Hello,")
    //协程已在等待时主线程还在继续,阻塞主线程2秒钟来保证JVM存活
    Thread.sleep(2000)
}

运行效果

Hello, //这里 停顿一秒后才打印的下一句 Kotlin!
Kotlin!

这里我们看到,一开始就创建了thread线程,然后通过Thread.sleep(1000)阻塞了1,秒,接着再执行打印,最后我们看到执行结果和上面那种方式一致!

乍一看运行效果一样!

那么Thread.sleep(1000)delay(1000),它俩之间有何区别?

注意刚刚我在解释这两个方法的作用时,特意用了不同词:

  • delay(1000)为挂起,它不会造成线程阻塞,但是会 挂起 协程,并且只能在协程中使用。
  • Thread.sleep(1000)为阻塞,它会造成线程阻塞!

那么挂起与阻塞有何区别?(重点!重点!重点!)

  • 挂起:一般是主动的,由系统或程序发出,甚至于辅存中去。(不释放CPU,可能释放内存,放在外存)
  • 阻塞:一般是被动的,在抢占资源中得不到资源,被动的挂起在内存,等待某种资源或信号量(即有了资源)将他唤醒。(释放CPU,不释放内存)

用客套话解释:

  • 挂起:是车子把货卸了,但是车还在开,车有可能又在拖其他货物了
  • 阻塞:是货没卸,车子停了,在等红绿灯,变绿灯了就走

不过第⼀个⽰例在同⼀段代码中混⽤了 ⾮阻塞的 delay(……) 与 阻塞的 Thread.sleep(……) 。这容易记混哪个是阻塞的、哪个是⾮阻塞的。那试试使⽤ runBlocking 协程构建器来阻塞:

2.2 示例二

import kotlinx.coroutines.*

fun main(){
    
    
    GlobalScope.launch {
    
    
        delay(1000)
        println("Kotlin!")
    }
    println("Hello,")
    //这个表达式阻塞了主线程,我们延迟 2 秒来保证 JVM 的存活
    runBlocking {
    
    
        delay(2000L)
    }
}

运行效果

Hello, //这里 停顿一秒后才打印的下一句 Kotlin!
Kotlin!

结果是相似的,调用了 runBlocking{} 的主线程会一直阻塞直到 runBlocking 内部的协程执行完毕。

这个例子并不是很明显,只是为了过渡而使用!来看看下一个示例:

2.3 示例三

刚刚说了: 调用了 runBlocking{} 的主线程会一直阻塞直到 runBlocking 内部的协程执行完毕。

那么,将对应的协程包裹在runBlocking {}里面试试?:

fun main() = runBlocking<Unit> {
    
     //开始执行主协程
    GlobalScope.launch {
    
    
        delay(1000L)
        println("Kotlin!")
    }
    println("Hello,")
    delay(2000L)
}

运行效果

Hello, //这里 停顿一秒后才打印的下一句 Kotlin!
Kotlin!

这⾥的 runBlocking <Unit> { …… } 作为⽤来启动顶层主协程的适配器。我们显式指定了其返回类型 Unit,因为在 Kotlin 中 main 函数必须返回 Unit 类型。<Unit>可省略

当然这样写会造成时间浪费,为啥要挂起2秒,可不可以把delay(2000L)去掉!让协程执行完了自动结束

2.4 示例四

fun main() = runBlocking<Unit> {
    
     //开始执行主协程
    val job:Job = GlobalScope.launch {
    
    
        delay(1000L)
        println("Kotlin!")
    }
    println("Hello,")
    job.join() // 等待直到子协程执行结束
    //....
}

运行效果

Hello, //这里 停顿一秒后才打印的下一句 Kotlin!
Kotlin!

这里额外定义一个变量job:Job接收了对应的协程,随后通过job.join(),一直等到子协程结束!

但这还是有缺陷:

当使⽤ GlobalScope.launch 时,这里会创建⼀个顶层协 程。虽然它很轻量,但它运⾏时仍会消耗⼀些内存资源。如果忘记保持对新启动的协程的引⽤,它还会继续 运⾏。如果协程中的代码挂起了会怎么样(例如,错误地延迟了太⻓时间),或者说启动了太多的协程导致内存不⾜会怎样?通过job.join(),⼿动保持对所有已启动协程的引⽤也很容易出错。

所以,接着看下一个示例:

2.5 示例五

在⽰例中,使⽤ runBlocking 协程构建器将 main 函数转换为协程。包括 runBlocking 在内的 每个协程构建器都将 CoroutineScope 的实例添加到其代码块所在的作⽤域中。可以在这个作⽤域中启动协程⽽⽆需显式调用 join函数,因为外部协程(⽰例中的 runBlocking )直到在其作⽤域中启动的所有协程都执⾏完毕后才会结束。因此,可以将示例简化为:

fun main() = runBlocking<Unit> {
    
    // this : CoroutineScope
    launch {
    
    
        delay(1000L)
        println("Kotlin!")
    }
    println("Hello,")
}

运行效果

Hello, //这里 停顿一秒后才打印的下一句 Kotlin!
Kotlin!

除了由不同的构建器提供协程作用域之外,还可以使用 coroutineScope 构建器声明自己的作用域。

  • runBlockingcoroutineScope 可能看起来很类似,因为它们都会等待其协程体以及所有子协程结束。

  • 主要区别在于,runBlocking 方法会阻塞当前线程来等待,而 coroutineScope 只是挂起,会释放底层线程用于其他用途。

  • 由于存在这点差异,runBlocking 是常规函数,而coroutineScope是挂起函数。

带着分析看看下一个示例:

2.6 示例六

fun main() = runBlocking<Unit> {
    
    
    launch {
    
    
        delay(200L)
        println("Hello Kotlin")

    }
    // 创建一个协程作用域 
    coroutineScope {
    
    
        launch {
    
     // 内嵌 launch
            delay(500L)
            println("内嵌 launch")
        }
        delay(100L)
        // 这⼀⾏会在内嵌 launch 之前输出
        println("自定义作用域")
    }
    // 这⼀⾏在内嵌 launch 执⾏完毕后才输出
    println("runBlocking over")
}

运行效果

自定义作用域
Hello Kotlin
内嵌 launch
runBlocking over

注意,当等待内嵌 launch 时,紧挨“自定义作用域”消息之后,就会执⾏并输出“Hello Kotlin”——尽管 coroutineScope 尚未结束。

结束语

好了,本篇到这里差不多结束了。相信你对协程有了一定的基础认识。在下一篇中,将会讲解协程对应的取消超时时组合挂起函数。

有任何不对的地方以及建议,欢迎指正。

猜你喜欢

转载自blog.csdn.net/qq_30382601/article/details/121379910