【kotlin 协程】万字协程 一篇完成kotlin 协程进阶


协程简介

Kotlin 中的协程提供了一种全新处理并发的方式,可以在 Android 平台上使用它来简化异步执行的代码。协程是从 Kotlin 1.3 版本开始引入,但这一概念在编程世界诞生的黎明之际就有了,最早使用协程的编程语言可以追溯到 1967 年的 Simula 语言。
在过去几年间,协程这个概念发展势头迅猛,现已经被诸多主流编程语言采用,比如 JavascriptC#Python、Ruby 以及 Go 等。Kotlin 的协程是基于来自其他语言的既定概念。

Android 平台上,协程主要用来解决两个问题:

  • 处理耗时任务 (Long running tasks),这种任务常常会阻塞住主线程;
  • 保证主线程安全 (Main-safety) ,即确保安全地从主线程调用任何 suspend 函数。

从本质上来说,协程就是一个轻量级的线程。

一、协程的基本使用

在使用协程之前,我们需要先引入Coroutine 的包

// Kotlin
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.4.32"

// 协程核心库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3"
// 协程Android支持库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3"
// 协程Java8支持库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.4.3"

创建协程的方式有很多种,这里不延伸协程的高级用法(热数据通道Channel冷数据流Flow.…),创建协程这里介绍常用的三种方式:

1.1、runBlocking 启动

runBlocking {
    println("runBlocking 启动一个协程")
}

runBlocking 启动一个协程会阻塞调用它的线程,只到里面的代码执行结束,返回值是泛型T

1.2、GlobalScope.launch 启动

GlobalScope.launch {
    println("launch 启动一个协程")
}

launch启动一个协程不会阻塞调用线程,必须要在协程作用域(CoroutineScope)中才能调用,返回值是一个Job

1.3、GlobalScope.async 启动

GlobalScope.async {
    println("async 启动一个协程")
}

async启动一个协程其实和launch 是一样的,不同点在于async的返回参数是: Deferred Deferred<out T> : Job,它实现了一个Deferred接口,但是Deferred 继承了job,Deferred和job 的不同点是,Deferred 里面定义了await 函数,需要与await()挂起函数结合使用。

1.4、三种启动方式的说明

  • runBlocking{} - 主要用于测试

    该方法的设计目的是让suspend风格编写的库能够在常规阻塞代码中使用,常在main方法和测试中使用。

  • GlobalScope.launch/async{} - 不推荐使用

    由于这样启动的协程存在启动协程的组件已被销毁但协程还存在的情况,极限情况下可能导致资源耗尽,因此并不推荐这样启动,尤其是在客户端这种需要频繁创建销毁组件的场景。

二、Coroutine 源码解析

这里我们使用CoroutineScope.launch{}的源码为例,来深入了解Coroutine:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FwlJqMCx-1656497201350)(/Users/tiger/Library/Application%20Support/typora-user-images/image-20220629150026321.png)]

上面是launch函数的定义,它以CoroutineScope的扩展函数的形成出现,函数参数分别是:协程上下文CoroutineContext协程启动模式CoroutineStart协程体,返回值是协程实例Job,其中CoroutineContext又包括了JobCoroutineDispatcherCoroutineName。下面我们就一一介绍这些内容:CoroutineContextJobCoroutineDispatcherCoroutineStartCoroutineScope

2.1、CoroutineContext

CoroutineContext: 协程上下文

  1. 线程行为、生命周期、异常以及调试
  2. 包含用户定义的一些数据集合,这些数据与协程密切相关
  3. 它是一个有索引的 Element 实例集合,一个介于 set 和 map之间的数据结构。每个 element 在这个集合有一个唯一的 Key
  • Job: 控制协程的生命周期

  • CoroutineDispatcher: 向合适的线程分发任务

  • CoroutineName: 协程的名称,调试的时候很有用

  • CoroutineExceptionHandler: 处理未被捕捉的异常

CoroutineContext 有两个非常重要的元素 — JobDispatcherJob 是当前的 Coroutine 实例而 Dispatcher 决定了当前 Coroutine 执行的线程,还可以添加CoroutineName,用于调试,添加 CoroutineExceptionHandler 用于捕获异常,它们都实现了Element接口。

fun main() {
    val coroutineContext = Job() + Dispatchers.Default + CoroutineName("myContext")
    println("$coroutineContext,${coroutineContext[CoroutineName]}")
    val newCoroutineContext = coroutineContext.minusKey(CoroutineName)
    println("$newCoroutineContext")
}

