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 

理解Python多重赋值 有更新!

  1. 赋值顺序记错了

以前一直以为是自右至左 依次赋值,今天在leetcode 复习算法时,发现自己把这个记混淆了

i = 0

def get_num() -> int:
    global i
    i = i + 1
  return i

a, b, c = get_num(), get_num(), get_num()
print(a, b, c)

打印结果是

1 2 3

2.赋值逻辑记错了

a, b = 0, 1

a, b = 7, a + 0

print(a, b)

打印结果是

7 0

不等于如下部分

a, b = 0, 1
a = 7
b = a + 0
print(a, b)

打印结果是

7 7

参考博客链接 https://blog.csdn.net/JewelCCL/article/details/79187469

复盘2018 有更新!

岁月不居,时节如流,2018年转眼就过去了,当2019年来的时候才发现,时间流逝的速度真快,对自己复盘一下

  • 工作之中:

今年是从3月中旬从原来247项目中,抽取计步功能转入趣走计步,即现在的趣走,5月发布了1.0版本,截止至今,用户数已经从0突破了百万,日活也突破了十万。 今年工作很忙,忙的很多时间,忘记了自己的初衷,大多数时间不是在写业务代码就是在画UI, 年初给自己的定的一些核心目标,现在回首,发现有的已经实现,有的已经偏离,有的已经不可能完成了。

  • 开源社区:

开源项目,今年对于我来说,开发技术上没有达到我预期的提升,体现在Github全年只提交了163次。 而对比我过去的2016年364次。2017年415次。开源项目今年全年时间,基本上只进行了bug修复和稳定性提升,相对2017年没有一些核心的功能开发,一部分原因是工作时间太忙,下班回来基本上十点左右了,偶尔周六还要加班,所以,业余时间也已经没有了精力。

  • 技术方向:

2017年快结束的时候,给自己定下了要去学Python,Golang,这个目标实现了,虽然没能在实际项目中发挥很大作用,但极大的扩展了我的技术视野,也让自己清晰的明白,不可以局限于自己的思维定式,很多接口设计,或者业务架构,业务逻辑,更应该从全局角度去,从实际场景去看,从用户体验区看,不可一味的意淫,例如,实际项目这样对客户端更友好,但是,对服务器可能是很大的资源消耗,这种数据结构设计,可能故意的通过字段冗余,去保证后续业务更方便拓展。说道自身项目,看由于当初趣走项目一上来,我就100%Kotlin开发,所以,也顺道给kotlin学了,现在线上已经稳定运行的趣走是100%Kotlin。对于新语言,虽然说不应该随波逐流,但是,要保持对新事物的好奇心和接纳心,当你自己亲自尝试之后,再去决定应不应该用,而不是无脑的听信一些根本没怎么在项目里面用过而一味批判这不好,那不好,所谓的大佬名言,或者一味的抵制新东西,觉得他会的就是最好的,其他的都是垃圾,xx等等。一年下来,更觉得脑子是个好东西,有的话,不可全信,也不能全不信,要用心思考去分析和辨别所谓大佬和大师的鸡汤。技术总是短期内被高估,长期内被低估。特别是一门新语言或者一套新框架出来的时候,很多人无脑的推崇或者无脑的抵制都不应该,应该学着从自身的工作中,和经验中,去分析和甄别。特别是选择一门新的方向时,没有人能预测未来会是怎样,应该用心去体会,深思熟虑,做出符合自己的决定。

  • 生活之中:

转眼之间,小孩又大了一岁,父母又老了一岁,生活压力也随之增大,去年亲戚家一年之内,因为突发疾病逝去了两名亲人,越发觉得,身体健康才是最重要,所以,从那次之后,越发的注重健康,要对自己好一点,要父母照顾好身体,只有家里老人都健康,小孩衣食无忧,自己才有精力和动力去拼搏,自己的努力才是值得的。时间飞逝,要面临的问题也越来越多,从去年开始考虑买房,由于房价一年来都不稳定,贷款利率突然加了那么多,买房的计划又搁置和推迟了,等2019年房价稳定了,就下手买房吧,毕竟是刚需住房,小孩也快上学了,这些必须得开始做了。

展望2019,希望2019年经济大环境快点好起来吧,经济下行对居民生活影响太大了,身边有不少朋友的公司在裁人和拖欠工资,希望2018年所有的不好都过去吧,希望2018年没有实现的遗憾,都会是2019年惊喜的铺垫。

跨越 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中配置使用的步骤。