Allen 专栏-移动互联网

Kotlin 协程1.0.1 版本翻译更新 置顶! 有更新!

Kotlin协程已发布正式稳定版,目前已在线上生产环境稳定运行,鉴于此次更新发生重大变化,新增的协程作用域,代码重构和迁移成本不大,又将新版本的文档仔细学习一遍,不少新特性还在尝试之中

Kotlin协程1.0.1版本 更新 更新时间 2018/12/12 23:30
原文 https://github.com/Kotlin/kotlinx.coroutines
参考代码片段 https://github.com/AllenCoder/KotlinDemo

你的第一个协程

fun main(args: Array<String>) {
    GlobalScope.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>) { 
   GlobalScope.launch {
           // 运行一个新的协程在后台任务
           delay(1000L)
           println("World!")
       }
       println("Hello,") // 主线程立即执行
       runBlocking {
           // 主线程延时2000ms
           delay(2000L)  //  
       }
}

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

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

import kotlinx.coroutines.*

fun main() = runBlocking<Unit> { // start main coroutine
    GlobalScope.launch { // launch new coroutine in background and continue
        delay(1000L)
        println("World!")
    }
    println("Hello,") // main coroutine continues here immediately
    delay(2000L)      // delaying for 2 seconds to keep JVM alive
}

等候工作

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

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

结构化并发

1303/5000
对于协程的实际使用仍有一些需要。当我们使用GlobalScope.launch时,我们创建了一个顶级协程。
尽管它很轻,但它在运行时仍会消耗一些内存资源。如果我们忘记保留对新启动的协程的引用,它仍会运行。
如果协程中的代码挂起(例如,我们错误地延迟了太长时间),如果我们启动了太多的协程并且内存不足会怎么样?
必须手动保持对所有已启动的协程的引用并加入它们是容易出错的。有一个更好的解决方案。
我们可以在代码中使用结构化并发。就像我们通常使用线程(线程总是全局的)一样,
我们可以在我们正在执行的操作的特定范围内启动协程,而不是在GlobalScope中启动协程。
在我们的示例中,我们使用runBlocking coroutine builder将main函数转换为协程。每个协程构建器(包括runBlocking)
都将CoroutineScope的实例添加到其代码块的范围内。我们可以在此范围内启动协程,
而无需显式连接它们,因为在其范围内启动的所有协程完成之前,外部协程(在我们的示例中为runBlocking)不会完成。因此,我们可以使我们的示例更简单:

import kotlinx.coroutines.*

fun main() = runBlocking { // this: CoroutineScope
    launch { // 运行一个协程在runBlocking作用域
        delay(1000L)
        println("World!")
    }
    println("Hello,")
}

作用域构建器

除了由不同构建器提供的协同作用域之外,还可以使用coroutineScope构建器声明自己的作用域。
它会创建新的协程范围,并且在所有已启动的子项完成之前不会完成。
runBlocking和coroutineScope之间的主要区别在于后者在等待所有子进程完成时不会阻塞当前线程。

fun main() = runBlocking { // this: CoroutineScope
    launch {
        delay(200L)
        println("Task from runBlocking")
    }

    coroutineScope { // Creates a new coroutine scope
        launch {
            delay(500L)
            println("Task from nested launch")
        }

        delay(100L)
        println("Task from coroutine scope") // This line will be printed before nested launch
    }

    println("Coroutine scope is over") // 直到所有任务执行完成打印
}

提取函数重构

让我们将代码块提取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!")
}

但是,如果提取的函数包含在当前作用域上调用的协程构建器,该怎么办?
在这种情况下,提取函数上的suspend修饰符是不够的。 在CoroutineScope上
制作doWorld扩展方法是其中一种解决方案,但它可能并不总是适用,
因为它不会使API更清晰。 惯用解决方案是将显式CoroutineScope作为包含目标函数的类中的字段,
或者在外部类实现CoroutineScope时隐式。 作为最后的手段,
可以使用CoroutineScope(coroutineContext),但是这种方法在结构上是不安全的,
因为您不再能够控制此方法的执行范围。 只有私有API才能使用此构建器。

协程足够轻量级

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{
    GlobalScope.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

import kotlinx.coroutines.*
import kotlin.system.*

fun main() = runBlocking<Unit> {
//sampleStart
    val time = measureTimeMillis {
        val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
        val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
        // some computation
        one.start() // start the first one
        two.start() // start the second one
        println("The answer is ${one.await() + two.await()}")
    }
    println("Completed in $time ms")
//sampleEnd    
}

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
}

它产生这样的东西:

The answer is 42
Completed in 2017 ms

所以,这里定义了两个协程,但是没有像前面的例子那样执行,但是程序员在完全通过调用start开始执行时会给出控制权。
我们首先启动一个,然后启动两个,然后等待各个协程完成。

注意,如果我们在println中调用了await并且在各个协程上省略了start,
那么我们就会得到顺序行为,因为await启动协程执行并等待执行完成,这不是懒惰的预期用例。
在计算值涉及挂起函数的情况下,async(start = CoroutineStart.LAZY)的用例是标准惰性函数的替代。

异步风格的功能

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

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

// somethingUsefulTwoAsync的结果类型是Deferred <Int> 
fun  somethingUsefulTwoAsync() = GlobalScope.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 函数,或者存储在顶级变量中并在整个应用程序中重用。

无限制与受限制的调度器

Dispatchers.Unconfined协程调度程序在调用程序线程中启动协程,但只在第一个挂起点之前。
暂停后,它将在线程中恢复,该线程完全由调用的挂起函数确定。
当协程不消耗CPU时间也不更新任何局限于特定线程的共享数据(如UI)时,无限制调度程序是合适的。

另一方面,默认情况下,继承外部CoroutineScope的调度程序。
特别是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 

跨越 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作者