输出结果:

[外链图片转存失败,建议将图片保存下来直接上传(img-oblRSXoV-1656497201351)(/Users/tiger/Library/Application%20Support/typora-user-images/image-20220629150718195.png)]

CoroutineContext 源码

[外链图片转存失败,建议将图片保存下来直接上传(img-JXwQeyAt-1656497201352)(/Users/tiger/Library/Application%20Support/typora-user-images/image-20220629151029045.png)]

通过源码我可以看到CoroutineContext 定义了四个核心的操作:

[外链图片转存失败,建议将图片保存下来直接上传(img-wFQcUMam-1656497201353)(/Users/tiger/Library/Application%20Support/typora-user-images/image-20220629153519634.png)]

  • 操作符get
    可以通过 key 来获取这个 Element。由于这是一个 get 操作符,所以可以像访问 map 中的元素一样使用 context[key]这种中括号的形式来访问。
  • 操作符 plus
    Set.plus 扩展函数类似,返回一个新的 context 对象,新的对象里面包含了两个里面的所有 Element,如果遇到重复的(Key 一样的),那么用+号右边的 Element 替代左边的。+ 运算符可以很容易的用于结合上下文,但是有一个很重要的事情需要小心 —— 要注意它们结合的次序,因为这个 + 运算符是不对称的。
  • fun fold(initial: R,operation: (R,Element) -> R): R
    Collection.fold 扩展函数类似,提供遍历当前 context 中所有 Element 的能力。
  • fun minusKey(key: Key<>): CoroutineContext
    返回一个上下文,其中包含该上下文中的元素,但不包含具有指定key的元素。

2.2、Job 源码

Job 用于处理协程

对于每一个所创建的协程 (通过 launch 或者 async),它会返回一个 Job实例,该实例是协程的唯一标识,并且负责管理协程的生命周期
CoroutineScope.launch 函数返回的是一个 Job 对象,代表一个异步的任务。Job 具有生命周期并且可以取消。 Job 还可以有层级关系,一个Job可以包含多个子Job,当父Job被取消后,所有的子Job也会被自动取消;当子Job被取消或者出现异常后父Job也会被取消。
除了通过 CoroutineScope.launch 来创建Job对象之外,还可以通过 Job() 工厂方法来创建该对象。默认情况下,子Job的失败将会导致父Job被取消,这种默认的行为可以通过 SupervisorJob 来修改。
具有多个子 Job 的父Job 会等待所有子Job完成(或者取消)后,自己才会执行完成

Job 生命周期的状态

[外链图片转存失败,建议将图片保存下来直接上传(img-YHyuoEt5-1656497201354)(/Users/tiger/Library/Application%20Support/typora-user-images/image-20220629154406779.png)]

一个任务可以包含一系列状态: 新创建 (New)、活跃 (Active)、完成中 (Completing)、已完成 (Completed)、取消中 (Cancelling) 和已取消 (Cancelled)。虽然我们无法直接访问这些状态,但是我们可以访问 Job 的属性: isActiveisCancelledisCompleted
如果协程处于活跃状态,协程运行出错或者调用 job.cancel() 都会将当前任务置为取消中 (Cancelling) 状态 (isActive = false,isCancelled = true)。当所有的子协程都完成后,协程会进入已取消 (Cancelled) 状态,此时 isCompleted = true

2.3、Job 的常用函数

这些函数都是线程安全的,所以可以直接在其他 Coroutine 中调用。

  • fun start(): Boolean
    调用该函数来启动这个 Coroutine,如果当前 Coroutine 还没有执行调用该函数返回 true,如果当前 Coroutine 已经执行或者已经执行完毕,则调用该函数返回 false

  • fun cancel(cause: CancellationException? = null)
    通过可选的取消原因取消此作业。 原因可以用于指定错误消息或提供有关取消原因的其他详细信息,以进行调试。

  • fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle
    通过这个函数可以给 Job 设置一个完成通知,当 Job 执行完成的时候会同步执行这个通知函数。 回调的通知对象类型为:typealias CompletionHandler = (cause: Throwable?) -> Unit. CompletionHandler 参数代表了 Job 是如何执行完成的。 cause 有下面三种情况:

    1. 如果 Job 是正常执行完成的,则 cause 参数为 null
    1. 如果 Job 是正常取消的,则 cause 参数为 CancellationException 对象。这种情况不应该当做错误处理,这是任务正常取消的情形。所以一般不需要在错误日志中记录这种情况。
    2. 其他情况表示 Job 执行失败了。

