Kotlin Android 协程入门

Kotlin1.1的时候介绍了协程,一种写异步的非阻塞的新方法,使用协程我们要引入kotlinx.coroutines库。

集成步骤

1.确保工程配置为kotlin1.1或者更高版本
2.在build.gradle中添加如下代码

apply plugin: 'kotlin'

kotlin {
    experimental {
        coroutines 'enable'
    }
}

注:如果项目中引用了apply plugin: 'kotlin-android',就不用引用apply plugin: 'kotlin'
3.由于android中使用kotlinx.coroutines,我们将它的最新版本添加到依赖项中

dependencies {
    ...
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.21'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:0.21'
}

4.添加混淆
在混淆代码中,具有不同类型的字段可以具有相同的名称,并且AtomicReferenceFieldUpdater可能无法找到正确的字段。要避免在混淆期间按类型进行字段重载,请将其添加到配置中:

-keepclassmembernames class kotlinx.** {
    volatile ;
}

第一个协程

我们可以把协程认为是一个轻量的线程。像线程一样,协程同样可以并行运行,彼此等待并进行通信。协程和线程最大的不同就是,协程很轻量,我们可以创建上千个,并且只消耗很少的性能。线程从开始到保持都要耗费很多资源,而且对现在机器来说上千个线程是一个很严峻的挑战。我们可以通过launch{}方法开启一个协程,默认情况下协程运行在一个共享的线程池上。线程仍然可以运行在一个基于协程开发的程序中,一个线程可以运行很多个协程,所以我们将不再需要很多的线程。示例如下:

import kotlinx.coroutines.experimental.*

fun main(args: Array) {
    println("Start")

    // Start a coroutine
    launch {
        delay(1000)
        println("Hello")
    }

    Thread.sleep(2000) // wait for 2 seconds
    println("Stop")
}

运行结果:

 


说明:上述代码中我们开启了一个协程,一秒后打印hello。我们使用delay()方法,就像使用Thread.sleep()方法,但是delay方法会更好一些,它不会阻塞线程,它只是暂停协程本身。当协程正在等待时,线程返回到池中,并且当等待完成时,协程将在池中的空闲线程上恢复。
如果你想在main函数中使用非阻塞的delay方法,会发生一个编译错误Suspend functions are only allowed to be called from a coroutine or another suspend function,因为我们没有在协程中执行,我们将它包装在runBolcking{}中使用。runBlocking{}会启动协程并等待协程执行完成

 


import kotlinx.coroutines.experimental.*

fun main(args: Array) {
   println("Start")

   // Start a coroutine
   launch {
       delay(1000)
       println("Hello1")
   }

runBlocking {
    delay(2000)
    println("Hello2")
}

   Thread.sleep(2000) // wait for 2 seconds
   println("Stop")
}

 

对比线程和协程所消耗的资源

执行如下代码:

  fun main(args: Array) {
    createThreads()
    createCoroutines()
  }
  fun createCoroutines() {
        thread(start = true) {
            val c = AtomicInteger()
            for (i in 1..1_000_000)
                launch {
                    c.addAndGet(i)
                }
            println("${c.get()},coroutines")
        }
    }

    fun createThreads() {
        thread(start = true) {
            val c = AtomicInteger()
            for (i in 1..1_000_000)
                thread(start = true) {
                    c.addAndGet(i)
                }
            println("${c.get()},threads")
        }
    }

 

 

结果:

 

说明:从运行结果上分析,执行协程要比线程轻量很多,但是它打印出了一些任意数字,这是因为有一些协程在输出结果的时候还没有运行结束。让我们在下一个小节对这个问题进行修复。

Async:从协程中返回来一个值

还有一个开启协程的方式就是async{},和lauch()一样,但是它会返回一个Deferred实例,这个实例含有一个await()函数返回协程结果。
让我们再次创建百万个协程,保留它们的Deferred对象。现在就不需要原子计数器了,我们只需要返回协程中添加的数字。

fun main(args: Array) {
    createAsyncCoroutines()
  }
fun createAsyncCoroutines() {
    thread(start = true) {
        val deferred = (1..1_000_000).map { n ->
            async {
                n
            }
        }

        runBlocking {
            val sum = deferred.sumBy { it.await() }
            println("Sum: $sum")
        }
    }

}

注:await()不能在协程之外使用,因为它需要挂起直到计算完成,并且只有协程能过在非阻塞的方式挂起。现在输出结果是1784293664,因为所有协程都执行完毕了,运行结果如下:
结果:

 


我们一样可以证明协程是并行运行的。如果我们使用delay函数,给每个async加上一秒延时。程序运行结果并不是1_000_000秒(大概11.5天)

 

val deferred = (1..1_000_000).map { n ->
    async {
        delay(1000)
        n
    }
}

挂起函数

假如说我们要实现一个单独的方法,这个方法等待1秒并且返回一个数字

suspend fun workload(n: Int): Int {
        delay(1000)
        return n
    }

注:协程最大的价值就是他可以挂起而不阻塞线程。编译器通过suspend关键字实现这一功能
如果我们现在执行workload方法的话,编译器就会知道这个方法可以挂起,并且做相应准备。

async {
    workload(n)
}

我们的workload()函数可以从协程(或其他挂起函数)调用,但不能从协程外部调用。当然,我们上面使用的delay()和await()本身也被声明为suspend,这就是我们必须将它们放在runBlocking {},launch {}或async {}中的原因。

 

你可能感兴趣的:(Kotlin)