Allen 专栏-移动互联网

Kotlin 协程官网翻译 有更新!

你的第一个协程

fun main(args: Array<String>) {
    launch { //在后台启动新的协程并继续 
        delay(1000L) //非阻塞延迟1秒(默认时间单位为ms) 
        println("World!") //延迟后打印
    }
    println("Hello,") //主线程继续,而协程延迟 
    Thread.sleep(2000L)//阻塞主线程2秒以保持JVM活动 
}

输出结果

Hello,
World!

从本质上讲,协同程序是轻量级的线程。它们是与发布 协同程序构建器一起启动的。您可以实现相同的结果替换 launch { … }thread { … } ,并 delay(…)Thread.sleep(…) 。尝试一下。

如果以替换launch为开头thread,则编译器会产生以下错误:

Error: Kotlin: Suspend functions are only allowed to be called from a coroutine or another suspend function

这是因为delay是一个特殊的挂起函数,它不会阻塞一个线程,但会挂起 协同程序,它只能从协程中使用。

桥接阻塞和非阻塞世界

第一个示例在同一代码中混合非阻塞 delay(…)和阻塞 Thread.sleep(…)。很容易迷失哪一个阻塞而另一个阻塞。让我们明确说明使用runBlocking coroutine builder进行阻塞:

fun main(args: Array<String>) { 
    launch { // launch new coroutine in background and continue
        delay(1000L)
        println("World!")
    }
    println("Hello,") // main thread continues here immediately
    runBlocking {     // but this expression blocks the main thread
        delay(2000L)  // ... while we delay for 2 seconds to keep JVM alive
    } 
}

结果是相同的,但此代码仅使用非阻塞延迟。主线程,调用runBlocking,块,直到协程内runBlocking完成。

这个例子也可以用更惯用的方式重写,runBlocking用来包装main函数的执行:

等待工作
在另一个协程正在工作时延迟一段时间并不是一个好方法。让我们明确等待(以非阻塞方式),直到我们启动的后台作业完成:

fun main(args: Array<String>) = runBlocking<Unit> {
    val job = launch { // launch new coroutine and keep a reference to its Job
        delay(1000L)
        println("World!")
    }
    println("Hello,")
    job.join() // wait until child coroutine completes
}

提取函数重构

让我们将代码块提取launch { … }到一个单独的函数中。当您对此代码执行“提取功能”重构时,您将获得带有suspend修饰符的新功能。这是你的第一个暂停功能。挂起函数可以在协程内部使用,就像常规函数一样,但它们的附加功能是它们可以反过来使用其他挂起函数(如delay本示例中所示)来暂停协程的执行。

fun main(args: Array<String>) = runBlocking<Unit> {
    val job = launch { doWorld() }
    println("Hello,")
    job.join()
}

// this is your first suspending function
suspend fun doWorld() {
    delay(1000L)
    println("World!")
}

协同程序足够轻量级

fun main(args: Array<String>) = runBlocking<Unit> {
    val jobs = List(100_000) {
        // launch a lot of coroutines and list their jobs
        launch {
            delay(1000L)
            print(".")
        }
    }
    jobs.forEach { it.join() } // wait for all jobs to complete
}

它启动了100K协同程序,一秒钟之后,每个协同程序都打印出一个点。现在,尝试使用线程。会发生什么?(很可能你的代码会产生某种内存不足的错误)

协同程序就像守护程序线程

下面的代码启动一个长时间运行的协同程序,每秒打印“我正在睡觉”两次,然后在一段延迟后从main函数返回:

fun main(args: Array<String>) = runBlocking<Unit> {
    launch {
        repeat(1000) { i ->
            println("I'm sleeping $i ...")
            delay(500L)
        }
    }
    delay(1300L) // just quit after delay
}

您可以运行并看到它打印三行并终止:

I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...

活动协同程序不会使进程保持活动状态。它们就像守护程序线程。

取消和超时

在小应用程序中,从“main”方法返回可能听起来像是一个好主意,以便隐式终止所有协同程序。在较大的长期运行的应用程序中,您需要更精细的控制。在推出函数返回一个作业,可用于取消运行协程:

fun main(args: Array<String>) = runBlocking<Unit> {
    val job = launch {
        repeat(1000) { i ->
            println("I'm sleeping $i ...")
            delay(500L)
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancel() // cancels the job
    job.join() // waits for job's completion 
    println("main: Now I can quit.")
}

输出如下

I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
main: I'm tired of waiting!
main: Now I can quit.

主调用后job.cancel,我们看不到其他协同程序的任何输出,因为它已被取消。还有一个Job扩展函数cancelAndJoin ,它结合了取消和连接调用。

取消是合作的

协同取消是合作的。协程代码必须合作才能取消。所有挂起函数kotlinx.coroutines都是可取消的。他们检查coroutine的取消并在取消时抛出CancellationException。但是,如果协程正在计算中并且未检查取消,则无法取消它,如下例所示:

fun main(args: Array<String>) = runBlocking<Unit> {
    val startTime = System.currentTimeMillis()
    val job = launch {
        var nextPrintTime = startTime
        var i = 0
        while (i < 5) { // computation loop, just wastes CPU
            // print a message twice a second
            if (System.currentTimeMillis() >= nextPrintTime) {
                println("I'm sleeping ${i++} ...")
                nextPrintTime += 500L
            }
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // cancels the job and waits for its completion
    println("main: Now I can quit.")
}

运行它以查看它继续打印“我正在睡觉”,即使在取消之后,直到作业在五次迭代后自行完成。
输出结果

I'm sleep 0
I'm sleep 1
I'm sleep 2
main I;m tried of waiting
I'm sleep 3
I'm sleep 4
main Now I can quit

使计算代码可取消

有两种方法可以使计算代码可以取消。第一个是定期调用检查取消的挂起功能。有一个收益率的功能是实现这一目的的好选择。另一个是明确检查取消状态。让我们尝试后一种方法。

fun main(args: Array<String>) = runBlocking<Unit> {
    val startTime = System.currentTimeMillis()
    val job = launch {
        var nextPrintTime = startTime
        var i = 0
        while (isActive) { // cancellable computation loop
            // print a message twice a second
            if (System.currentTimeMillis() >= nextPrintTime) {
                println("I'm sleeping ${i++} ...")
                nextPrintTime += 500L
            }
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // cancels the job and waits for its completion
    println("main: Now I can quit.")
}

如您所见,现在此循环已取消。isActive是通过CoroutineScope对象在协同程序代码中可用的属性。

最后关闭资源

可取消的挂起函数会在取消时抛出CancellationException,这可以通过所有常规方式处理。例如,当取消协程时,try {…} finally {…}表达式和Kotlin use函数通常会执行其终结操作:

fun main(args: Array<String>) = runBlocking<Unit> {
   val job = launch {
        try {
            repeat(1000) { i ->
                println("I'm sleeping $i ...")
                delay(500L)
            }
        } finally {
            println("I'm running finally")
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // cancels the job and waits for its completion
    println("main: Now I can quit.")
	

无论加入和cancelAndJoin等待所有完成动作来完成的,所以上面的例子产生下面的输出:

I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
main: I'm tired of waiting!
I'm running finally
main: Now I can quit.

运行不可取消的块

finally在前一个示例的块中尝试使用挂起函数将导致CancellationException,因为运行此代码的协程将 被取消。通常,这不是问题,因为所有表现良好的关闭操作(关闭文件,取消作业或关闭任何类型的通信通道)通常都是非阻塞的,并且不涉及任何挂起功能。但是,在极少数情况下,当您需要挂起已取消的协同程序时,可以withContext(NonCancellable) {…}使用withContext函数和NonCancellable上下文包装相应的代码, 如下例所示:

fun main(args: Array<String>) = runBlocking<Unit> {
    val job = launch {
        try {
            repeat(1000) { i ->
                println("I'm sleeping $i ...")
                delay(500L)
            }
        } finally {
            withContext(NonCancellable) {
                println("I'm running finally")
                delay(1000L)
                println("And I've just delayed for 1 sec because I'm non-cancellable")
            }
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // cancels the job and waits for its completion
    println("main: Now I can quit.")
}

超时退出
在实践中取消协程执行的最明显的原因是因为它的执行时间超过了一些超时。虽然您可以手动跟踪对相应作业的引用并启动单独的协同程序以在延迟后取消跟踪的协程,但是有一个准备好使用withTimeout函数执行此操作。请看以下示例:

fun main(args: Array<String>) = runBlocking<Unit> {
    withTimeout(1300L) {
        repeat(1000) { i ->
            println("I'm sleeping $i ...")
            delay(500L)
        }
    }
}
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
Exception in thread "main" kotlinx.coroutines.experimental.TimeoutCancellationException: Timed out waiting for 1300 MILLISECONDS

该TimeoutCancellationException由抛出withTimeout是的子类CancellationException。我们之前没有看到它的堆栈跟踪打印在控制台上。这是因为在取消的协程中CancellationException被认为是协程完成的正常原因。但是,在这个例子中我们withTimeout在main函数内部使用了。

因为取消只是一个例外,所有资源都将以通常的方式关闭。您可以在超时包裹代码try {…} catch (e: TimeoutCancellationException) {…}块,如果你需要专门做一些额外的行动在任何类型的超时或使用withTimeoutOrNull功能类似于withTimeout,但返回null的超时,而不是抛出一个异常:


fun main(args: Array<String>) = runBlocking<Unit> {
    val result = withTimeoutOrNull(1300L) {
        repeat(1000) { i ->
            println("I'm sleeping $i ...")
            delay(500L)
        }
        "Done" // will get cancelled before it produces this result
    }
    println("Result is $result")
}

运行此代码时不再有异常:

I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
Result is null

撰写暂停功能

默认顺序

假设我们在其他地方定义了两个挂起函数,它们可以像某种远程服务调用或计算一样有用。我们只是假装它们很有用,但实际上每个只是为了这个例子的目的而延迟一秒:

suspend fun doSomethingUsefulOne(): Int {
    delay(1000L) // pretend we are doing something useful here
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000L) // pretend we are doing something useful here, too
    return 29
}

如果需要按顺序调用它们,我们该怎么做- 首先doSomethingUsefulOne 然后 doSomethingUsefulTwo计算结果的总和?实际上,如果我们使用第一个函数的结果来决定是否需要调用第二个函数或决定如何调用它,我们就会这样做。

我们只使用正常的顺序调用,因为协程中的代码与常规代码中的代码一样,默认是顺序的。以下示例通过测量执行两个挂起函数所需的总时间来演示它:

fun main(args: Array<String>) = runBlocking<Unit> {
    val time = measureTimeMillis {
        val one = doSomethingUsefulOne()
        val two = doSomethingUsefulTwo()
        println("The answer is ${one + two}")
    }
    println("Completed in $time ms")
}

它产生这样的东西:

The answer is 42
Completed in 2017 ms

并发使用异步

如果在调用doSomethingUsefulOne和之间没有依赖关系,doSomethingUsefulTwo并且我们希望通过同时执行两者来更快地得到答案,该怎么办?这是异步来帮助的地方。

从概念上讲,异步就像启动一样。它启动一个单独的协程,这是一个轻量级的线程,与所有其他协同程序同时工作。不同之处在于launch返回一个Job并且不携带任何结果值,同时async返回Deferred - 一个轻量级的非阻塞未来,表示稍后提供结果的承诺。您可以使用.await()延迟值来获取其最终结果,但Deferred也是a Job,因此您可以根据需要取消它。

fun main(args: Array<String>) = runBlocking<Unit> {
    val time = measureTimeMillis {
        val one = async { doSomethingUsefulOne() }
        val two = async { doSomethingUsefulTwo() }
        println("The answer is ${one.await() + two.await()}")
    }
    println("Completed in $time ms")
}

它产生这样的东西:

The answer is 42
Completed in 1017 ms

这是两倍的速度,因为我们同时执行了两个协同程序。注意,与协同程序的并发性始终是显式的。

懒惰地开始异步

使用值为CoroutineStart.LAZY的可选参数进行异步时有一个惰性选项。它仅在某些等待需要其结果或调用启动函数时才启动协同程序 。运行以下示例,该示例仅与此前一个示例不同:start

fun main(args: Array<String>) = runBlocking<Unit> {
    val time = measureTimeMillis {
        val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
        val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
        println("The answer is ${one.await() + two.await()}")
    }
    println("Completed in $time ms")
}

它产生这样的东西:

The answer is 42
Completed in 2017 ms

所以,我们回到顺序执行,因为我们首先启动并等待one,然后启动并等待two。它不是懒惰的预期用例。lazy在计算值涉及暂停函数的情况下,它被设计为标准函数的替代。

异步风格的功能

我们可以定义使用异步协同生成器调用doSomethingUsefulOne和doSomethingUsefulTwo 异步调用的异步样式函数。使用“Async”后缀命名此类函数是一种很好的方式,以突出显示它们只启动异步计算并且需要使用结果延迟值来获取结果的事实。

// somethingUsefulOneAsync的结果类型是Deferred <Int> 
fun  somethingUsefulOneAsync() = async {
    doSomethingUsefulOne()
}

// somethingUsefulTwoAsync的结果类型是Deferred <Int> 
fun  somethingUsefulTwoAsync() = async {
    doSomethingUsefulTwo()
}

注意,这些xxxAsync功能不是 暂停功能。它们可以在任何地方使用。但是,它们的使用总是意味着它们的动作与调用代码的异步(这里意味着并发)。

以下示例显示了它们在协同程序之外的用法:

// note, that we don't have `runBlocking` to the right of `main` in this example
fun main(args: Array<String>) {
    val time = measureTimeMillis {
        // we can initiate async actions outside of a coroutine
        val one = somethingUsefulOneAsync()
        val two = somethingUsefulTwoAsync()
        // but waiting for a result must involve either suspending or blocking.
        // here we use `runBlocking { ... }` to block the main thread while waiting for the result
        runBlocking {
            println("The answer is ${one.await() + two.await()}")
        }
    }
    println("Completed in $time ms")
}

协同上下文和调度员

协同程序总是在某些上下文中执行,该上下文由 在Kotlin标准库中定义的CoroutineContext类型的值表示 。

协程上下文是一组各种元素。主要元素是我们之前见过的协同工作及其调度程序,本节将对其进行介绍。

调度员和线程

协程上下文包括一个协程调度程序(请参阅CoroutineDispatcher),它确定相应的协程用于执行的线程。协程调度程序可以将协程执行限制在特定线程,将其分派给线程池,或让它无限制地运行。

所有协同构建器(如launch和async)都接受一个可选的 CoroutineContext 参数,该参数可用于显式指定新协程和其他上下文元素的调度程序。

请尝试以下示例:

fun main(args: Array<String>) = runBlocking<Unit> {
    val jobs = arrayListOf<Job>()
    jobs += launch(Unconfined) { // not confined -- will work with main thread
        println("      'Unconfined': I'm working in thread ${Thread.currentThread().name}")
    }
    jobs += launch(coroutineContext) { // context of the parent, runBlocking coroutine
        println("'coroutineContext': I'm working in thread ${Thread.currentThread().name}")
    }
    jobs += launch(CommonPool) { // will get dispatched to ForkJoinPool.commonPool (or equivalent)
        println("      'CommonPool': I'm working in thread ${Thread.currentThread().name}")
    }
    jobs += launch(newSingleThreadContext("MyOwnThread")) { // will get its own new thread
        println("          'newSTC': I'm working in thread ${Thread.currentThread().name}")
    }
    jobs.forEach { it.join() }
}

它产生以下输出(可能以不同的顺序):

      'Unconfined': I'm working in thread main
      'CommonPool': I'm working in thread ForkJoinPool.commonPool-worker-1
          'newSTC': I'm working in thread MyOwnThread
'coroutineContext': I'm working in thread main

我们在前面部分中使用的默认调度程序由DefaultDispatcher表示,它等于当前实现中的CommonPool。所以,launch { … }是一样的launch(DefaultDispatcher) { … },它是一样的launch(CommonPool) { … }。

父coroutineContext和 Unconfined上下文之间的区别 将在稍后显示。

注意,newSingleThreadContext创建一个新线程,这是一个非常昂贵的资源。在实际应用程序中,它必须在不再需要时释放,使用close 函数,或者存储在顶级变量中并在整个应用程序中重用。

无限制与受限制的调度员

该开敞协程调度员开始协程在调用线程,但直到第一个悬挂点。暂停后,它将在线程中恢复,该线程完全由调用的挂起函数确定。当协同程序不消耗CPU时间也不更新任何局限于特定线程的共享数据(如UI)时,无限制调度程序是合适的。

另一方面, coroutineContext 属性(在任何协同程序中可用)是对此特定协同程序的上下文的引用。这样,可以继承父上下文。特别是runBlocking协同程序的默认调度程序仅限于调用程序线程,因此继承它具有通过可预测的FIFO调度将执行限制在此线程的效果。

fun main(args: Array<String>) = runBlocking<Unit> {
    val jobs = arrayListOf<Job>()
    jobs += launch(Unconfined) { // not confined -- will work with main thread
        println("      'Unconfined': I'm working in thread ${Thread.currentThread().name}")
        delay(500)
        println("      'Unconfined': After delay in thread ${Thread.currentThread().name}")
    }
    jobs += launch(coroutineContext) { // context of the parent, runBlocking coroutine
        println("'coroutineContext': I'm working in thread ${Thread.currentThread().name}")
        delay(1000)
        println("'coroutineContext': After delay in thread ${Thread.currentThread().name}")
    }
    jobs.forEach { it.join() }
}

所以,这继承了协程coroutineContext的runBlocking {…}继续在执行main线程,而不受限制一个曾在默认执行线程重新恢复延迟 功能使用。

调试协程和线程

协同程序可以暂停在一个线程,并恢复与另一个线程开敞调度员或默认多线程调度。即使使用单线程调度程序,也可能很难弄清楚协程正在做什么,何时何地。使用线程调试应用程序的常用方法是在每个日志语句的日志文件中打印线程名称。日志框架普遍支持此功能。使用协同程序时,单独的线程名称不会给出很多上下文,因此 kotlinx.coroutines包括调试工具以使其更容易。

使用-Dkotlinx.coroutines.debugJVM选项运行以下代码:

fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")

fun main(args: Array<String>) = runBlocking<Unit> {
    val a = async(coroutineContext) {
        log("I'm computing a piece of the answer")
        6
    }
    val b = async(coroutineContext) {
        log("I'm computing another piece of the answer")
        7
    }
    log("The answer is ${a.await() * b.await()}")
}

有三个协同程序。主协程(#1) - runBlocking一个和两个协程计算延迟值a(#2)和b(#3)。它们都在上下文中执行,runBlocking并且仅限于主线程。此代码的输出是:

[main @coroutine#2] I'm computing a piece of the answer
[main @coroutine#3] I'm computing another piece of the answer
[main @coroutine#1] The answer is 42

该log函数在方括号中打印线程的名称,您可以看到它是main 线程,但是当前正在执行的协程的标识符被附加到它。打开调试模式时,会将此标识符连续分配给所有已创建的协同程序。

您可以在newCoroutineContext函数的文档中阅读有关调试工具的更多信息。

在线程之间跳转

使用 -Dkotlinx.coroutines.debug JVM选项运行以下代码:

fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")

fun main(args: Array<String>) {
    newSingleThreadContext("Ctx1").use { ctx1 ->
        newSingleThreadContext("Ctx2").use { ctx2 ->
            runBlocking(ctx1) {
                log("Started in ctx1")
                withContext(ctx2) {
                    log("Working in ctx2")
                }
                log("Back to ctx1")
            }
        }
    }
}

它演示了几种新技术。一个是使用带有明确指定上下文的runBlocking,另一个是使用withContext函数来更改协程的上下文,同时仍然保持在下面的输出中可以看到的相同协程:

[Ctx1 @coroutine#1] Started in ctx1
[Ctx2 @coroutine#1] Working in ctx2
[Ctx1 @coroutine#1] Back to ctx1

请注意,此示例还使用useKotlin标准库中的函数来释放在不再需要时使用newSingleThreadContext创建的线程。

工作在上下文中

协程的工作是其背景的一部分。协程可以使用coroutineContext[Job]表达式从其自己的上下文中检索它:

fun main(args: Array<String>) = runBlocking<Unit> {
    println("My job is ${coroutineContext[Job]}")
}

在调试模式下运行时会产生类似的东西:

My job is "coroutine#1":BlockingCoroutine{Active}@6d311334

因此,isActive在CoroutineScope仅仅是一个方便快捷 coroutineContext[Job]?.isActive == true。

子协程

当 coroutineContext 协程的用于启动另一个协程,该工作新协程成为孩子的家长协程的工作。当父协程被取消时,它的所有子节点也会被递归取消。

fun main(args: Array<String>) = runBlocking<Unit> {
    // launch a coroutine to process some kind of incoming request
    val request = launch {
        // it spawns two other jobs, one with its separate context
        val job1 = launch {
            println("job1: I have my own context and execute independently!")
            delay(1000)
            println("job1: I am not affected by cancellation of the request")
        }
        // and the other inherits the parent context
        val job2 = launch(coroutineContext) {
            delay(100)
            println("job2: I am a child of the request coroutine")
            delay(1000)
            println("job2: I will not execute this line if my parent request is cancelled")
        }
        // request completes when both its sub-jobs complete:
        job1.join()
        job2.join()
    }
    delay(500)
    request.cancel() // cancel processing of the request
    delay(1000) // delay a second to see what happens
    println("main: Who has survived request cancellation?")
}

此代码的输出是:

job1: I have my own context and execute independently!
job2: I am a child of the request coroutine
job1: I am not affected by cancellation of the request
main: Who has survived request cancellation?

结合上下文

可以使用+运算符组合协程上下文。右侧的上下文替换了左侧上下文的相关条目。例如,可以继承父协程的Job,同时替换其调度程序:

fun main(args: Array<String>) = runBlocking<Unit> {
    // start a coroutine to process some kind of incoming request
    val request = launch(coroutineContext) { // use the context of `runBlocking`
        // spawns CPU-intensive child job in CommonPool !!! 
        val job = launch(coroutineContext + CommonPool) {
            println("job: I am a child of the request coroutine, but with a different dispatcher")
            delay(1000)
            println("job: I will not execute this line if my parent request is cancelled")
        }
        job.join() // request completes when its sub-job completes
    }
    delay(500)
    request.cancel() // cancel processing of the request
    delay(1000) // delay a second to see what happens
    println("main: Who has survived request cancellation?")
}

此代码的预期结果是:

job: I am a child of the request coroutine, but with a different dispatcher
main: Who has survived request cancellation?

父母的责任

父协同程序总是等待所有孩子的完成。Parent不必显式跟踪它启动的所有子节点,也不必使用Job.join在结束时等待它们:

fun main(args: Array<String>) = runBlocking<Unit> {
    // launch a coroutine to process some kind of incoming request
    val request = launch {
        repeat(3) { i -> // launch a few children jobs
            launch(coroutineContext)  {
                delay((i + 1) * 200L) // variable delay 200ms, 400ms, 600ms
                println("Coroutine $i is done")
            }
        }
        println("request: I'm done and I don't explicitly join my children that are still active")
    }
    request.join() // wait for completion of the request, including all its children
    println("Now processing of the request is complete")
}

结果将是:

request: I'm done and I don't explicitly join my children that are still active
Coroutine 0 is done
Coroutine 1 is done
Coroutine 2 is done
Now processing of the request is complete

命名协同程序以进行调试

当协同程序经常记录时,自动分配的ID很好,您只需要关联来自同一协程的日志记录。但是,当协程与特定请求的处理或执行某些特定后台任务相关联时,最好将其明确命名以用于调试目的。 CoroutineName上下文元素与线程名称具有相同的功能。当调试模式打开时,它将显示在执行此协程的线程名称中。

以下示例演示了此概念:

fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")

fun main(args: Array<String>) = runBlocking(CoroutineName("main")) {
    log("Started main coroutine")
    // run two background value computations
    val v1 = async(CoroutineName("v1coroutine")) {
        delay(500)
        log("Computing v1")
        252
    }
    val v2 = async(CoroutineName("v2coroutine")) {
        delay(1000)
        log("Computing v2")
        6
    }
    log("The answer for v1 / v2 = ${v1.await() / v2.await()}")
}

它使用-Dkotlinx.coroutines.debugJVM选项生成的输出类似于:

[main @main#1] Started main coroutine
[ForkJoinPool.commonPool-worker-1 @v1coroutine#2] Computing v1
[ForkJoinPool.commonPool-worker-2 @v2coroutine#3] Computing v2
[main @main#1] The answer for v1 / v2 = 42

通过明确的工作取消

让我们将关于上下文,child对象和工作的知识放在一起。假设我们的应用程序有一个具有生命周期的对象,但该对象不是协程。例如,我们正在编写一个Android应用程序,并在Android活动的上下文中启动各种协同程序,以执行异步操作以获取和更新数据,执行动画等。所有这些协同程序必须在活动被销毁时取消,以避免内存泄漏。

我们可以通过创建与我们活动的生命周期相关联的Job实例来管理协同程序的生命周期。使用Job()工厂函数创建作业实例,如以下示例所示。为方便起见,launch(coroutineContext + job)我们可以编写launch(coroutineContext, parent = job)以明确表示正在使用父作业的事实,而不是使用表达式。

现在,Job.cancel的单个调用取消了我们启动的所有孩子。此外,Job.join等待所有这些完成,所以我们也可以在这个示例中使用cancelAndJoin:

fun main(args: Array<String>) = runBlocking<Unit> {
    val job = Job() // create a job object to manage our lifecycle
    // now launch ten coroutines for a demo, each working for a different time
    val coroutines = List(10) { i ->
        // they are all children of our job object
        launch(coroutineContext, parent = job) { // we use the context of main runBlocking thread, but with our parent job
            delay((i + 1) * 200L) // variable delay 200ms, 400ms, ... etc
            println("Coroutine $i is done")
        }
    }
    println("Launched ${coroutines.size} coroutines")
    delay(500L) // delay for half a second
    println("Cancelling the job!")
    job.cancelAndJoin() // cancel all our coroutines and wait for all of them to complete
}



Launched 10 coroutines
Coroutine 0 is done
Coroutine 1 is done
Cancelling the job!

正如你所看到的,只有前三个协同程序打印了一条消息,而其他协同程序被一次调用取消了job.cancelAndJoin()。因此,我们在假设的Android应用程序中需要做的就是在创建活动时创建父作业对象,将其用于子协同程序,并在销毁活动时取消它。我们不能join在Android生命周期的情况下使用它们,因为它是同步的,但是这种连接能力在构建后端服务以确保有限的资源使用时非常有用。

通道

延迟值提供了在协同程序之间传输单个值的便捷方法。管道提供了一种传输值流的方法。

通道的基础知识

一个通道是在概念上非常相似BlockingQueue。一个关键的区别是,它不是阻塞put操作,而是暂停发送,而不是阻塞take操作,它有一个暂停接收。


fun main(args: Array<String>) = runBlocking<Unit> {
    val channel = Channel<Int>()
    launch {
        // this might be heavy CPU-consuming computation or async logic, we'll just send five squares
        for (x in 1..5) channel.send(x * x)
    }
    // here we print five received integers:
    repeat(5) { println(channel.receive()) }
    println("Done!")
}

此代码的输出是:

1
4
9
16
25
Done!

关闭和迭代通道

与队列不同,可以关闭通道以指示不再有元素到来。在接收器端,使用常规for循环来接收来自信道的元素是方便的。

从概念上讲,关闭就像向通道发送特殊的关闭令牌。一旦收到此关闭令牌,迭代就会停止,因此可以保证收到关闭前所有先前发送的元素:


fun main(args: Array<String>) = runBlocking<Unit> {
    val channel = Channel<Int>()
    launch {
        for (x in 1..5) channel.send(x * x)
        channel.close() // we're done sending
    }
    // here we print received values using `for` loop (until the channel is closed)
    for (y in channel) println(y)
    println("Done!")
}

建立渠道生产者

协程生成一系列元素的模式很常见。这是生产者 - 消费者模式的一部分,通常在并发代码中找到。您可以将这样的生成器抽象为一个以通道作为参数的函数,但这与必须从函数返回结果的常识相反。

有一个名为produce的便利协程构建器,它可以很容易地在生产者端执行,并且扩展函数consumeEach,它取代了for消费者端的循环:

fun produceSquares() = produce<Int> {
    for (x in 1..5) send(x * x)
}

fun main(args: Array<String>) = runBlocking<Unit> {
    val squares = produceSquares()
    squares.consumeEach { println(it) }
    println("Done!")
}

管道

管道是一个协程正在生成的模式,可能是无限的值流:

fun produceNumbers() = produce<Int> {
    var x = 1
    while (true) send(x++) // infinite stream of integers starting from 1
}

而另一个协程或协同程序正在消耗该流,进行一些处理,并产生一些其他结果。在下面的例子中,数字只是平方:

fun square(numbers: ReceiveChannel<Int>) = produce<Int> {
    for (x in numbers) send(x * x)
}

主代码启动并连接整个管道:

fun main(args: Array<String>) = runBlocking<Unit> {
    val numbers = produceNumbers() // produces integers from 1 and on
    val squares = square(numbers) // squares integers
    for (i in 1..5) println(squares.receive()) // print first five
    println("Done!") // we are done
    squares.cancel() // need to cancel these coroutines in a larger app
    numbers.cancel()
}

我们不必在这个示例应用程序中取消这些协同程序,因为 协同程序就像守护程序线程,但是在更大的应用程序中,如果我们不再需要它,我们将需要停止我们的管道。或者,我们可以运行管道协同程序作为 主协程的子代,如以下示例所示。

带管道的素数

让我们通过一个使用协程管道生成素数的例子将管道带到极端。我们从无限的数字序列开始。这次我们引入一个显式context参数并将其传递给generate构建器,以便调用者可以控制我们的协程运行的位置:

fun numbersFrom(context: CoroutineContext, start: Int) = produce<Int>(context) {
    var x = start
    while (true) send(x++) // infinite stream of integers from start
}

以下管道阶段过滤传入的数字流,删除可由给定素数整除的所有数字:

fun filter(context: CoroutineContext, numbers: ReceiveChannel<Int>, prime: Int) = produce<Int>(context) {
    for (x in numbers) if (x % prime != 0) send(x)
}

现在我们通过从2开始一个数字流来构建我们的管道,从当前通道获取素数,并为找到的每个素数启动新的管道阶段:

numbersFrom(2) -> filter(2) -> filter(3) -> filter(5) -> filter(7) ... 

以下示例打印前十个素数,在主线程的上下文中运行整个管道。由于所有协同程序都是在其coroutineContext中作为主runBlocking协程的 子进程启动的,因此我们不必保留我们已经启动的所有协同程序的明确列表。我们使用cancelChildren 扩展函数取消所有子协同程序。

fun main(args: Array<String>) = runBlocking<Unit> {
    var cur = numbersFrom(coroutineContext, 2)
    for (i in 1..10) {
        val prime = cur.receive()
        println(prime)
        cur = filter(coroutineContext, cur, prime)
    }
    coroutineContext.cancelChildren() // cancel all children to let main finish
}

此代码的输出是:

2
3
5
7
11
13
17
19
23
29

请注意,您可以使用buildIterator 标准库中的coroutine builder 来构建相同的管道 。更换produce用buildIterator,send用yield,receive用next, ReceiveChannel用Iterator,并摆脱上下文。你也不需要runBlocking。但是,如上所示使用通道的管道的好处是,如果在CommonPool上下文中运行它,它实际上可以使用多个CPU内核。

无论如何,这是找到素数的极不切实际的方法。在实践中,管道确实涉及一些其他挂起调用(如对远程服务的异步调用),并且这些管道不能使用buildSeqeunce/ 构建buildIterator,因为它们不允许任意挂起,这与produce完全异步完全不同 。

扇出

多个协同程序可以从同一个通道接收,在它们之间分配工作。让我们从生成器协程开始,它定期生成整数(每秒十个数字):

fun produceNumbers() = produce<Int> {
    var x = 1 // start from 1
    while (true) {
        send(x++) // produce next
        delay(100) // wait 0.1s
    }
}

然后我们可以有几个处理器协同程序。在这个例子中,他们只打印他们的id和收到的号码:

fun launchProcessor(id: Int, channel: ReceiveChannel<Int>) = launch {
    for (msg in channel) {
        println("Processor #$id received $msg")
    }    
}

现在让我们启动五个处理器,让它们工作几乎一秒钟。走着瞧吧:


fun main(args: Array<String>) = runBlocking<Unit> {
    val producer = produceNumbers()
    repeat(5) { launchProcessor(it, producer) }
    delay(950)
    producer.cancel() // cancel producer coroutine and thus kill them all
}

输出将类似于以下输出,尽管接收每个特定整数的处理器ID可能不同:

Processor #2 received 1
Processor #4 received 2
Processor #0 received 3
Processor #1 received 4
Processor #3 received 5
Processor #2 received 6
Processor #4 received 7
Processor #0 received 8
Processor #1 received 9
Processor #3 received 10

注意,取消生成器协同程序会关闭其通道,从而最终终止处理器协同程序正在执行的通道上的迭代。

另外,请注意我们如何使用for循环显式迭代通道以在launchProcessor代码中执行扇出。与consumeEach此不同,这种for循环模式可以非常安全地从多个协同程序中使用。如果其中一个处理器协同程序失败,则其他处理程序协同程序仍将处理该通道,而通过其写入的处理器consumeEach 总是在正常或异常终止时消耗(取消)底层通道。

扇入

多个协同程序可以发送到同一个通道。例如,让我们有一个字符串通道和一个挂起函数,它以指定的延迟重复发送指定的字符串到此通道:

suspend fun sendString(channel: SendChannel<String>, s: String, time: Long) {
    while (true) {
        delay(time)
        channel.send(s)
    }
}

现在,让我们看看如果我们启动几个协同程序发送字符串会发生什么(在这个例子中,我们在主线程的上下文中将它们作为主协程的子节点启动):

fun main(args: Array<String>) = runBlocking<Unit> {
    val channel = Channel<String>()
    launch(coroutineContext) { sendString(channel, "foo", 200L) }
    launch(coroutineContext) { sendString(channel, "BAR!", 500L) }
    repeat(6) { // receive first six
        println(channel.receive())
    }
    coroutineContext.cancelChildren() // cancel all children to let main finish
}

输出是:

foo
foo
BAR!
foo
foo
BAR!


缓冲频道

到目前为止显示的通道没有缓冲区。当发送方和接收方彼此相遇(也称为集合点)时,无缓冲的信道传输元素。如果首先调用send,那么它将被挂起,直到调用receive,如果先调用receive,它将被挂起,直到调用send。

两个信道()工厂函数和产生助洗剂采取可选的capacity参数来指定缓冲区大小。缓冲区允许发送方在挂起之前发送多个元素,类似于BlockingQueue具有指定容量的缓冲区已满时阻塞。

看一下以下代码的行为:

fun main(args: Array<String>) = runBlocking<Unit> {
    val channel = Channel<Int>(4) // create buffered channel
    val sender = launch(coroutineContext) { // launch sender coroutine
        repeat(10) {
            println("Sending $it") // print before sending each element
            channel.send(it) // will suspend when buffer is full
        }
    }
    // don't receive anything... just wait....
    delay(1000)
    sender.cancel() // cancel sender coroutine
}

它使用容量为4的缓冲通道打印“发送” 五次:

Sending 0
Sending 1
Sending 2
Sending 3
Sending 4

前四个元素被添加到缓冲区,发送方在尝试发送第五个元素时暂停。

Ticker通道

Ticker通道是一个特殊的会合通道,Unit每次从此通道上次消耗后产生给定的延迟通道。虽然它可能看起来没有用,但它是一个有用的构建块,可以创建复杂的基于时间的生产 管道和操作员,这些管道和操作员可以进行窗口化和其他时间依赖的处理。可以在select中使用Ticker通道执行“on tick”操作。

要创建此类渠道,请使用工厂方法代码。要指示不需要其他元素,请使用ReceiveChannel.cancel方法。

fun main(args: Array<String>) = runBlocking<Unit> {
    val tickerChannel = ticker(delay = 100, initialDelay = 0) // create ticker channel
    var nextElement = withTimeoutOrNull(1) { tickerChannel.receive() }
    println("Initial element is available immediately: $nextElement") // initial delay hasn't passed yet

    nextElement = withTimeoutOrNull(50) { tickerChannel.receive() } // all subsequent elements has 100ms delay
    println("Next element is not ready in 50 ms: $nextElement")

    nextElement = withTimeoutOrNull(60) { tickerChannel.receive() }
    println("Next element is ready in 100 ms: $nextElement")

    // Emulate large consumption delays
    println("Consumer pauses for 150ms")
    delay(150)
    // Next element is available immediately
    nextElement = withTimeoutOrNull(1) { tickerChannel.receive() }
    println("Next element is available immediately after large consumer delay: $nextElement")
    // Note that the pause between `receive` calls is taken into account and next element arrives faster
    nextElement = withTimeoutOrNull(60) { tickerChannel.receive() } 
    println("Next element is ready in 50ms after consumer pause in 150ms: $nextElement")

    tickerChannel.cancel() // indicate that no more elements are needed
}


它打印以下行:


Initial element is available immediately: kotlin.Unit
Next element is not ready in 50 ms: null
Next element is ready in 100 ms: kotlin.Unit
Consumer pauses for 150ms
Next element is available immediately after large consumer delay: kotlin.Unit
Next element is ready in 50ms after consumer pause in 150ms: kotlin.Unit

请注意,自动收报机知道可能的消费者暂停,并且默认情况下,如果发生暂停,则调整下一个生成的元素延迟,尝试维持生成元素的固定速率。

可选地,mode可以指定等于[TickerMode.FIXED_DELAY]的参数以维持元素之间的固定延迟。

渠道公平

对于从多个协同程序调用它们的顺序,向通道发送和接收操作是公平的。它们以先进先出顺序提供,例如,要调用的第一个协程receive 获取元素。在以下示例中,两个协程“ping”和“pong”正在从共享的“table”通道接收“ball”对象。

data class Ball(var hits: Int)

fun main(args: Array<String>) = runBlocking<Unit> {
    val table = Channel<Ball>() // a shared table
    launch(coroutineContext) { player("ping", table) }
    launch(coroutineContext) { player("pong", table) }
    table.send(Ball(0)) // serve the ball
    delay(1000) // delay 1 second
    coroutineContext.cancelChildren() // game over, cancel them
}

suspend fun player(name: String, table: Channel<Ball>) {
    for (ball in table) { // receive the ball in a loop
        ball.hits++
        println("$name $ball")
        delay(300) // wait a bit
        table.send(ball) // send the ball back
    }
}

“ping”协程首先启动,因此它是第一个接收球的人。即使“ping”coroutine在将球送回桌面后立即再次接球,球也会被“pong”协程接收,因为它已经在等待它了:

ping Ball(hits=1)
pong Ball(hits=2)
ping Ball(hits=3)
pong Ball(hits=4)

请注意,由于正在使用的执行程序的性质,有时通道可能会产生看起来不公平的执行。有关详细信息,请参阅此问

共享可变状态和并发

可以使用多线程调度程序(如默认的CommonPool)同时执行协同程序。它提出了所有常见的并发问题。主要问题是同步访问共享可变状态。在协同程序领域,这个问题的一些解决方案类似于多线程世界中的解决方案,但其他解决方案是独一无二的。

问题

让我们推出一千个协同程序,它们都做了一千次相同的动作(总计一百万次执行)。我们还将测量完成时间以进行进一步比较:

suspend fun massiveRun(context: CoroutineContext, action: suspend () -> Unit) {
    val n = 1000 // number of coroutines to launch
    val k = 1000 // times an action is repeated by each coroutine
    val time = measureTimeMillis {
        val jobs = List(n) {
            launch(context) {
                repeat(k) { action() }
            }
        }
        jobs.forEach { it.join() }
    }
    println("Completed ${n * k} actions in $time ms")    
}

我们从一个非常简单的操作开始,该操作使用多线程CommonPool上下文来增加共享的可变变量。

var counter = 0

fun main(args: Array<String>) = runBlocking<Unit> {
    massiveRun(CommonPool) {
        counter++
    }
    println("Counter = $counter")
}

最后打印什么?它不太可能打印“Counter = 1000000”,因为一千个协程counter从多个线程同时增加而没有任何同步。

注意:如果您的旧系统具有2个或更少的CPU,那么您将始终看到1000000,因为 CommonPool在这种情况下仅在一个线程中运行。要重现此问题,您需要进行以下更改:


val mtContext = newFixedThreadPoolContext(2, "mtPool") // explicitly define context with two threads
var counter = 0

fun main(args: Array<String>) = runBlocking<Unit> {
    massiveRun(mtContext) { // use it instead of CommonPool in this sample and below 
        counter++
    }
    println("Counter = $counter")
}

最后打印什么?它不太可能打印“Counter = 1000000”,因为一千个协程counter从多个线程同时增加而没有任何同步。

注意:如果您的旧系统具有2个或更少的CPU,那么您将始终看到1000000,因为 CommonPool在这种情况下仅在一个线程中运行。要重现此问题,您需要进行以下更改:

val mtContext = newFixedThreadPoolContext(2, "mtPool") // explicitly define context with two threads
var counter = 0

fun main(args: Array<String>) = runBlocking<Unit> {
    massiveRun(mtContext) { // use it instead of CommonPool in this sample and below 
        counter++
    }
    println("Counter = $counter")
}

Volatiles 没有任何帮助
有一个常见的误解是,使变量volatile解决了并发问题。让我们试一试:

@Volatile // in Kotlin `volatile` is an annotation 
var counter = 0

fun main(args: Array<String>) = runBlocking<Unit> {
    massiveRun(CommonPool) {
        counter++
    }
    println("Counter = $counter")
}

这段代码运行速度较慢,但​​我们仍然没有得到“Counter = 1000000”,因为volatile变量保证可线性化(这是“原子”的技术术语)读取和写入相应的变量,但不提供原子性较大的行动(在我们的案例中增加)

线程安全的数据结构

适用于线程和协同程序的通用解决方案是使用线程安全(也称为同步,可线性化或原子)数据结构,该数据结构为需要在共享状态上执行的相应操作提供所有必需的同步。在简单计数器的情况下,我们可以使用AtomicInteger具有原子incrementAndGet操作的类:

var counter = AtomicInteger()

fun main(args: Array<String>) = runBlocking<Unit> {
    massiveRun(CommonPool) {
        counter.incrementAndGet()
    }
    println("Counter = ${counter.get()}")
}

这是针对此特定问题的最快解决方案。它适用于普通计数器,集合,队列和其他标准数据结构以及它们的基本操作。但是,它不容易扩展到复杂状态或没有现成的线程安全实现的复杂操作。

线程限制细粒度

线程限制是解决共享可变状态问题的一种方法,其中对特定共享状态的所有访问都限于单个线程。它通常用于UI应用程序,其中所有UI状态都局限于单个事件派发/应用程序线程。使用
单线程上下文很容易应用协同程序:

val counterContext = newSingleThreadContext("CounterContext")
var counter = 0

fun main(args: Array<String>) = runBlocking<Unit> {
    massiveRun(CommonPool) { // run each coroutine in CommonPool
        withContext(counterContext) { // but confine each increment to the single-threaded context
            counter++
        }
    }
    println("Counter = $counter")
}

此代码的工作速度非常慢,因为它可以进行细粒度的线程限制。每个增量CommonPool使用withContext块从多线程上下文切换到单线程上下文。

线程限制粗粒度

实际上,线程限制是在大块中执行的,例如,大块状态更新业务逻辑仅限于单个线程。下面的示例就是这样,在单线程上下文中运行每个协程开始。

val counterContext = newSingleThreadContext("CounterContext")
var counter = 0

fun main(args: Array<String>) = runBlocking<Unit> {
    massiveRun(counterContext) { // run each coroutine in the single-threaded context
        counter++
    }
    println("Counter = $counter")
}

这现在可以更快地运行并产生正确的结果。

相互排斥

该问题的相互排除解决方案 是使用永远不会同时执行的关键部分来保护共享状态的所有修改。在一个阻塞的世界中,你通常会使用synchronized或ReentrantLock为此而使用。Coroutine的替代品叫做Mutex。它具有锁定和解锁功能,可以分隔关键部分。关键的区别在于它Mutex.lock()是一个暂停功能。它不会阻塞线程。
还有withLock扩展功能,方便代表 mutex.lock(); try { … } finally { mutex.unlock() }模式:

val mutex = Mutex()
var counter = 0

fun main(args: Array<String>) = runBlocking<Unit> {
    massiveRun(CommonPool) {
        mutex.withLock {
            counter++        
        }
    }
    println("Counter = $counter")
}

此示例中的锁定是细粒度的,因此它付出了代价。但是,对于某些必须定期修改某些共享状态的情况,它是一个不错的选择,但是没有自然线程可以限制此状态。

Actors

的演员是由一个协程,即被限制和封装到该协程的状态下,并与其他协同程序进行通信的信道的组合的实体。一个简单的actor可以写成一个函数,但是一个具有复杂状态的actor更适合一个类。

有一个actor协程构建器,它可以方便地将actor的邮箱通道组合到其作用域中,以便从发送通道接收消息并将其组合到生成的作业对象中,这样对actor的单个引用就可以作为其句柄携带。

使用actor的第一步是定义一个actor要处理的消息类。Kotlin的密封课程非常适合这个目的。我们CounterMsg使用IncCounter消息定义密封类以增加计数器和GetCounter消息以获取其值。后者需要发送回复。甲CompletableDeferred通信原码,即表示将在将来已知的(传送)一个单一的值,在这里用于该目的。

sealed class CounterMsg
object IncCounter : CounterMsg() // one-way message to increment counter
class GetCounter(val response: CompletableDeferred<Int>) : CounterMsg() // a request with reply

然后我们定义一个使用actor coroutine builder 启动actor的函数:

// This function launches a new counter actor
fun counterActor() = actor<CounterMsg> {
    var counter = 0 // actor state
    for (msg in channel) { // iterate over incoming messages
        when (msg) {
            is IncCounter -> counter++
            is GetCounter -> msg.response.complete(counter)
        }
    }
}

主要代码很简单:

fun main(args: Array<String>) = runBlocking<Unit> {
    val counter = counterActor() // create the actor
    massiveRun(CommonPool) {
        counter.send(IncCounter)
    }
    // send a message to get a counter value from an actor
    val response = CompletableDeferred<Int>()
    counter.send(GetCounter(response))
    println("Counter = ${response.await()}")
    counter.close() // shutdown the actor
}

执行者本身执行的上下文无关紧要(正确性)。一个actor是一个协程,一个协同程序按顺序执行,因此将状态限制到特定协程可以解决共享可变状态的问题。实际上,演员可以修改自己的私有状态,但只能通过消息相互影响(避免任何锁定)。

Actor比在负载下锁定更有效,因为在这种情况下它总是有工作要做,而且根本不需要切换到不同的上下文。

注意,actor协程构建器是产品协同程序构建器的双重构件。一个actor与它接收消息的频道相关联,而一个制作者与它发送元素的频道相关联。

选择表达式

选择表达式可以同时等待多个挂起函数,并选择 第一个可用的挂起函数。

从频道中选择

让我们有两个字符串生成器:fizz和buzz。该fizz生产“菲斯”串每300毫秒:

fun fizz(context: CoroutineContext) = produce<String>(context) {
    while (true) { // sends "Fizz" every 300 ms
        delay(300)
        send("Fizz")
    }
}

而buzz产品“Buzz!” 字符串每500毫秒:

fun buzz(context: CoroutineContext) = produce<String>(context) {
    while (true) { // sends "Buzz!" every 500 ms
        delay(500)
        send("Buzz!")
    }
}

使用接收暂停功能,我们可以接收任一从一个通道或其他。但select表达式允许我们同时使用其 onReceive子句从两者接收:

suspend fun selectFizzBuzz(fizz: ReceiveChannel<String>, buzz: ReceiveChannel<String>) {
    select<Unit> { // <Unit> means that this select expression does not produce any result 
        fizz.onReceive { value ->  // this is the first select clause
            println("fizz -> '$value'")
        }
        buzz.onReceive { value ->  // this is the second select clause
            println("buzz -> '$value'")
        }
    }
}

让我们一起运行七次:

fun main(args: Array<String>) = runBlocking<Unit> {
    val fizz = fizz(coroutineContext)
    val buzz = buzz(coroutineContext)
    repeat(7) {
        selectFizzBuzz(fizz, buzz)
    }
    coroutineContext.cancelChildren() // cancel fizz & buzz coroutines    
}

这段代码的结果是:

fizz -> 'Fizz'
buzz -> 'Buzz!'
fizz -> 'Fizz'
fizz -> 'Fizz'
buzz -> 'Buzz!'
fizz -> 'Fizz'
buzz -> 'Buzz!'

选择关闭

所述的onReceive条款select当信道被关闭引起相应失败 select抛出异常。我们可以使用onReceiveOrNull子句在关闭通道时执行特定操作。以下示例还显示该select表达式返回其所选子句的结果:

suspend fun selectAorB(a: ReceiveChannel<String>, b: ReceiveChannel<String>): String =
    select<String> {
        a.onReceiveOrNull { value -> 
            if (value == null) 
                "Channel 'a' is closed" 
            else 
                "a -> '$value'"
        }
        b.onReceiveOrNull { value -> 
            if (value == null) 
                "Channel 'b' is closed"
            else    
                "b -> '$value'"
        }
    }

让我们使用它a产生“Hello”字符串四次的频道b和产生“世界”四次的频道:

fun main(args: Array<String>) = runBlocking<Unit> {
    // we are using the context of the main thread in this example for predictability ... 
    val a = produce<String>(coroutineContext) {
        repeat(4) { send("Hello $it") }
    }
    val b = produce<String>(coroutineContext) {
        repeat(4) { send("World $it") }
    }
    repeat(8) { // print first eight results
        println(selectAorB(a, b))
    }
    coroutineContext.cancelChildren()    
}

这段代码的结果非常有趣,所以我们将在模式细节中分析它:

a -> 'Hello 0'
a -> 'Hello 1'
b -> 'World 0'
a -> 'Hello 2'
a -> 'Hello 3'
b -> 'World 1'
Channel 'a' is closed
Channel 'a' is closed

有几个观察要做出来。

首先,select是偏向于第一条。当可以同时选择多个子句时,其中的第一个子句将被选中。在这里,两个通道都在不断地产生字符串,因此a作为select中的第一个子句的channel获胜。但是,因为我们使用的是无缓冲通道,所以a它的发送调用会不时地暂停,并且也有机会b发送。

第二个观察结果是,当通道已经关闭时,会立即选择onReceiveOrNull。

选择发送

选择表达式具有onSend子句,可以与选择的偏见性结合使用。

让我们编写一个整数生成器的示例,side当主要通道上的消费者无法跟上它时,它会将其值发送到通道:

fun produceNumbers(context: CoroutineContext, side: SendChannel<Int>) = produce<Int>(context) {
    for (num in 1..10) { // produce 10 numbers from 1 to 10
        delay(100) // every 100 ms
        select<Unit> {
            onSend(num) {} // Send to the primary channel
            side.onSend(num) {} // or to the side channel     
        }
    }
}

消费者将会非常缓慢,需要250毫秒才能处理每个号码:


fun main(args: Array<String>) = runBlocking<Unit> {
    val side = Channel<Int>() // allocate side channel
    launch(coroutineContext) { // this is a very fast consumer for the side channel
        side.consumeEach { println("Side channel has $it") }
    }
    produceNumbers(coroutineContext, side).consumeEach { 
        println("Consuming $it")
        delay(250) // let us digest the consumed number properly, do not hurry
    }
    println("Done consuming")
    coroutineContext.cancelChildren()    
}

那么让我们看看会发生什么:

Consuming 1
Side channel has 2
Side channel has 3
Consuming 4
Side channel has 5
Side channel has 6
Consuming 7
Side channel has 8
Side channel has 9
Consuming 10
Done consuming

选择延期值

可以使用onAwait子句选择延迟值。让我们从一个异步函数开始,该函数在随机延迟后返回一个延迟字符串值:

fun asyncString(time: Int) = async {
    delay(time.toLong())
    "Waited for $time ms"
}

让我们随机延迟开始十几个。

fun asyncStringsList(): List<Deferred<String>> {
    val random = Random(3)
    return List(12) { asyncString(random.nextInt(1000)) }
}

现在,主函数等待第一个函数完成并计算仍处于活动状态的延迟值的数量。注意,我们在这里使用的select表达式是Kotlin DSL,因此我们可以使用任意代码为它提供子句。在这种情况下,我们遍历一个延迟值列表,onAwait为每个延迟值提供子句。

fun main(args: Array<String>) = runBlocking<Unit> {
    val list = asyncStringsList()
    val result = select<String> {
        list.withIndex().forEach { (index, deferred) ->
            deferred.onAwait { answer ->
                "Deferred $index produced answer '$answer'"
            }
        }
    }
    println(result)
    val countActive = list.count { it.isActive }
    println("$countActive coroutines are still active")
}

输出是:

Deferred 4 produced answer 'Waited for 128 ms'
11 coroutines are still active

切换延迟值的通道

让我们编写一个使用延迟字符串值通道的通道生成器函数,等待每个接收的延迟值,但只有在下一个延迟值结束或通道关闭之前。这个例子将onReceiveOrNull和onAwait子句放在一起 select:


fun switchMapDeferreds(input: ReceiveChannel<Deferred<String>>) = produce<String> {
    var current = input.receive() // start with first received deferred value
    while (isActive) { // loop while not cancelled/closed
        val next = select<Deferred<String>?> { // return next deferred value from this select or null
            input.onReceiveOrNull { update ->
                update // replaces next value to wait
            }
            current.onAwait { value ->  
                send(value) // send value that current deferred has produced
                input.receiveOrNull() // and use the next deferred from the input channel
            }
        }
        if (next == null) {
            println("Channel was closed")
            break // out of loop
        } else {
            current = next
        }
    }
}

为了测试它,我们将使用一个简单的异步函数,它在指定的时间后解析为指定的字符串:

fun asyncString(str: String, time: Long) = async {
    delay(time)
    str
}

main函数只是启动一个协程来打印结果switchMapDeferreds并向它发送一些测试数据:


fun main(args: Array<String>) = runBlocking<Unit> {
    val chan = Channel<Deferred<String>>() // the channel for test
    launch(coroutineContext) { // launch printing coroutine
        for (s in switchMapDeferreds(chan)) 
            println(s) // print each received string
    }
    chan.send(asyncString("BEGIN", 100))
    delay(200) // enough time for "BEGIN" to be produced
    chan.send(asyncString("Slow", 500))
    delay(100) // not enough time to produce slow
    chan.send(asyncString("Replace", 100))
    delay(500) // give it time before the last one
    chan.send(asyncString("END", 500))
    delay(1000) // give it time to process
    chan.close() // close the channel ... 
    delay(500) // and wait some time to let it finish
}

这段代码的结果:

BEGIN
Replace
END
Channel was closed

跨越 10 个版本 Solo 升级到最新版本2.9.3 有更新!

前言

我的博客Allen 专栏-移动互联网,环境是腾讯云的 Centos,版本是 7.2,时隔一年过去,最新博客版本已经是 2.9.3,遂决定升级。Github查询升级策略,得知不可以跳版本升级, 抓狂(需要跨越 10个版本升级),今天下午,硬着头皮上了。

过程

数据备份
  • 下载DataGrip对原始博客数据进行备份
  • 将备份的数据拷贝到本机,将在本机完成升级后再部署到云上。
升级前准备

准备sourceTree
准备IDEA
准备DataGrip

版本升级日志日志SourceTree截图

版本升级日志日

升级及验证
  1. 从 2.2.0 到 2.9.3,逐个拉起tag 本地idea 重新编译部署
  2. 每次tag切换完成后, 修改 latke.properties 及 local.properties。
  3. 启动 IDEA,编译运行 ,升级成功后 自动打开浏览器 localhost 请求首页,此时系统会自动完成升级。
  4. 等待 30S,检查 preference 或 option 表的 version 字段,看是否升级成功(版本号是否更新)。
  5. 视情况进行下一步或排错。
升级后扫尾
  • 直到升级到最新版本,在本机将升级成功后的数据库导出,和程序包一起上传至腾讯云,同时检查程序的配置文件 latke.properties 及 local.properties。

总结

升级过程还是很顺利的,升级完之后,顺便把之前的https的个人免费证书再次配上了。偷懒了一年,是该拾取原来的东西了,该用的时候还得用上。
但是升级的步骤真的很无聊,十几个版本,依次这种操作,其实solo团队应该增加一个升级更新脚本,可以跨版本升级,满足这种大版本跳跃升级的需求
升级过程中,顺便翻阅solo的更新日志,看得出作者和维护团队都很尽心尽力将这个开源项目做好,在这里,表示感谢!!

Calendar的add方法中的DAY_OF_MONTH参数和DAY_OF_YEAR参数有什么区别? 有更新!

简单来说,没区别。

Calendar 类的 add 方法是个抽象方法,在 JDK 中有三个类实现了这个方法,分别是:

  • java.util.JapaneseImperialCalendar

  • java.util.GregorianCalendar

  • sun.util.BuddhistCalendar

忽略第三个,只看前两个实现类,有这么一段代码:

case DAY_OF_MONTH: // synonym of DATE
case DAY_OF_YEAR:
case DAY_OF_WEEK:
    break;

粗看一下,break以后的执行分支并没有针对这三种做区别处理。
而且 amount 似乎没有受第一个参数的范围限制,比如调用:

calendar.add(Calendar.DAY_OF_MONTH, 100);

可以看到最终结果的 day_of_year 还是增长了100 天。

这个方法的 API 中描述的两个规则可以解释这种超过范围情况:

Add rule 1. The value of field after the call minus the value of field
before the call is amount, modulo any overflow that has occurred in
field. Overflow occurs when a field value exceeds its range and, as a
result, the next larger field is incremented or decremented and the
field value is adjusted back into its range.

Add rule 2. If a smaller field is expected to be invariant, but it is
impossible for it to be equal to its prior value because of changes in
its minimum or maximum after field is changed, then its value is
adjusted to be as close as possible to its expected value. A smaller
field represents a smaller unit of time. HOUR is a smaller field than
DAY_OF_MONTH. No adjustment is made to smaller fields that are not
expected to be invariant. The calendar system determines what fields
are expected to be invariant.

所以我觉得这两个方法调用没有区别,即便 amount 超出了 day_of_month 的范围,这个方法也会正确处理。
不过从可读性考虑,还是用适当的参数比较好。

文章转载自 https://segmentfault.com/q/1010000004275325

Jupyter Notebook 分析运动模型

import os as os
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# 数据格式
orientationX,orientationY,orientationZ,world_accelerometerX,world_accelerometerY,world_accelerometerZ
1.202669,-0.351937,0.035280,0.152070,0.369052,9.003349
0 1.200916,-0.352036,0.036314,0.029368,0.487795,...
1 1.191972,-0.351980,0.037856,-0.098848,0.156166...
2 1.186100,-0.348452,0.036277,0.066162,-0.294649...
3 1.181217,-0.347043,0.034305,0.053812,-0.307321...
4 1.191511,-0.345502,0.031640,-0.080843,0.073440...
5 1.197771,-0.344494,0.030718,-0.268438,0.376443...
6 1.205506,-0.342699,0.031295,-0.342979,0.038622...
7 1.201536,-0.341691,0.029713,-0.308891,-0.40908...
8 1.206752,-0.338629,0.027096,-0.180185,-0.78439...
9 1.189720,-0.337710,0.025001,-0.016011,-0.90016...
10 1.188346,-0.335357,0.020660,-0.010016,-0.66437...
11 1.182130,-0.335195,0.020691,-0.168150,-0.08738...
12 1.191688,-0.334611,0.020730,-0.318920,0.537937...
13 1.189974,-0.335423,0.022399,-0.329259,0.755035...
14 1.203741,-0.335940,0.026293,-0.070470,0.440032...
15 1.204384,-0.337948,0.026441,0.174845,-0.220265...
16 1.202886,-0.342656,0.026160,0.182681,-0.621191...
17 1.189893,-0.344865,0.023103,0.150804,-0.733242...
18 1.186932,-0.346718,0.023389,-0.082537,-0.36582...
19 1.184310,-0.347660,0.022779,-0.180841,0.055544...
20 1.179736,-0.348779,0.023385,-0.218992,0.540119...
21 1.178087,-0.349583,0.025911,-0.006427,0.492867...
22 1.178802,-0.350321,0.025646,0.133606,0.202018,...
23 1.164416,-0.351872,0.025682,0.198423,-0.351209...
24 1.168749,-0.352112,0.025120,0.063690,-0.899801...
25 1.167735,-0.351873,0.021966,-0.135112,-1.00265...
26 1.170264,-0.350667,0.021338,-0.322740,-0.73996...
27 1.174926,-0.347044,0.018143,-0.351066,-0.29029...
28 1.190670,-0.345708,0.017527,-0.313132,-0.09275...
29 1.188927,-0.344971,0.017858,-0.141977,0.155593...
... ...
121646 1.203871,0.340158,-0.905497,-0.995467,-0.29141...
121647 1.138590,0.343419,-0.908940,-1.009314,-0.34160...
121648 1.138590,0.343419,-0.908940,-0.980498,-0.25634...
121649 1.119260,0.344839,-0.910607,-0.886564,-0.23111...
121650 1.119260,0.344839,-0.910607,-0.854298,-0.14274...
121651 1.213301,0.346194,-0.911407,-0.920431,-0.04586...
121652 1.213419,0.347656,-0.912726,-0.827374,-0.06716...
121653 1.213419,0.347656,-0.912726,-1.043766,-0.11626...
121654 1.177102,0.348217,-0.913946,-1.011394,-0.18760...
121655 1.177102,0.348217,-0.913946,-1.048592,-0.16886...
121656 1.142298,0.348099,-0.914461,-1.149854,-0.22330...
121657 1.142298,0.348099,-0.914461,-1.179157,-0.19167...
121658 1.173866,0.348120,-0.913902,-1.140562,-0.13793...
121659 1.173866,0.348120,-0.913902,-1.148874,0.106232...
121660 1.103764,0.348767,-0.912465,-1.265401,0.025554...
121661 1.103764,0.348767,-0.912465,-1.275287,-0.02291...
121662 1.091189,0.349590,-0.910857,-1.171825,0.008048...
121663 1.091189,0.349590,-0.910857,-1.163578,-0.11556...
121664 1.113554,0.350159,-0.909639,-1.077687,-0.15680...
121665 1.147780,0.350319,-0.909358,-1.094061,-0.14901...
121666 1.147780,0.350319,-0.909358,-1.190770,-0.14847...
121667 1.150275,0.351600,-0.908222,-1.042431,-0.26741...
121668 1.150275,0.351600,-0.908222,-1.025228,-0.28615...
121669 1.140762,0.352663,-0.907117,-0.936975,-0.20711...
121670 1.191499,0.353653,-0.905951,-0.961837,-0.22595...
121671 1.191499,0.353653,-0.905951,-0.915990,0.072008...
121672 1.216137,0.354658,-0.904944,-0.926901,0.087669...
121673 1.216137,0.354658,-0.904944,-1.070568,-0.07154...
121674 1.281445,0.355040,-0.904827,-1.056531,-0.02863...
121675 1.281445,0.355040,-0.904827,-1.129279,-0.10096...

121676 rows × 1 columns

data =pd.read_table('SensorData/bus_data_set_101')
data = pd.DataFrame(data)
data
orientationX orientationY orientationZ world_accelerometerX world_accelerometerY world_accelerometerZ
0 NaN NaN NaN NaN NaN NaN
1 NaN NaN NaN NaN NaN NaN
2 NaN NaN NaN NaN NaN NaN
3 NaN NaN NaN NaN NaN NaN
4 NaN NaN NaN NaN NaN NaN
5 NaN NaN NaN NaN NaN NaN
6 NaN NaN NaN NaN NaN NaN
7 NaN NaN NaN NaN NaN NaN
8 NaN NaN NaN NaN NaN NaN
9 NaN NaN NaN NaN NaN NaN
10 NaN NaN NaN NaN NaN NaN
11 NaN NaN NaN NaN NaN NaN
12 NaN NaN NaN NaN NaN NaN
13 NaN NaN NaN NaN NaN NaN
14 NaN NaN NaN NaN NaN NaN
15 NaN NaN NaN NaN NaN NaN
16 NaN NaN NaN NaN NaN NaN
17 NaN NaN NaN NaN NaN NaN
18 NaN NaN NaN NaN NaN NaN
19 NaN NaN NaN NaN NaN NaN
20 NaN NaN NaN NaN NaN NaN
21 NaN NaN NaN NaN NaN NaN
22 NaN NaN NaN NaN NaN NaN
23 NaN NaN NaN NaN NaN NaN
24 NaN NaN NaN NaN NaN NaN
25 NaN NaN NaN NaN NaN NaN
26 NaN NaN NaN NaN NaN NaN
27 NaN NaN NaN NaN NaN NaN
28 NaN NaN NaN NaN NaN NaN
29 NaN NaN NaN NaN NaN NaN
... ... ... ... ... ... ...
121646 NaN NaN NaN NaN NaN NaN
121647 NaN NaN NaN NaN NaN NaN
121648 NaN NaN NaN NaN NaN NaN
121649 NaN NaN NaN NaN NaN NaN
121650 NaN NaN NaN NaN NaN NaN
121651 NaN NaN NaN NaN NaN NaN
121652 NaN NaN NaN NaN NaN NaN
121653 NaN NaN NaN NaN NaN NaN
121654 NaN NaN NaN NaN NaN NaN
121655 NaN NaN NaN NaN NaN NaN
121656 NaN NaN NaN NaN NaN NaN
121657 NaN NaN NaN NaN NaN NaN
121658 NaN NaN NaN NaN NaN NaN
121659 NaN NaN NaN NaN NaN NaN
121660 NaN NaN NaN NaN NaN NaN
121661 NaN NaN NaN NaN NaN NaN
121662 NaN NaN NaN NaN NaN NaN
121663 NaN NaN NaN NaN NaN NaN
121664 NaN NaN NaN NaN NaN NaN
121665 NaN NaN NaN NaN NaN NaN
121666 NaN NaN NaN NaN NaN NaN
121667 NaN NaN NaN NaN NaN NaN
121668 NaN NaN NaN NaN NaN NaN
121669 NaN NaN NaN NaN NaN NaN
121670 NaN NaN NaN NaN NaN NaN
121671 NaN NaN NaN NaN NaN NaN
121672 NaN NaN NaN NaN NaN NaN
121673 NaN NaN NaN NaN NaN NaN
121674 NaN NaN NaN NaN NaN NaN
121675 NaN NaN NaN NaN NaN NaN

121676 rows × 6 columns

Battery Historian

Battery Historian是一款工具,用于检查运行Android 5.0 Lollipop(API级别21)及更高版本的Android设备上与电池相关的信息和事件,而该设备未插入。它允许应用程序开发人员在时间线上可视化系统和应用程序级事件通过平移和缩放功能,可轻松查看自设备上次充满电以来的各种汇总统计信息,并选择一个应用程序并检查影响所选应用程序特定电池的指标。它还允许两个错误报告的A / B比较,突出显示关键电池相关指标的差异。

入门

使用Docker

安装Docker

运行Battery Historian图像。选择一个端口号,并用``下面的命令替换该号码:

docker -- run -p <port>:9999 gcr.io/android-battery-historian/stable:3.0 --port 9999

对于Linux和Mac OS X:

  • 就是这样,你完成了!历史学家将在 http://localhost:

对于Windows:

  • 您可能必须在BIOS中启用虚拟化

  • 一旦你启动Docker,它应该告诉你它正在使用的机器的IP地址。例如,如果IP地址是123.456.78.90,Historian将在http://123.456.78.90:

有关端口转发的更多信息,请参阅Docker文档

从源代码构建

确保你至少有Golang版本1.8.1:

  • 按照http://golang.org/doc/install上的说明下载和安装Go编译器,工具和库。
  • 根据http://golang.org/doc/code.html#Organization中的说明创建一个工作空间目录 。
  • 确保GOPATHGOBIN环境变量的设置适当并且添加到您的$PATH 环境变量。$GOBIN should be set to $GOPATH/bin
    • 对于Windows,您可以通过“系统”控制面板的“高级”选项卡上的“环境变量”按钮来设置环境变量。某些版本的Windows通过“系统”控制面板中的“高级系统设置”选项提供此控制面板。

    • 对于Linux和Mac OS X,可以将以下行添加到〜/ .bashrc或〜/ .profile文件(假设您的工作区是$ HOME / work):

      export GOPATH=$HOME/work
      export GOBIN=$GOPATH/bin
      export PATH=$PATH:$GOBIN
      
      

接下来,如果尚未安装,请从https://git-scm.com/downloads安装Git 。

接下来,确保安装了Python 2.7(不是Python 3!)。 如果不是,请查看https://python.org/downloads,并确保python被添加到你的$PATH环境变量中。

接下来,从http://www.oracle.com/technetwork/java/javase/downloads/index.html安装Java 。

接下来,下载Battery Historian代码及其依赖项:

$ go get -d -u github.com/google/battery-historian/...

最后,运行Battery Historian!

$ cd $GOPATH/src/github.com/google/battery-historian

# Compile Javascript files using the Closure compiler
$ go run setup.go

# Run Historian on your machine (make sure $PATH contains $GOBIN)
$ go run cmd/battery-historian/battery-historian.go [--port <default:9999>]

请记住,您必须始终从$GOPATH/src/github.com/google/battery-historian目录中运行电池历史记录:

cd $GOPATH/src/github.com/google/battery-historian
go run cmd/battery-historian/battery-historian.go [--port <default:9999>]

如何采取错误报告

要从Android设备上获取错误报告,您需要启用USB下的调试Settings > System > Developer Options。在Android 4.2及更高版本中,开发者选项屏幕默认为隐藏。您可以按照此处的说明启用此功能。

要从运行Android 7.0和更高版本的开发设备获取错误报告:

$ adb bugreport bugreport.zip

对于6.0及更低版本的设备:

$ adb bugreport > bugreport.txt

开始分析!

你现在都准备好了 运行historian并访问http:// localhost:9999并上传bugreport.txt文件以开始分析。

截图

时间线:

系统统计:

应用统计信息:

高级

重置聚合电池统计信息和历史记录:

adb shell dumpsys batterystats --reset

Wakelock分析

默认情况下,即使在运行的基础上维护汇总统计信息,Android也不会为特定于应用程序的用户空间wakelock转换记录时间戳。如果您希望Historian在时间线上显示每个单独的唤醒锁的详细信息,则应在开始实验之前使用以下命令启用完整的唤醒锁报告:

adb shell dumpsys batterystats --enable full-wake-history

请注意,通过启用完全唤醒锁定报告,电池历史记录将在几个小时内溢出。短期试运行(3-4小时)使用此选项。

内核跟踪分析

生成一个记录内核唤醒源和内核唤醒锁活动的跟踪文件:

首先,将设备连接到台式机/笔记本电脑并启用内核跟踪日志记录:

$ adb root
$ adb shell

# Set the events to trace.
$ echo "power:wakeup_source_activate" >> /d/tracing/set_event
$ echo "power:wakeup_source_deactivate" >> /d/tracing/set_event

# The default trace size for most devices is 1MB, which is relatively low and might cause the logs to overflow.
# 8MB to 10MB should be a decent size for 5-6 hours of logging.

$ echo 8192 > /d/tracing/buffer_size_kb

$ echo 1 > /d/tracing/tracing_on

然后,将该设备用于预期的测试用例。

最后,提取日志:

$ echo 0 > /d/tracing/tracing_on
$ adb pull /d/tracing/trace <some path>

# Take a bug report at this time.
$ adb bugreport > bugreport.txt

注意:

历史学家绘制并实时关联事件(PST或UTC),而内核跟踪文件以jiffies(自引导时间以来的秒)记录事件。为了把这些事件联系起来,有一个脚本可以用来近似jiffies。当系统挂起并恢复时,脚本读取dmesg中记录的UTC时间。脚本的范围仅限于dmesg中存在的时间戳的数量。由于脚本在系统挂起时使用dmesg日志,因此每个设备都有不同的脚本,唯一的区别是它试图找到的设备特定的dmesg日志。这些脚本已经集成到Battery Historian工具本身中。

电力监测分析

电源监视器文件中的行应具有以下格式之一,并且格式在整个文件中应该是一致的:

<timestamp in epoch seconds, with a fractional component> <amps> <optional_volts>

要么

<timestamp in epoch milliseconds> <milliamps> <optional_millivolts>

来自电源监视器文件的条目将被覆盖在时间线图的顶部。

为确保电源监视器和错误报告时间表有所对应,请在运行任何电源监视器记录之前重置电池状态:

adb shell dumpsys batterystats --reset

停止电源监视器记录后,马上收到错误报告。

如果使用季风:

https://android.googlesource.com/platform/cts/+/master/tools/utils/monsoon.py下载AOSP季风Python脚本

# Run the script.
$ monsoon.py --serialno 2294 --hz 1 --samples 100000 -timestamp | tee monsoon.out

# ...let device run a while...

$ stop monsoon.py

修改原始文件

如果你想修改原始文件(pb / * / *。proto),首先下载需要的附加工具:

https://github.com/google/protobuf/blob/master/src/README.md安装协议缓冲区的标准C ++实现

下载Go proto编译器:

$ go get -u github.com/golang/protobuf/protoc-gen-go

编译器插件protoc-gen-go将安装在$ GOBIN中,它必须位于你的$ PATH协议编译器protoc中才能找到它。

对原始文件进行更改。

最后,使用重新生成已编译的Go原型输出文件regen_proto.sh

其他命令行工具
# System stats
$ go run cmd/checkin-parse/local_checkin_parse.go --input=bugreport.txt

# Timeline analysis
$ go run cmd/history-parse/local_history_parse.go --summary=totalTime --input=bugreport.txt

# Diff two bug reports
$ go run cmd/checkin-delta/local_checkin_delta.go --input=bugreport_1.txt,bugreport_2.txt

支持

如果您在此项目中发现错误,请提交问题:https//github.com/google/battery-historian/issues

执照

版权所有2016 Google,Inc.

根据Apache许可证2.0版(“许可证”)获得许可; 除遵守许可证外,不得使用此文件。您可以在获得许可证副本

http://www.apache.org/licenses/LICENSE-2.0

除非适用法律要求或书面同意,否则根据许可证分发的软件按“原样”分发,不附有任何明示或暗示的保证或条件。请参阅许可证以获取有关许可证下的权限和限制的特定语言。

Android 开发中的日常积累 有更新!

Android 开发中的日常积累

Android 性能优化

Android 加固与反编译

Android Studio专题

Android 开发中值得看的优秀内容和工具

Android 开源软件

Android 开发辅助工具

Android 推送(含IM)

Android后端等服务

Android 应用内测平台

Android社会化分享,短信验证,意见反馈,支付等相关

安卓网络层(包含图片)

安卓orm框架,用得比较多的就GreenDao,Ormlite

安卓Json解析

Android插件化开发与动态加载

Android 热更新

### Material Design专题
- MaterialShadows 图标阴影虚化

RecyclerView优秀库

安卓开发值得关注的库

iOS 值得关注的库

-iOS图像识别

Gradle插件相关

Kotlin专题

安卓资源相关

git

Android NoSql

设计网站,可以寻找一些酷炫的设计稿

国外个人博客

国外的一些优秀网站

Ibeacon与蓝牙4.0相关

WEB相关

Android 进程保活

前端相关

其他

ReactNative 专题

更新日志

  • 2017/6/30 android 获取帧率fps
  • 2017/6/26 添加iOS图像识别库 ,android 防截屏思路
  • 2017/6/23 添加Collect.js 库
  • 2017/6/19 添加node.js版的htp-server 一行代码实现文件服务器
  • 2017/6/12 添加Android引导页(AppIntro),View提示View (ViewTooltip)
  • 2017/6/6 添加Android WebView框架,遮罩式的导航页
  • 2017/6/5 添加Android Hook框架(Legend)

License

Copyright 2016 AllenCoder

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Mac下Android studio 之NDK配置教程(二)

#Mac下Android studio 之NDK配置教程(二)
##(一)简述

从上一篇NDK配置教程(一) 中,我 简单的阐述了MAC下NDK的基本解压和环境配置步骤。


本节我讲详细描述android studio 在MAC下的的配置运行步骤。

##(二)开始

首先,我们前往上次解压好的NDK文件路径,使用Android studio 导入Hello-jni这里写图片描述
导入完成后,AS会自动帮我们构建为grade版本。
等待完成,版本构建完成。

打开文件目录如图所示
这里写图片描述

我们在local.properties文件下添加一句话为:

ndk.dir=/Applications/Android-NDK/android-ndk-r10e

这个根据你自己的设置,改为你的ndk路径。

点击编译,如果没有出现运行错误,说明配置完成。

点击运行,手机上正常Hello-jni字样。说明配置成功。


提示注意:
1. 运行NDK的测试程序,不建议使用genymotion ,因为内核版本 问题,ndk程序会出现无法运行。
2. 尽量使用真机调试ndk程序。
3.可使用运行模拟器开启intel加速效果也可以。

Mac下Android studio 之NDK配置教程(一)

#Mac下Android studio 之NDK配置教程(一)

##1.概述

     最近项目全线转移到Mac下使用使用Android studio开发。遇到关键代码封装到 ***native***层,此时在win下的NDK配置步骤全部失效。
    为此,花费了大量时间用来查阅资料,在此,记录下来,分享给大家供以后配置中作为参考。

##2.环境

本人使用的开发配置 是:MAC OS 10.10 +androioid studio 1.2+android-ndk-r10e-darwin-x86_64+git 。其他配置类似操作即可。

##3.操作流程
1.MAC 下打开终端 获取NDK目录的权限

chmod a+x android-ndk-r10c-darwin-x86_64.bin
后面为文件存放路径 。


2.cd 路径。
cd 路径。
如果不知道文件的具体路径,可以直接拖拽到命令终端窗口下即可。


3.继续执行解压命令
./android-ndk-r10c-darwin-x86_64.bin


此时在文件存放路径会得到一个解压完成后的NDK存放目录。
效果如下图:

这里写图片描述

得到下图说明:解压文件成功。

此时:打开:命令终端:
这里写图片描述
请按如下步骤操作:

1.输入命令 pico .bash_profile
2. export NDK_ROOT=/Applications/Android-NDK/android-ndk-r10e
3. export PATH=$PATH:$NDK_ROOT
最后保存( control+X) 选 Y
这里写图片描述
4.更新刚配置的环境变量输入source .bash_profile


##验证成功

1.终端下进入
NDK 下的sample 文件夹下

这里写图片描述

按如下步骤测试是否配置成功:
1.cd hello-jni/
2. 执行 ndk-build
这里写图片描述
出现如下界面表示基本变量配置成功。。

我将在教程二继续介绍怎么在Android studio中配置使用的步骤。

android canvas 绘图笔记

android canvas 绘图笔记

1.PathEffect类
画虚线

 Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
        p.setStyle(Paint.Style.STROKE);
        p.setColor(Color.WHITE);
        p.setStrokeWidth(1);
        PathEffect effect = new DashPathEffect(new float[]{getDip2px(4), getDip2px(4), getDip2px(4), getDip2px(4)}, 1);
        p.setPathEffect(effect);
        p.setAlpha(125);//透明度50%
        p.setColor(getResources().getColor(R.color.line_chart_dash_line));
        canvas.drawLine(0, ScreenHeight / 2, ScreenWidth, ScreenHeight / 2, p);
        canvas.drawLine(ScreenWidth / 2, 0, ScreenWidth / 2, getBottom() - getDip2px(25), p);

2.android onMeasure

3.android BlurMaskFilter

***Android MaskFilter的基本使用:***

MaskFilter类可以为Paint分配边缘效果。
        对MaskFilter的扩展可以对一个Paint边缘的alpha通道应用转换。Android包含了下面几种MaskFilter:

        BlurMaskFilter   指定了一个模糊的样式和半径来处理Paint的边缘。
        EmbossMaskFilter  指定了光源的方向和环境光强度来添加浮雕效果。

        要应用一个MaskFilter,可以使用setMaskFilter方法,并传递给它一个MaskFilter对象。下面的例子是对一个已经存在的Paint应用一个EmbossMaskFilter:

Android MaskFilter的基本使用:

MaskFilter类可以为Paint分配边缘效果。
    对MaskFilter的扩展可以对一个Paint边缘的alpha通道应用转换。Android包含了下面几种MaskFilter:

    BlurMaskFilter   指定了一个模糊的样式和半径来处理Paint的边缘。
    EmbossMaskFilter  指定了光源的方向和环境光强度来添加浮雕效果。

    要应用一个MaskFilter,可以使用setMaskFilter方法,并传递给它一个MaskFilter对象。下面的例子是对一个已经存在的Paint应用一个EmbossMaskFilter:

关于设置硬件加速不能用的方法

LAYER_TYPE_NONE

LAYER_TYPE_SOFTWARE

LAYER_TYPE_HARDWARE

View级别

您可以在运行时用以下的代码关闭单个view的硬件加速:

myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
注:您不能在view级别开启硬件加速
为什么需要这么多级别的控制?
很明显,硬件加速能够带来性能提升,android为什么要弄出这么多级别的控制,
而不是默认就是全部硬件加速呢?原因是并非所有的2D绘图操作支持硬件加速,
如果您的程序中使用了自定义视图或者绘图调用,程序可能会工作不正常。
如果您的程序中只是用了标准的视图和Drawable,放心大胆的开启硬件加速吧!
具体是哪些绘图操作不支持硬件加速呢?以下是已知不支持硬件加速的绘图操作:

Canvas
clipPath()
clipRegion()
drawPicture()
drawPosText()
drawTextOnPath()
drawVertices()
Paint
setLinearText()
setMaskFilter()
setRasterizer()

另外还有一些绘图操作,开启和不开启硬件加速,效果不一样:

Canvas
clipRect(): XOR, Difference和ReverseDifference裁剪模式被忽略,3D变换将不会应用在裁剪的矩形上。

drawBitmapMesh():colors数组被忽略

drawLines():反锯齿不支持

setDrawFilter():可以设置,但无效果

Paint

setDither(): 忽略
setFilterBitmap():过滤永远开启
setShadowLayer():只能用在文本上
ComposeShader
ComposeShader
只能包含不同类型的shader (比如一个BitmapShader和一个LinearGradient,但不能是两个BitmapShader实例)

ComposeShader不能包含ComposeShader

如果应用程序受到这些影响,您可以在受影响的部分调用setLayerType(View.LAYER_TYPE_SOFTWARE, null),这样在其它地方仍然可以享受硬件加速带来的好处

2.Paint(画笔)类

   要绘制图形,首先得调整画笔,按照自己的开发需要设置画笔的相关属性。Pain类的常用属性设置方法如下:

  setAntiAlias();            //设置画笔的锯齿效果

  setColor();                 //设置画笔的颜色

  setARGB();                 //设置画笔的A、R、G、B值

  setAlpha();                 //设置画笔的Alpha值

  setTextSize();             //设置字体的尺寸

  setStyle();                  //设置画笔的风格(空心或实心)

  setStrokeWidth();        //设置空心边框的宽度

  getColor();                  //获取画笔的颜色
  

 3.Canvas(画布)类

  画笔属性设置好之后,还需要将图像绘制到画布上。Canvas类可以用来实现各种图形的绘制工作,如绘制直线、矩形、圆等等。Canvas绘制常用图形的方法如下:

  绘制直线:canvas.drawLine(float startX, float startY, float stopX, float stopY, Paint paint);

  绘制矩形:canvas.drawRect(float left, float top, float right, float bottom, Paint paint);

  绘制圆形:canvas.drawCircle(float cx, float cy, float radius, Paint paint);

  绘制字符:canvas.drawText(String text, float x, float y, Paint paint);

  绘制图形:canvas.drawBirmap(Bitmap bitmap, float left, float top, Paint paint);

Shader 渐变色设置

 //设置渐变器后绘制
        //为Paint设置渐变器
        Shader mShasder = new LinearGradient(0, 0, 40, 60, new int[]{Color.RED,Color.GREEN,Color.BLUE,Color.YELLOW}, null, Shader.TileMode.REPEAT);
        mPaint.setShader(mShasder);
        //设置阴影
        mPaint.setShadowLayer(45, 10, 10, Color.GRAY);

Paint.Style区别

Paint.Style.FILL    :填充内部
Paint.Style.FILL_AND_STROKE  :填充内部和描边
Paint.Style.STROKE  :仅描边

Region 判断是否在路径范围内

“onTouchEvent 在自定义View 中”

@Override  
public boolean onTouchEvent(MotionEvent event) {  
    switch (event.getAction()) {  
    case MotionEvent.ACTION_DOWN:  
        // 按下  
        cx = (int) event.getX();  
        cy = (int) event.getY();  
        // 通知重绘  
        postInvalidate();   //该方法会调用onDraw方法,重新绘图  
        break;  
    case MotionEvent.ACTION_MOVE:  
        // 移动  
        cx = (int) event.getX();  
        cy = (int) event.getY();  
        // 通知重绘  
        postInvalidate();  
        break;  
    case MotionEvent.ACTION_UP:  
        // 抬起  
        cx = (int) event.getX();  
        cy = (int) event.getY();  
        // 通知重绘  
        postInvalidate();  
        break;  
    }  
      
    /* 
     * 备注1:此处一定要将return super.onTouchEvent(event)修改为return true,原因是: 
     * 1)父类的onTouchEvent(event)方法可能没有做任何处理,但是返回了false。 
     * 2)一旦返回false,在该方法中再也不会收到MotionEvent.ACTION_MOVE及MotionEvent.ACTION_UP事件。 
     */  
    //return super.onTouchEvent(event);  
    return true;    
}  

RectF

  • Android Rect和RectF的区别*
    1、精度不一样,Rect是使用int类型作为数值,RectF是使用float类型作为数值
    2、两个类型提供的方法也不是完全一致
    
    Rect:
    equals(Object obj)   (for some reason it as it's own implementation of equals)
    exactCenterX()
    exactCenterY()
    flattenToString()
    toShortString()
    unflattenFromString(String str)
    
    RectF:
    round(Rect dst)
    roundOut(Rect dst)
    set(Rect src)
    

Shader

//设置渐变器后绘制
        //为Paint设置渐变器
        Shader mShasder = new LinearGradient(0, 0, 40, 60, new int[]{Color.RED,Color.GREEN,Color.BLUE,Color.YELLOW}, null, Shader.TileMode.REPEAT);
        mPaint.setShader(mShasder);
        //设置阴影
        mPaint.setShadowLayer(45, 10, 10, Color.GRAY);
Paint p=new Paint();
LinearGradient lg=new LinearGradien(0,0,100,100,Color.RED,Color.BLUE,Shader.TileMode.MIRROR);  
参数一为渐变起初点坐标x位置,
参数二为y轴位置,
参数三和四分辨对应渐变终点,
最后参数为平铺方式,这里设置为镜像
Gradient是基于Shader类,所以我们通过Paint的setShader方法来设置这个渐变,代码如下: mPaint.setShader(lg);

canvas.drawCicle(0,0,200,mPaint); //参数3为画圆的半径,类型为float型。
 
 
它除了定义开始颜色和结束颜色以外还可以定义,多种颜色组成的分段渐变效果

LinearGradient shader = new LinearGradient(0, 0, endX, endY, new int[]{startColor, midleColor, endColor},new float[]{0 , 0.5f, 1.0f}, TileMode.MIRROR);

其中参数new int[]{startColor, midleColor, endColor}是参与渐变效果的颜色集合,

其中参数new float[]{0 , 0.5f, 1.0f}是定义每个颜色处于的渐变相对位置,

这个参数可以为null,如果为null表示所有的颜色按顺序均匀的分布

LinearGradient有两个构造函数;

public LinearGradient(float x0, float y0, float x1, float y1, int[] colors, float[] positions,Shader.TileMode tile)

参数:
float x0: 渐变起始点x坐标

float y0:渐变起始点y坐标

float x1:渐变结束点x坐标

float y1:渐变结束点y坐标

int[] colors:颜色 的int 数组

float[] positions: 相对位置的颜色数组,可为null,  若为null,可为null,颜色沿渐变线均匀分布

Shader.TileMode tile: 渲染器平铺模式

public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1,Shader.TileMode tile)
float x0: 渐变起始点x坐标

float y0:渐变起始点y坐标

float x1:渐变结束点x坐标

float y1:渐变结束点y坐标

int color0: 起始渐变色

int color1: 结束渐变色

Shader.TileMode tile: 渲染器平铺模式


Android 透明度渐变

android 代码截屏

 /**
     * Returns the bitmap that represents the chart.
     *
     * @return
     */
    public Bitmap getChartBitmap() {
        // Define a bitmap with the same size as the view
        Bitmap returnedBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.RGB_565);
        // Bind a canvas to it
        Canvas canvas = new Canvas(returnedBitmap);
        // Get the view's background
        Drawable bgDrawable = getBackground();
        if (bgDrawable != null)
            // has background drawable, then draw it on the canvas
            bgDrawable.draw(canvas);
        else
            // does not have background drawable, then draw white background on
            // the canvas
            canvas.drawColor(Color.WHITE);
        // draw the view on the canvas
        draw(canvas);
        // return the bitmap
        return returnedBitmap;
    }
 /**
     * Returns the bitmap that represents the chart.
     *
     * @return
     */
    public Bitmap getChartBitmap() {
        // Define a bitmap with the same size as the view
        Bitmap returnedBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.RGB_565);
        // Bind a canvas to it
        Canvas canvas = new Canvas(returnedBitmap);
        // Get the view's background
        Drawable bgDrawable = getBackground();
        if (bgDrawable != null)
            // has background drawable, then draw it on the canvas
            bgDrawable.draw(canvas);
        else
            // does not have background drawable, then draw white background on
            // the canvas
            canvas.drawColor(Color.WHITE);
        // draw the view on the canvas
        draw(canvas);
        // return the bitmap
        return returnedBitmap;
    }

对canvas的translate()方法的理解

canvas.save();//锁画布(为了保存之前的画布状态)
        canvas.translate(10, 10);//把当前画布的原点移到(10,10),后面的操作都以(10,10)作为参照点,默认原点为(0,0)
        drawScene(canvas);
        canvas.restore();//把当前画布返回(调整)到上一个save()状态之前

        canvas.save();//锁画布(为了保存之前的画布状态)
        canvas.translate(160, 10);//把当前画布的原点移到(160,10),后面的操作都以(160,10)作为参照点,
        canvas.clipRect(10, 10, 90, 90);//这里的真实坐标为左上(170,170)、右下(250,250)
        canvas.clipRect(30, 30, 70, 70, Region.Op.DIFFERENCE);
        drawScene(canvas);
        canvas.restore();

图片镜像

public Bitmap convertBmp(Bitmap bmp) {  
        int w = bmp.getWidth();  
        int h = bmp.getHeight();  
  
        Matrix matrix = new Matrix();  
        matrix.postScale(-1, 1); // 镜像水平翻转  
        Bitmap convertBmp = Bitmap.createBitmap(bmp, 0, 0, w, h, matrix, true);  
          
        return convertBmp;  
    }  

Android 防内存泄露handler

Android 防内存泄露handler

1.使用弱引用 WeakRefHander

	/**
	 * 作者: allen on 15/11/24.感谢开源作者https://coding.net/u/coding/p/Coding-Android/git
	 */
	
	/**
	 * 弱引用 handler 防止内存泄露
	 */
	public class WeakRefHander extends Handler {
	
	    private final WeakReference<Handler.Callback> mRef;
	    private final int mLoopTime;
	    private int NO_LOOP = -1;
	    private int what =0;
	
	    /**
	     * 循环
	     *
	     * @param loopAction
	     * @param loopTime
	     */
	    public WeakRefHander(Handler.Callback loopAction, int loopTime) {
	        super();
	        this.mRef = new WeakReference<>(loopAction);
	        this.mLoopTime = loopTime;
	
	    }
	
	    /**
	     * 不循环
	     *
	     * @param loopAction
	     */
	    public WeakRefHander(Handler.Callback loopAction) {
	        super();
	        mRef = new WeakReference<>(loopAction);
	        mLoopTime = NO_LOOP;
	    }
	
	    @Override
	    public void handleMessage(Message msg) {
	        Handler.Callback action = mRef.get();
	        if (action != null) {
	            action.handleMessage(msg);
	            if (mLoopTime != NO_LOOP) {
	                sendEmptyMessageDelayed(what, mLoopTime);
	            }
	        }
	    }
	
	    public void start() {
	        removeMessages(0);
	        sendEmptyMessageDelayed(0, 0);
	    }
	
	    public void start(int what, long delay) {
	        this.what = what;
	        removeMessages(what);
	        sendEmptyMessageDelayed(what, delay);
	    }
	
	    public void stop() {
	        removeMessages(what);
	    }
	
	    public void clear() {
	        removeMessages(what);
	        mRef.clear();
		    }
		}

2. 实现 Activity implements WeakRefHander.Callback

3. 在handleMessage处理业务逻辑

示例代码:

    public class MainActivity extends AppCompatActivity implements WeakRefHander.Callback {
        private WeakRefHander weakRefHander;
        private static final int HANDLER_MESSAGE_START = 1001;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            weakRefHander = new WeakRefHander(this, 1);
            weakRefHander.start(HANDLER_MESSAGE_START, 1000 * 30);
    }

    @Override
    public boolean handleMessage(Message msg) {
        switch (msg.what) {
            case HANDLER_MESSAGE_START:
                //Todo 处理业务逻辑
                break;
            default:
                break;
        }
        return true;
    }


    @Override
    public void onResume() {
        super.onResume();
        weakRefHander.start();
    }

    @Override
    public void onPause() {
        weakRefHander.stop();
        super.onPause();
    }

    @Override
    protected void onDestroy() {
        weakRefHander.clear();
        super.onDestroy();
    }
    }

-Github示例参考代码

参考作者:-Coding-Android作者