这个函数的返回值为 DisposableHandle 对象,如果不再需要监控 Job 的完成情况了, 则可以调用 DisposableHandle.dispose 函数来取消监听。如果 Job 已经执行完了, 则无需调用 dispose 函数了,会自动取消监听。

  • suspend fun join()

join 函数和前面三个函数不同,这是一个 suspend 函数。所以只能在 Coroutine 内调用。

这个函数会暂停当前所处的 Coroutine直到该Coroutine执行完成。所以 join 函数一般用来在另外一个 Coroutine 中等待 job 执行完成后继续执行。当 Job 执行完成后, job.join 函数恢复,这个时候 job 这个任务已经处于完成状态了,而调用 job.joinCoroutine 还继续处于 activie 状态。

请注意,只有在其所有子级都完成后,作业才能完成

该函数的挂起是可以被取消的,并且始终检查调用的CoroutineJob是否取消。如果在调用此挂起函数或将其挂起时,调用CoroutineJob被取消或完成,则此函数将引发 CancellationException

2.4、SupervisorJob

SupervisorJob 是一个顶层函数,定义如下:

[外链图片转存失败,建议将图片保存下来直接上传(img-Cr1mmCX0-1656497201354)(/Users/tiger/Library/Application%20Support/typora-user-images/image-20220629172905770.png)]

该函数创建了一个处于 active 状态的supervisor job。如前所述, Job 是有父子关系的,如果子Job 失败了父Job会自动失败,这种默认的行为可能不是我们期望的。比如在 Activity 中有两个子Job分别获取一篇文章的评论内容和作者信息。如果其中一个失败了,我们并不希望父Job自动取消,这样会导致另外一个子Job也被取消。而SupervisorJob就是这么一个特殊的 Job,里面的子Job不相互影响,一个子Job失败了,不影响其他子Job的执行。SupervisorJob(parent:Job?) 具有一个parent参数,如果指定了这个参数,则所返回的 Job 就是参数 parent 的子Job。如果 Parent Job 失败了或者取消了,则这个 Supervisor Job 也会被取消。当 Supervisor Job被取消后,所有 Supervisor Job 的子Job也会被取消。

MainScope() 的实现就使用了 SupervisorJob 和一个 Main Dispatcher

[外链图片转存失败,建议将图片保存下来直接上传(img-jBwCsJkb-1656497201355)(/Users/tiger/Library/Application%20Support/typora-user-images/image-20220629173116558.png)]

但是SupervisorJob是很容易被误解的,它和协程异常处理、子协程所属Job类型还有域有很多让人混淆的地方,具体异常处理可以看Google的这一篇文章:协程中的取消和异常 | 异常处理详解

三、suspend关键字

这个 suspend 关键字,既然它并不是真正实现挂起,那它的作用是什么?

它其实是一个提醒。

函数的创建者对函数的使用者的提醒:我是一个耗时函数,我被我的创建者用挂起的方式放在后台运行,所以请在协程里调用我。

挂起的操作 —— 也就是切线程,依赖的是挂起函数里面的实际代码,而不是这个关键字。

所以这个关键字,只是一个提醒

3.1、CoroutineDispatcher 调度器

  • Dispatchers.Default

    默认的调度器,适合处理后台计算,是一个CPU密集型任务调度器。如果创建 Coroutine 的时候没有指定 dispatcher,则一般默认使用这个作为默认值。Default dispatcher 使用一个共享的后台线程池来运行里面的任务。注意它和IO共享线程池,只不过限制了最大并发数不同。

  • Dispatchers.IO

    顾名思义这是用来执行阻塞 IO 操作的,是和Default共用一个共享的线程池来执行里面的任务。根据同时运行的任务数量,在需要的时候会创建额外的线程,当任务执行完毕后会释放不需要的线程。

  • Dispatchers.Unconfined

    由于Dispatchers.Unconfined未定义线程池,所以执行的时候默认在启动线程。遇到第一个挂起点,之后由调用resume的线程决定恢复协程的线程。

  • Dispatchers.Main

    指定执行的线程是主线程,在Android上就是UI线程。

由于子Coroutine 会继承父Coroutine 的 context,所以为了方便使用,我们一般会在 父Coroutine 上设定一个 Dispatcher,然后所有 子Coroutine 自动使用这个 Dispatcher。

3.2、CoroutineStart 协程启动模式

  • CoroutineStart.DEFAULT

    协程创建后立即开始调度,在调度前如果协程被取消,其将直接进入取消响应的状态虽然是立即调度,但也有可能在执行前被取消

  • CoroutineStart.ATOMIC

    协程创建后立即开始调度,协程执行到第一个挂起点之前不响应取消
    虽然是立即调度,但其将调度和执行两个步骤合二为一了,就像它的名字一样,其保证调度和执行是原子操作,因此协程也一定会执行

  • CoroutineStart.LAZY

    只要协程被需要时,包括主动调用该协程的start、join或者await等函数时才会开始调度,如果调度前就被取消,协程将直接进入异常结束状态

  • CoroutineStart.UNDISPATCHED

    协程创建后立即在当前函数调用栈中执行,直到遇到第一个真正挂起的点
    是立即执行,因此协程一定会执行

这些启动模式的设计主要是为了应对某些特殊的场景。业务开发实践中通常使用DEFAULTLAZY这两个启动模式就够了

3.3、CoroutineScope - 协程作用域

定义协程必须指定其 CoroutineScopeCoroutineScope 可以对协程进行追踪,即使协程被挂起也是如此。同调度程序 (Dispatcher) 不同,CoroutineScope 并不运行协程,它只是确保您不会失去对协程的追踪。为了确保所有的协程都会被追踪,Kotlin 不允许在没有使用 CoroutineScope 的情况下启动新的协程。CoroutineScope 可被看作是一个具有超能力的 ExecutorService 的轻量级版本。CoroutineScope 会跟踪所有协程,同样它还可以取消由它所启动的所有协程。这在 Android 开发中非常有用,比如它能够在用户离开界面时停止执行协程。

Coroutine 是轻量级的线程,并不意味着就不消耗系统资源。 当异步操作比较耗时的时候,或者当异步操作出现错误的时候,需要把这个 Coroutine 取消掉来释放系统资源。在 Android 环境中,通常每个界面(ActivityFragment等)启动的 Coroutine 只在该界面有意义,如果用户在等待 Coroutine 执行的时候退出了这个界面,则再继续执行这个 Coroutine 可能是没必要的。另外 Coroutine 也需要在适当的 context 中执行,否则会出现错误,比如在非 UI 线程去访问 View。 所以 Coroutine 在设计的时候,要求在一个范围(Scope)内执行,这样当这个 Scope 取消的时候,里面所有的子 Coroutine 也自动取消。所以要使用 Coroutine 必须要先创建一个对应的 CoroutineScope

CoroutineScope 接口

public interface CoroutineScope {
    public val coroutineContext: CoroutineContext
}

CoroutineScope 只是定义了一个新 Coroutine 的执行 Scope。每个 coroutine builder都是 CoroutineScope 的扩展函数,并且自动的继承了当前 ScopecoroutineContext

3.4、分类及行为规则

官方框架在实现复合协程的过程中也提供了作用域,主要用以明确写成之间的父子关系,以及对于取消或者异常处理等方面的传播行为。该作用域包括以下三种:

  • 顶级作用域
    没有父协程的协程所在的作用域为顶级作用域。

  • 协同作用域
    协程中启动新的协程,新协程为所在协程的子协程,这种情况下,子协程所在的作用域默认为协同作用域。此时子协程抛出的未捕获异常,都将传递给父协程处理,父协程同时也会被取消。

    coroutineScope 内部的异常会向上传播,子协程未捕获的异常会向上传递给父协程,任何一个子协程异常退出,会导致整体的退出

    [外链图片转存失败,建议将图片保存下来直接上传(img-9WOgO3ii-1656497201356)(/Users/tiger/Library/Application%20Support/typora-user-images/image-20220629171306019.png)]

  • 主从作用域
    与协同作用域在协程的父子关系上一致,区别在于,处于该作用域下的协程出现未捕获的异常时,不会将异常向上传递给父协程。

    supervisorScope属于主从作用域,会继承父协程的上下文,它的特点就是子协程的异常不会影响父协程

    [外链图片转存失败,建议将图片保存下来直接上传(img-6XWwSHDa-1656497201357)(/Users/tiger/Library/Application%20Support/typora-user-images/image-20220629171225579.png)]

除了三种作用域中提到的行为以外,父子协程之间还存在以下规则:

  • 父协程被取消,则所有子协程均被取消。由于协同作用域和主从作用域中都存在父子协程关系,因此此条规则都适用。
  • 父协程需要等待子协程执行完毕之后才会最终进入完成状态,不管父协程自身的协程体是否已经执行完。
  • 子协程会继承父协程的协程上下文中的元素,如果自身有相同key的成员,则覆盖对应的key,覆盖的效果仅限自身范围内有效。

四、Android中协程的使用及取消和异常

普通协程如果产生未处理异常会将此异常传播至它的父协程,然后父协程会取消所有的子协程、取消自己、将异常继续向上传递。下面拿一个官方的图来示例这个过程:

[外链图片转存失败,建议将图片保存下来直接上传(img-kZUAkoGs-1656497201357)(/Users/tiger/Downloads/28647715daef471ebdeef6883d823716~tplv-k3u1fbpfcp-zoom-in-crop-mark-3024-0-0-0.image.gif)]

这种情况有的时候并不是我们想要的,我们更希望一个协程在产生异常时,不影响其他协程的执行,在上文中我们也提到了一些解决方案,下面我们就在实践一下。

4.1、使用SupervisorJob

    //    使用官方库的 MainScope()获取一个协程作用域用于创建协程
    private val mScope = MainScope();

    fun onClickCoroutine(view: View) {

        mScope.launch(Dispatchers.Default) {
            println("我是第一个协程")
        }

        mScope.launch(Dispatchers.Default) {
            println("我是第二个协程")
            throw RuntimeException("RuntimeException 就是一个异常")
        }

        mScope.launch(Dispatchers.Default) {
            println("我是第三个协程")
        }
    }

代码执行结果:

[外链图片转存失败,建议将图片保存下来直接上传(img-rO5GJIh7-1656497201358)(/Users/tiger/Library/Application%20Support/typora-user-images/image-20220629175116538.png)]

MainScope()之前提到过了,它的实现就是用了SupervisorJob。执行结果就是 第二个协程 抛出异常后,第三个协程 正常执行了,但是程序崩了,因为我们没有处理这个异常,下面完善一下代码

异常处理:

fun onClickCoroutine(view: View) {

    mScope.launch(Dispatchers.Default) {
        println("我是第一个协程")
    }

    mScope.launch(Dispatchers.Default + CoroutineExceptionHandler { coroutineContext, throwable ->
        println(
            "CoroutineExceptionHandler: $throwable"
        )
    }) {
        println("我是第二个协程")
        throw RuntimeException("RuntimeException 就是一个异常")
    }

    mScope.launch(Dispatchers.Default) {
        println("我是第三个协程")
    }
}

打印结果:

[外链图片转存失败,建议将图片保存下来直接上传(img-bJDbdHiB-1656497201359)(/Users/tiger/Library/Application%20Support/typora-user-images/image-20220629175335800.png)]

程序没有崩溃,并且异常处理的打印也输出了,这就达到了我们想要的效果。但是要注意一个事情,这几个子协程的父级是SupervisorJob,但是他们再有子协程的话,他们的子协程的父级就不是SupervisorJob了,所以当它们产生异常时,就不是我们演示的效果了。我们使用一个官方的图来解释这个关系:

[外链图片转存失败,建议将图片保存下来直接上传(img-fvunLv8l-1656497201359)(/Users/tiger/Library/Application%20Support/typora-user-images/image-20220629175408336.png)]

如图所示,新的协程被创建时,会生成新的 Job 实例替代 SupervisorJob

4.2、使用supervisorScope

这个作用域上文中也有提到,使用supervisorScope也可以达到我们想要的效果,上代码:

fun onClickCoroutine(view: View) {

    val coroutineScope = CoroutineScope(Job() + Dispatchers.Default)

    coroutineScope.launch(CoroutineExceptionHandler { coroutineContext, throwable ->
        println(
            "CoroutineExceptionHandler: $throwable"
        )
    }) {
        supervisorScope {
            launch {
                println("我是第一个协程")
            }
            launch {
                println("我是第二个协程")
                throw RuntimeException("RuntimeException 就是一个异常")
            }
            launch {
                println("我是第三个协程")
            }
        }
    }
}

运行结果

[外链图片转存失败,建议将图片保存下来直接上传(img-D1HGdoRb-1656497201360)(/Users/tiger/Library/Application%20Support/typora-user-images/image-20220629180127369.png)]

可以看到已经达到了我们想要的效果,但是如果将supervisorScope换成coroutineScope,结果就不是这样了。最终还是拿官方的图来展示:

[外链图片转存失败,建议将图片保存下来直接上传(img-ueenzjef-1656497201361)(/Users/tiger/Library/Application%20Support/typora-user-images/image-20220629180156664.png)]

文章到这里就结束了,本文参考Quyunshuo,如有侵权,请联系删除。

原文地址:https://blog.csdn.net/u010755471

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


文章浏览阅读8.8k次,点赞9次,收藏20次。本文操作环境:win10/Android studio 3.21.环境配置 在SDK Tools里选择 CMAKE/LLDB/NDK点击OK 安装这些插件. 2.创建CMakeLists.txt文件 在Project 目录下,右键app,点击新建File文件,命名为CMakeLists.txt点击OK,创建完毕! 3.配置文件 在CMa..._link c++ project with gradle
文章浏览阅读1.2w次,点赞15次,收藏69次。实现目的:由mainActivity界面跳转到otherActivity界面1.写好两个layout文件,activity_main.xml和otherxml.xmlactivity_main.xml&lt;?xml version="1.0" encoding="utf-8"?&gt;&lt;RelativeLayout ="http://schemas..._android studio 界面跳转
文章浏览阅读3.8w次。前言:最近在找Android上的全局代理软件来用,然后发现了这两款神作,都是外国的软件,而且都是开源的软件,因此把源码下载了下来,给有需要研究代理这方面的童鞋看看。不得不说,国外的开源精神十分浓,大家相互使用当前基础的开源软件,然后组合成一个更大更强的大开源软件。好吧,废话不多说,下面简单介绍一下这两款开源项目。一、ProxyDroid:ProxyDroid功能比较强大,用到的技术也比较多,源码也_proxydroid
文章浏览阅读2.5w次,点赞17次,收藏6次。创建项目后,运行项目时Gradle Build 窗口却显示错误:程序包R不存在通常情况下是不会出现这个错误的。我是怎么遇到这个错误的呢?第一次创建项目,company Domain我使用的是:aven.com,但是创建过程在卡在了Building 'Calculator' Gradle Project info这个过程中,于是我选择了“Cancel”第二次创建项目,我还是使用相同的项目名称和项目路_r不存在
文章浏览阅读8.9w次,点赞4次,收藏43次。前言:在Android上使用系统自带的代理,限制灰常大,仅支持系统自带的浏览器。这样像QQ、飞信、微博等这些单独的App都不能使用系统的代理。如何让所有软件都能正常代理呢?ProxyDroid这个软件能帮你解决!使用方法及步骤如下:一、推荐从Google Play下载ProxyDroid,目前最新版本是v2.6.6。二、对ProxyDroid进行配置(基本配置:) (1) Auto S_proxydroid使用教程
文章浏览阅读1.1w次,点赞4次,收藏17次。Android Studio提供了一个很实用的工具Android设备监视器(Android device monitor),该监视器中最常用的一个工具就是DDMS(Dalvik Debug Monitor Service),是 Android 开发环境中的Dalvik虚拟机调试监控服务。可以进行的操作有:为测试设备截屏,查看特定进程中正在运行的线程以及堆栈信息、Logcat、广播状态信息、模拟电话_安卓摄像头调试工具
文章浏览阅读2.1k次。初学Android游戏开发的朋友,往往会显得有些无所适从,他们常常不知道该从何处入手,每当遇到自己无法解决的难题时,又往往会一边羡慕于 iPhone下有诸如Cocos2d-iphone之类的免费游戏引擎可供使用,一边自暴自弃的抱怨Android平台游戏开发难度太高,又连个像样的游 戏引擎也没有,甚至误以为使用Java语言开发游戏是一件费力不讨好且没有出路的事情。事实上,这种想法完全是没有必_有素材的游戏引擎
文章浏览阅读3.2k次,点赞2次,收藏2次。2014年12月从csdn专家福利获得的一本书《Android游戏开发技术实战详解》,尘封了一年多的时间,今天才翻开来看。我认识中的Android,提到Android最先浮现在我脑海中的是那可爱的机器人图标:这个Logo是由Ascender公司设计的,诞生于2010年,其设计灵感源于男女厕所门上的图形符号(真的是灵感无处不在),于是布洛克绘制了一个简单的机器人,它的躯干就像锡罐的形状,头上还有两根_智能手机的特点有哪些?
文章浏览阅读8.1k次,点赞9次,收藏11次。首先,Android是不是真的找工作越来越难呢?这个可能是大家最关心的。这个受大的经济环境以及行业发展前景的影响,同时也和个人因素有关。2016-08-26近期一方面是所在的公司招聘Java开发人员很难招到合适的,投简历的人很少;而另一方面,经常听身边的人说Android、iOS方面找工作不好找,特别是没什么经验的,经验比较少的!说是不好找,但在我家所在的吉林省省会长春,会Unity3D+Maya_android 开发和asp.net哪个好 site:blog.csdn.net
文章浏览阅读6.1k次。在上篇“走进Android开发的世界,HelloWorld”,我们创建了一个Android 项目 HelloWorld,并演示了如何通过USB连接手机查看运行效果;而如果没有手机或没有对应型号的手机,又想做对应型号(屏幕尺寸、Android系统版本)的适配,应该怎么办呢?这时Android模拟器就派上用场了。Android模拟器Android SDK自带一个移动模拟器。它是一个可以运行在你电脑上的_安卓移动开发软件怎样预览
文章浏览阅读8.9k次。Google IO 2017 上宣布,将Kotlin语言作为安卓开发的官方语言。Kotlin由JetBrains公司开发,与Java 100%互通,并具备诸多Java尚不支持的新特性。谷歌称还将与JetBrains公司合作,为Kotlin设立一个非盈利基金会。Kotlin 是一个基于 JVM 的静态类型编程语言,Kotlin可以编译成Java字节码,也可以编译成JavaScript,方便在没有JV_kotlin为什么被嫌弃
文章浏览阅读9.6w次,点赞17次,收藏35次。有些情况下,不方便使用断点的方式来调试,而是希望在控制台打印输出日志,使用过Eclipse的同学都知道Java可以使用 System.out.println(""); 来在控制台打印输出日志,但是在android studio中却是不行的,还是有差别的,那应该用什么呢?android.util.Log在调试代码的时候我们需要查看调试信息,那我们就需要用Android Log类。android.ut_andirod.studio 为什么不在控制台打印输出
文章浏览阅读8.2k次,点赞2次,收藏8次。在上篇“走进Android开发的世界,HelloWorld”,我们创建了一个Android 项目 HelloWorld,并演示了如何通过USB连接手机查看运行效果;这里讲一下如何为应用添加一个按钮,并为按钮添加Click单击事件处理程序,显示/隐藏另一个按钮。添加按钮在HelloWorld项目的基础上,打开界面布局文件:activity_main.xml切换到Design(设计)模式;在组件But_activity_main.xml按钮隐藏
文章浏览阅读2.9k次,点赞3次,收藏9次。android 开发工具主流的还是Android Studio,当然也有很多人喜欢用Eclipse,也有人喜欢用IntelliJ IDEA ;还有Xamarin这种只需要编写一次代码,可以编译多种平台可运行的强大工具。但是它又真的强大吗?就我看来没有,身边很多人还是在用Android Studio、XCode开发应用,没见谁在用Xamarin之类的工具。系统要求WindowsMicrosoft®_android开发下载安装
文章浏览阅读4.2k次,点赞7次,收藏26次。你知道Hello World程序的由来吗?对于大多数编程语言的学习来说,真正入门的一课就是 Hello World!会而不难,难而不会。虽然很多人写过关于Android开发Hello World的文章,但随着时间的推移,开发工具、技术的进步,可能有些已经过时了。我就记录一下当下我所经历的第一个Android APP HelloWorld。一、准备1、开发环境参考:Android Studio 下载_android helloworld textview 句柄获取
这篇“android轻量级无侵入式管理数据库自动升级组件怎么实现”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定...
今天小编给大家分享一下Android实现自定义圆形进度条的常用方法有哪些的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文...
这篇文章主要讲解了“Android如何解决字符对齐问题”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Android...
这篇文章主要介绍“Android岛屿数量算法怎么使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Android岛屿数量算...
本篇内容主要讲解“Android如何开发MQTT协议的模型及通信”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Andro...