阿里P7大佬教你破解 Kotlin 协程(4) - 异常处理篇

跨平台app开发框架首选,android组件化和插件化 需要注意 onMeasure,onLayout,onDraw 的调用时机,以及 Touch 事件分发机制。 阅读详情

破解 Kotlin 协程(4) - 异常处理篇

在这里插入图片描述

关键词:Kotlin 协程 异常处理

异步代码的异常处理通常都比较让人头疼,而协程则再一次展现了它的威力。

1. 引子

我们在前面一篇文章当中提到了这样一个例子:

typealias Callback = (User) -> Unit

fun getUser(callback: Callback){
    ...
}

我们通常会定义这样的回调接口来实现异步数据的请求,我们可以很方便的将它转换成协程的接口:

suspend fun getUserCoroutine() = suspendCoroutine<User> {
    continuation ->
    getUser {
        continuation.resume(it)
    }
}

并最终交给按钮点击事件或者其他事件去触发这个异步请求:

getUserBtn.setOnClickListener {
    GlobalScope.launch(Dispatchers.Main) {
        userNameView.text = getUserCoroutine().name
    }
}

那么问题来了,既然是请求,总会有失败的情形,而我们这里并没有对错误的处理,接下来我们就完善这个例子。

2. 添加异常处理逻辑

首先我们加上异常回调接口函数:

interface Callback<T> {
    fun onSuccess(value: T)

    fun onError(t: Throwable)
}

接下来我们在改造一下我们的 getUserCoroutine:

suspend fun getUserCoroutine() = suspendCoroutine<User> { continuation ->
    getUser(object : Callback<User> {
        override fun onSuccess(value: User) {
            continuation.resume(value)
        }

        override fun onError(t: Throwable) {
            continuation.resumeWithException(t)
        }
    })
}

大家可以看到,我们似乎就是完全把 Callback 转换成了一个 Continuation,在调用的时候我们只需要:

GlobalScope.launch(Dispatchers.Main) {
    try {
        userNameView.text = getUserCoroutine().name
    } catch (e: Exception) {
        userNameView.text = "Get User Error: $e"
    }
}

是的,你没看错,一个异步的请求异常,我们只需要在我们的代码中捕获就可以了,这样做的好处就是,请求的全流程异常都可以在一个 try...catch... 当中捕获,那么我们可以说真正做到了把异步代码变成了同步的写法。

如果你一直在用 RxJava 处理这样的逻辑,那么你的请求接口可能是这样的:

fun getUserObservable(): Single<User> {
    return Single.create<User> { emitter ->
        getUser(object : Callback<User> {
            override fun onSuccess(value: User) {
                emitter.onSuccess(value)
            }

            override fun onError(t: Throwable) {
                emitter.onError(t)
            }
        })
    }
}

调用时大概是这样的:

getUserObservable()
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe ({ user ->
            userNameView.text = user.name
        }, {
            userNameView.text = "Get User Error: $it"
        })

其实你很容易就能发现在这里 RxJava 做的事儿跟协程的目的是一样的,只不过协程用了一种更自然的方式。

也许你已经对 RxJava 很熟悉并且感到很自然,但相比之下,RxJava 的代码比协程的复杂度更高,更让人费解,这一点我们后面的文章中也会持续用例子来说明这一点。

3. 全局异常处理

线程也好、RxJava 也好,都有全局处理异常的方式,例如:

fun main() {
    Thread.setDefaultUncaughtExceptionHandler {t: Thread, e: Throwable ->
        //handle exception here
        println("Thread '${t.name}' throws an exception with message '${e.message}'")
    }

    throw ArithmeticException("Hey!")
}

我们可以为线程设置全局的异常捕获,当然也可以为 RxJava 来设置全局异常捕获:

RxJavaPlugins.setErrorHandler(e -> {
        //handle exception here
        println("Throws an exception with message '${e.message}'")
});

协程显然也可以做到这一点。类似于通过 Thread.setUncaughtExceptionHandler 为线程设置一个异常捕获器,我们也可以为每一个协程单独设置 CoroutineExceptionHandler,这样协程内部未捕获的异常就可以通过它来捕获:

private suspend fun main(){
    val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
        log("Throws an exception with message: ${throwable.message}")
    }

    log(1)
    GlobalScope.launch(exceptionHandler) {
        throw ArithmeticException("Hey!")
    }.join()
    log(2)
}

运行结果:

19:06:35:087 [main] 1
19:06:35:208 [DefaultDispatcher-worker-1 @coroutine#1] Throws an exception with message: Hey!
19:06:35:211 [DefaultDispatcher-worker-1 @coroutine#1] 2

CoroutineExceptionHandler 竟然也是一个上下文,协程的这个上下文可真是灵魂一般的存在,这倒是一点儿也不让人感到意外。

当然,这并不算是一个全局的异常捕获,因为它只能捕获对应协程内未捕获的异常,如果你想做到真正的全局捕获,在 Jvm 上我们可以自己定义一个捕获类实现:

class GlobalCoroutineExceptionHandler: CoroutineExceptionHandler {
    override val key: CoroutineContext.Key<*> = CoroutineExceptionHandler

    override fun handleException(context: CoroutineContext, exception: Throwable) {
        println("Coroutine exception: $exception")
    }
}

然后在 classpath 中创建 META-INF/services/kotlinx.coroutines.CoroutineExceptionHandler,文件名实际上就是 CoroutineExceptionHandler 的全类名,文件内容就写我们的实现类的全类名:

com.bennyhuo.coroutines.sample2.exceptions.GlobalCoroutineExceptionHandler

这样协程中没有被捕获的异常就会最终交给它处理。

Jvm 上全局 CoroutineExceptionHandler 的配置,本质上是对 ServiceLoader 的应用,之前我们在讲 Dispatchers.Main 的时候提到过,Jvm 上它的实现也是通过 ServiceLoader 来加载的。

需要明确的一点是,通过 async 启动的协程出现未捕获的异常时会忽略 CoroutineExceptionHandler,这与 launch 的设计思路是不同的。

4. 异常传播

异常传播还涉及到协程作用域的概念,例如我们启动协程的时候一直都是用的 GlobalScope,意味着这是一个独立的顶级协程作用域,此外还有 coroutineScope{...} 以及 supervisorScope{...}。

  • 通过 GlobeScope 启动的协程单独启动一个协程作用域,内部的子协程遵从默认的作用域规则。通过 GlobeScope 启动的协程“自成一派”。
  • coroutineScope 是继承外部 Job 的上下文创建作用域,在其内部的取消操作是双向传播的,子协程未捕获的异常也会向上传递给父协程。它更适合一系列对等的协程并发的完成一项工作,任何一个子协程异常退出,那么整体都将退出,简单来说就是”一损俱损“。这也是协程内部再启动子协程的默认作用域。
  • supervisorScope 同样继承外部作用域的上下文,但其内部的取消操作是单向传播的,父协程向子协程传播,反过来则不然,这意味着子协程出了异常并不会影响父协程以及其他兄弟协程。它更适合一些独立不相干的任务,任何一个任务出问题,并不会影响其他任务的工作,简单来说就是”自作自受“,例如 UI,我点击一个按钮出了异常,其实并不会影响手机状态栏的刷新。需要注意的是,supervisorScope 内部启动的子协程内部再启动子协程,如无明确指出,则遵守默认作用域规则,也即 supervisorScope 只作用域其直接子协程。

这么说还是比较抽象,因此我们拿一些例子来分析一下:

suspend fun main() {
    log(1)
    try {
        coroutineScope { //①
            log(2)
            launch { // ②
                log(3)
                launch { // ③
                    log(4)
                    delay(100)
                    throw ArithmeticException("Hey!!")
                }
                log(5)
            }
            log(6)
            val job = launch { // ④
                log(7)
                delay(1000)
            }
            try {
                log(8)
                 job.join()
                log("9")
            } catch (e: Exception) {
                log("10. $e")
            }
        }
        log(11)
    } catch (e: Exception) {
        log("12. $e")
    }
    log(13)
}

这例子稍微有点儿复杂,但也不难理解,我们在一个 coroutineScope 当中启动了两个协程 ②④,在 ② 当中启动了一个子协程 ③,作用域直接创建的协程记为①。那么 ③ 当中抛异常会发生什么呢?我们先来看下输出:

11:37:36:208 [main] 1
11:37:36:255 [main] 2
11:37:36:325 [DefaultDispatcher-worker-1] 3
11:37:36:325 [DefaultDispatcher-worker-1] 5
11:37:36:326 [DefaultDispatcher-worker-3] 4
11:37:36:331 [main] 6
11:37:36:336 [DefaultDispatcher-worker-1] 7
11:37:36:336 [main] 8
11:37:36:441 [DefaultDispatcher-worker-1] 10. kotlinx.coroutines.JobCancellationException: ScopeCoroutine is cancelling; job=ScopeCoroutine{Cancelling}@2bc92d2f
11:37:36:445 [DefaultDispatcher-worker-1] 12. java.lang.ArithmeticException: Hey!!
11:37:36:445 [DefaultDispatcher-worker-1] 13

注意两个位置,一个是 10,我们调用 join,收到了一个取消异常,在协程当中支持取消的操作的suspend方法在取消时会抛出一个 CancellationException,这类似于线程中对 InterruptException 的响应,遇到这种情况表示 join 调用所在的协程已经被取消了,那么这个取消究竟是怎么回事呢?

原来协程 ③ 抛出了未捕获的异常,进入了异常完成的状态,它与父协程 ② 之间遵循默认的作用域规则,因此 ③ 会通知它的父协程也就是 ② 取消,② 根据作用域规则通知父协程 ① 也就是整个作用域取消,这是一个自下而上的一次传播,这样身处 ① 当中的 job.join 调用就会抛异常,也就是 10 处的结果了。如果不是很理解这个操作,想一下我们说到的, coroutineScope 内部启动的协程就是“一损俱损”。实际上由于父协程 ① 被取消,协程④ 也不能幸免,如果大家有兴趣的话,也可以对 ④ 当中的 delay进行捕获,一样会收获一枚取消异常。

还有一个位置就是 12,这个是我们对 coroutineScope 整体的一个捕获,如果 coroutineScope 内部以为异常而结束,那么我们是可以对它直接 try...catch... 来捕获这个异常的,这再一次表明协程把异步的异常处理到同步代码逻辑当中。

那么如果我们把 coroutineScope 换成 supervisorScope,其他不变,运行结果会是怎样呢?

11:52:48:632 [main] 1
11:52:48:694 [main] 2
11:52:48:875 [main] 6
11:52:48:892 [DefaultDispatcher-worker-1 @coroutine#1] 3
11:52:48:895 [DefaultDispatcher-worker-1 @coroutine#1] 5
11:52:48:900 [DefaultDispatcher-worker-3 @coroutine#3] 4
11:52:48:905 [DefaultDispatcher-worker-2 @coroutine#2] 7
11:52:48:907 [main] 8
Exception in thread "DefaultDispatcher-worker-3 @coroutine#3" java.lang.ArithmeticException: Hey!!
    at com.bennyhuo.coroutines.sample2.exceptions.ScopesKt$main$2$1$1.invokeSuspend(Scopes.kt:17)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:238)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)
11:52:49:915 [DefaultDispatcher-worker-3 @coroutine#2] 9
11:52:49:915 [DefaultDispatcher-worker-3 @coroutine#2] 11
11:52:49:915 [DefaultDispatcher-worker-3 @coroutine#2] 13

我们可以看到,1-8 的输出其实没有本质区别,顺序上的差异是线程调度的前后造成的,并不会影响协程的语义。差别主要在于 9 与 10、11与12的区别,如果把 scope 换成 supervisorScope,我们发现 ③ 的异常并没有影响作用域以及作用域内的其他子协程的执行,也就是我们所说的“自作自受”。

这个例子其实我们再稍做一些改动,为 ② 和 ③ 增加一个 CoroutineExceptionHandler,就可以证明我们前面提到的另外一个结论:

首先我们定义一个 CoroutineExceptionHandler,我们通过上下文获取一下异常对应的协程的名字:

val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
    log("${coroutineContext[CoroutineName]} $throwable")
}

接着,基于前面的例子我们为 ② 和 ③ 添加 CoroutineExceptionHandler 和名字:

...
supervisorScope { //①
    log(2)
    launch(exceptionHandler + CoroutineName("②")) { // ②
        log(3)
        launch(exceptionHandler + CoroutineName("③")) { // ③
            log(4)
...

再运行这段程序,结果就比较有意思了:

...
07:30:11:519 [DefaultDispatcher-worker-1] CoroutineName(②) java.lang.ArithmeticException: Hey!!
...

我们发现触发的 CoroutineExceptionHandler 竟然是协程 ② 的,意外吗?不意外,因为我们前面已经提到,对于 supervisorScope 的子协程 (例如 ②)的子协程(例如 ③),如果没有明确指出,它是遵循默认的作用于规则的,也就是 coroutineScope 的规则了,出现未捕获的异常会尝试传递给父协程并尝试取消父协程。

究竟使用什么 Scope,大家自己根据实际情况来确定,我给出一些建议:

  • 对于没有协程作用域,但需要启动协程的时候,适合用 GlobalScope
  • 对于已经有协程作用域的情况(例如通过 GlobalScope 启动的协程体内),直接用协程启动器启动
  • 对于明确要求子协程之间相互独立不干扰时,使用 supervisorScope
  • 对于通过标准库 API 创建的协程,这样的协程比较底层,没有 Job、作用域等概念的支撑,例如我们前面提到过 suspend main 就是这种情况,对于这种情况优先考虑通过 coroutineScope 创建作用域;更进一步,大家尽量不要直接使用标准库 API,除非你对 Kotlin 的协程机制非常熟悉。

当然,对于可能出异常的情况,请大家尽量做好异常处理,不要将问题复杂化。

5. join 和 await

前面我们举例子一直用的是 launch,启动协程其实常用的还有 async、 actor 和 produce,其中 actor 和 launch 的行为类似,在未捕获的异常出现以后,会被当做为处理的异常抛出,就像前面的例子那样。而 async 和 produce 则主要是用来输出结果的,他们内部的异常只在外部消费他们的结果时抛出。这两组协程的启动器,你也可以认为分别是“消费者”和“生产者”,消费者异常立即抛出,生产者只有结果消费时抛出异常。

actor 和 produce 这两个 API 目前处于比较微妙的境地,可能会被废弃或者后续提供替代方案,不建议大家使用,我们在这里就不展开细讲了。

那么消费结果指的是什么呢?对于 async 来讲,就是 await,例如:

suspend fun main() {
    val deferred = GlobalScope.async<Int> { 
        throw ArithmeticException()
    }
    try {
        val value = deferred.await()
        log("1. $value")
    } catch (e: Exception) {
        log("2. $e")
    }
}

这个从逻辑上很好理解,我们调用 await 时,期望 deferred 能够给我们提供一个合适的结果,但它因为出异常,没有办法做到这一点,因此只好给我们丢出一个异常了。

13:25:14:693 [main] 2. java.lang.ArithmeticException

我们自己实现的 getUserCoroutine 也属于类似的情况,在获取结果时,如果请求出了异常,我们就只能拿到一个异常,而不是正常的结果。相比之下, join 就有趣的多了,它只关注是否执行完,至于是因为什么完成,它不关心,因此如果我们在这里替换成 join:

suspend fun main() {
    val deferred = GlobalScope.async<Int> {
        throw ArithmeticException()
    }
    try {
        deferred.join()
        log(1)
    } catch (e: Exception) {
        log("2. $e")
    }
}

我们就会发现,异常被吞掉了!

13:26:15:034 [main] 1

如果例子当中我们用 launch 替换 async, join 处仍然不会有任何异常抛出,还是那句话,它只关心有没有完成,至于怎么完成的它不关心。不同之处在于, launch 中未捕获的异常与 async 的处理方式不同, launch 会直接抛出给父协程,如果没有父协程(顶级作用域中)或者处于 supervisorScope 中父协程不响应,那么就交给上下文中指定的 CoroutineExceptionHandler处理,如果没有指定,那传给全局的 CoroutineExceptionHandler 等等,而 async 则要等 await 来消费。

不管是哪个启动器,在应用了作用域之后,都会按照作用域的语义进行异常扩散,进而触发相应的取消操作,对于 async 来说就算不调用 await 来获取这个异常,它也会在 coroutineScope 当中触发父协程的取消逻辑,这一点请大家注意。

6. 小结

这一篇我们讲了协程的异常处理。这一块儿稍微显得有点儿复杂,但仔细理一下主要有三条线:

  1. 协程内部异常处理流程:launch 会在内部出现未捕获的异常时尝试触发对父协程的取消,能否取消要看作用域的定义,如果取消成功,那么异常传递给父协程,否则传递给启动时上下文中配置的 CoroutineExceptionHandler 中,如果没有配置,会查找全局(JVM上)的 CoroutineExceptionHandler 进行处理,如果仍然没有,那么就将异常交给当前线程的 UncaughtExceptionHandler 处理;而 async 则在未捕获的异常出现时同样会尝试取消父协程,但不管是否能够取消成功都不会后其他后续的异常处理,直到用户主动调用 await 时将异常抛出。
  2. 异常在作用域内的传播:当协程出现异常时,会根据当前作用域触发异常传递,GlobalScope 会创建一个独立的作用域,所谓“自成一派”,而 在 coroutineScope 当中协程异常会触发父协程的取消,进而将整个协程作用域取消掉,如果对 coroutineScope 整体进行捕获,也可以捕获到该异常,所谓“一损俱损”;如果是 supervisorScope,那么子协程的异常不会向上传递,所谓“自作自受”。
  3. join 和 await 的不同:join 只关心协程是否执行完,await 则关心运行的结果,因此 join 在协程出现异常时也不会抛出该异常,而 await 则会;考虑到作用域的问题,如果协程抛异常,可能会导致父协程的取消,因此调用 join 时尽管不会对协程本身的异常进行抛出,但如果 join 调用所在的协程被取消,那么它会抛出取消异常,这一点需要留意。

如果大家能把这三点理解清楚了,那么协程的异常处理可以说就非常清晰了。文中因为异常传播的原因,我们提到了取消,但没有展开详细讨论,后面我们将会专门针对取消输出一篇文章,帮助大家加深理解。

附加说明

join 在父协程被取消时有一个 bug 会导致不抛出取消异常,我在准备本文时发现该问题,目前已经提交到官方并得到了修复,预计合入到 1.2.1 发版,大家有兴趣可以查看这个 issue:No CancellationException thrown when join on a crashed Job https://github.com/Kotlin/kotlinx.coroutines/issues/1123。

当然,这个 bug 对于生成环境的影响很小,大家也不要担心。

分享更多的Kotlin知识,需要的扫码免费领取!

《高级Kotlin强化实战 》

第一章 Kotlin 入门教程

​ ● Kotlin 概述

​ ● Kotlin 与 Java 比较

​ ● 巧用 Android Studio

​ ● 认识 Kotlin 基本类型

​ ● 走进 Kotlin 的数组

​ ● 走进 Kotlin 的集合

​ ● 完整代码

​ ● 基础语法

img

第二章 Kotlin 实战避坑指南

​ ● 方法入参是常量,不可修改

​ ● 不要 Companion、INSTANCE?

​ ● Java 重载,在 Kotlin 中怎么巧妙过渡一下?

​ ● Kotlin 中的判空姿势

​ ● Kotlin 复写 Java 父类中的方法

​ ● Kotlin “狠”起来,连TODO都不放过!

​ ● is、as` 中的坑

​ ● Kotlin 中的 Property 的理解

​ ● also 关键字

​ ● takeIf 关键字

​ ● 单例模式的写法

img

第三章 项目实战《Kotlin Jetpack 实战》

​ ● 从一个膜拜大神的 Demo 开始

​ ● Kotlin 写 Gradle 脚本是一种什么体验?

​ ● Kotlin 编程的三重境界

​ ● Kotlin 高阶函数

​ ● Kotlin 泛型

​ ● Kotlin 扩展

​ ● Kotlin 委托

​ ● 协程“不为人知”的调试技巧

​ ● 图解协程:suspend

《史上最详Android版kotlin协程入门进阶实战 》

第一章 Kotlin协程的基础介绍

​ ● 协程是什么

​ ● 什么是Job 、Deferred 、协程作用域

​ ● Kotlin协程的基础用法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zyX42Hxm-1681283765466)(https://docimg1.docs.qq.com/image/ME6Ygm_vFgAkpKvYo-TGIg.png?w=567&h=303)]

第二章 kotlin协程的关键知识点初步讲解

​ ● 协程调度器

​ ● 协程上下文

​ ● 协程启动模式

​ ● 协程作用域

​ ● 挂起函数

img

第三章 kotlin协程的异常处理

​ ● 协程异常的产生流程

​ ● 协程的异常处理

img

安卓开发中优化处理Retrofit协程请求中的异常 通过封装和统一处理Retrofit协程请求中的异常,我们可以提高代码的可维护性和错误处理的准确性。在移动端开发中,使用Retrofit库和协程来进行网络请求已经成为常见的做法。然而,在处理网络请求过程中,异常情况的处理是一个重要的方面。本文将介绍如何封装和处理Retrofit协程请求中的异常,以提高代码的可维护性和错误处理的准确性。例如,我们创建一个名为ApiService的接口,其中包含一个获取用户信息的GET请求方法。现在,我们可以在应用的其他地方使用封装好的请求方法,并处理请求结果。 阅读详情

相关推荐

【信息科学与工程学】计算机科学与自动化——第三百零四篇 高性能系统开发指南01

高并发:限流、熔断、降级高性能:红包拆分、多级缓存、大Key/热Key拆分、Pipeline、线程池、RocketMQ零丢失、幂等、分布式锁、集群缩容、分批入库海量数据处理:日志分表分片、跨表查询、冷热分层、大数据引擎服务器选型系统安全:网关安全、服务器安全、云存储安全、风控、监控、测试数据一致性:缓存与数据库一致性、大厂方案、蓝绿回滚、流量一致性、流程一致性项目与团队管理:流程问题、管理问题(风险、成本、人员、文档、培训)稳定性建设:上线三板斧、线上五步闭环、混沌工程、容量规划。

weixin_49199313的博客 251

Kotlin 协程 - 协程取消篇

协程的任务的取消需要靠协程内部调用的协作支持,这就类似于我们线程中断以及对中断状态的响应一样。 1. 线程的中断 我们先从大家熟悉的话题讲起。线程有一个被废弃的 stop 方法,这个方法会让线程立即死掉,并且释放它持有的锁,这样会让它正在读写的存储处于一个不安全的状态,因此 stop 被废弃了。如果我们启动了一个线程并让它执行一些任务,但很快我们就后悔了,stop 还不让用,那该怎么办? val thread = thread { ... } thread.stop() // !!! Depre.

pureChilder的博客 2876

OpenHarmony实战开发-如何实现文件分布式同步编辑

在分布式文件场景中,分布式设备管理包含了分布式设备搜索、分布式设备列表弹窗、远端设备拉起三部分。首先在分布式组网内搜索设备,然后把设备展示到分布式设备列表弹窗中,最后根据用户的选择拉起远端设备。两台设备组网的分布式场景是工作中常常需要的。这样的分布式场景有助于加快工作效率,减少工作中的冗余,本例将为大家介绍如何实现上述功能。​ (2)订阅分布式数据变化:通过this.distributedObject.on(‘status’, this.statusCallback)监听分布式数据对象的变更。

2401_82546228的博客 754

Kotlin协程-取消与超时

1.Job:取消协程 【类似于线程的cancle、join】 suspend fun main(args: Array<String>) { var job = GlobalScope.launch { repeat(1000, { println(it) delay(500) }) ...

大王叫我来巡山 1518

关于Vscode配置Unity环境时的一些报错问题(持续更新)

3.找到设置文件,添加如下代码,用于告诉VSCode)在处理 C# 项目时优先使用 C# 扩展来加载和管理项目,而不是使用 C# Dev Kit 或其他扩展。1.很可能是unity的插件版本问题,打开window > package manager将。2.很可能是项目文件的问题,建议unity打开edit > perfercenes。3.手动重装其他版本的.Net框架(去官网找,多下几个无所谓的)解决:(由于造成问题不一样,所以建议都尝试一次)下载net请求超时(一般都会超时很正常的)

bug制造机 1890

车道线检测1.0(Python+Opencv基础版)

车道线检测是指在道路上自动检测和识别车道线的过程。车道线是道路上的标记,用于指示车辆行驶的方向和位置。 通过对车道线进行检测和识别,自动驾驶和智能驾驶系统可以实现车辆的自动导航、车道保持、车道偏离警告等功能。

Dream_orz的博客 4265

【Kotlin 协程】协程底层实现 ③ ( 结构化并发 | MainScope 作用域 | 取消协程作用域 | Activity 实现 CoroutineScope 协程作用域接口 )

一、MainScope 协程作用域 二、取消 MainScope 协程作用域 三、Activity 实现 CoroutineScope 协程作用域接口

让 学习 成为一种 习惯 ( 韩曙亮 の 技术博客 ) 2814

使用 Python 爬取 YouTube 视频评论(弹幕)

✅ 如何使用 Python 调用 YouTube Data API v3。✅ 如何爬取指定视频的所有评论信息。✅ 如何对评论数据进行初步分析与可视化。✅ 如何扩展功能,实现评论回复获取和多视频批量爬取。

2201_76125261的博客 645

kotlin--协程上下文、异常处理

从前面我们可以大致了解了协程的玩法,如果一个协程中使用子协程,那么该协程会等待子协程执行结束后才真正退出,而达到这种效果的原因就是协程上下文,上下文贯穿了协程的生命周期,这套思想和我们app的上下文很像 在开始真正了解协程上下文之前,我们先来看看下面的例子 下面的图代表了一个协程a的生命,就像一条从上至下的直线,它的生命只有100ms 当我们在a协程延迟函数100ms之前开启一个子协程b...

aruba的博客 781

RH850从0搭建Autosar开发环境【2X】- Davinci Configurator之EcuC模块配置详解(上)

RH850从0搭建Autosar开发环境【2X】- Davinci Configurator之EcuC模块配置详解(上)

weixin_42412049的博客 1531

结构化数据特征构建神器openfe的实战代码

最近订阅专栏的几位同学一起参加了AI比赛,该比赛的数据形式是结构化的。随着特征和模型的不断迭代优化,达到了一个难以跨越的瓶颈。希望我能够给出一些有效的方法,帮助他们提高成绩。在个人的调研和体验后,向他们推荐了结构化数据特征构建神器openfe,最终也帮助他们提升了十来名的成绩,个人也感到十分的欣慰。特意写此博客,希望能够帮助更多的同学。 文章目录 1. 安装 2. 实战例子一 3. 实战例子二

herosunly的博客 3万+

阿里P7大佬教你破解 Kotlin 协程(5) - 协程取消篇

​ ● 协程是什么​ ● 什么是Job 、Deferred 、协程作用域​ ● Kotlin协程的基础用法。

Android_XG的博客 535

Claude Code 100个真实案例 - 用AI搭建GIS地理信息系统(地图标注+热力图)

本案例展示如何使用 Claude Code 从零搭建一个完整的 GIS 地理信息系统。我们将使用 Python + Flask 作为后端,Leaflet.js 作为前端地图引擎,实现地图标注、热力图展示、区域绘制、坐标搜索等功能。全程通过与 Claude Code 对话完成,让你感受 AI 在地理信息领域的强大开发能力。

yueyeguzhuo的专栏 113

阿里P7大佬教你 Kotlin 协程(1) - 入门篇

Kotlin 协程(1) - 入门篇

Android_XG的博客 354

CH7034B显示模块原理图设计,已量产

显示桥接板的价值,不在于简单把 RGB 排线换成另一种接口,而在于能否把并行视频、时钟、显示控制、模拟输出和辅助配置同时组织成稳定系统。当前这张 CH7034B 原理图就是这种思路:U3 CH7034B 作为核心桥接芯片,输入侧是 LCD_R0~R7、LCD_G0~G7、LCD_B0~B7、LCD_DE、LCD_HSYNC、LCD_VSYNC、LCD_CLK 与 LCD_BL,输出侧则通过 DAC0~DAC2 和 DDC_SC/DDC_SD 形成模拟显示接口边界。

不脱发的程序猿 165

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

分享两道阿里P7究极难度算法题,快来收藏!

缘起 随着互联网企业的不断发展,产品项目中的模块越来越多,用户体验要求也越来越高,想实现小步快跑、快速迭代的目的越来越难,还有应用之间的互相调用等等问题,插件化技术应用而生。如果没有插件化技术,美团、淘宝这些集成了大量“app”的应用,可能会有几个g那么大。 所以,当今的Android移动开发,不会热修复、插件化、组件化,80%以上的面试都过不了。 阿里P8大佬每天熬夜到凌晨一两点,花了将近半个月时间将Android热修复框架、插件化框架、组件化框架、图片加载框架、网络访问框架、RxJava响应式编程框架、

左夜天 139

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

原理竟然是这!分享两道阿里P7究极难度算法题,快来收藏!

缘起 随着互联网企业的不断发展,产品项目中的模块越来越多,用户体验要求也越来越高,想实现小步快跑、快速迭代的目的越来越难,还有应用之间的互相调用等等问题,插件化技术应用而生。如果没有插件化技术,美团、淘宝这些集成了大量“app”的应用,可能会有几个g那么大。 所以,当今的Android移动开发,不会热修复、插件化、组件化,80%以上的面试都过不了。 阿里P8大佬每天熬夜到凌晨一两点,花了将近半个月时间将Android热修复框架、插件化框架、组件化框架、图片加载框架、网络访问框架、RxJava响应式编程框架、

m0_56255252的博客 161

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

kotlin编程实践pdf,Jetpack-MVVM-高频提问和解答

自我介绍一下,小编13年北京科技大学毕业,曾经在字节跳动待过,也去过vivo、小米等大厂,18年进入华为一直到现在。小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担,能在岗位上再创辉煌,步步高升!!。

2401_84903191的博客 699

安卓系统应用程序开发!灵魂一问-如何彻底防止APK反编译?大厂面试题汇总

缘起 随着互联网企业的不断发展,产品项目中的模块越来越多,用户体验要求也越来越高,想实现小步快跑、快速迭代的目的越来越难,还有65535,应用之间的互相调用等等问题,插件化技术应用而生。如果没有插件化技术,美团、淘宝这些集成了大量“app”的应用,可能会有几个g那么大。 所以,当今的Android移动开发,不会热修复、插件化、组件化,80%以上的面试都过不了。 阿里P8大佬每天熬夜到凌晨一两点,花了将近半个月时间将Android热修复框架、插件化框架、组件化框架、图片加载框架、网络访问框架、RxJava响应

335

37岁Android程序员裸辞,四个月被497家公司拒绝,问猎头后懵了

分享一份工作1到5年以上的Android程序员架构进阶学习路线体系,希望能对那些还在从事Android开发却还不知道如何去提升自己的,还处于迷茫的朋友!阿里P7级Android架构师技术脑图;查漏补缺,体系化深入学习提升**全套体系化高级架构视频;**七大主流技术模块,视频+源码+笔记有任何问题,欢迎广大网友一起来交流《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

2301_79098336的博客 1068

爬虫程序优化要点_爬虫程序的改进措施有哪些内容(1),网络安全快速转战Kotlin教程

Python编程学习,学习内容包含:语法、正则、文件、 网络、多线程等常用库,推荐《Python核心编程》,不要看完;在实际的渗透测试过程中,面对复杂多变的网络环境,当常用工具不能满足实际需求的时候,往往需要对现有工具进行扩展,或者编写符合我们要求的工具、自动化脚本,这个时候就需要具备一定的编程能力。分析目标服务器的爬虫策略,控制爬虫请求频率甚至包括验证码、加密数据的破解,同时使用优质代理或爬虫代理,寻找业务独享、网络稳定、高并发、低延迟的代理产品,确保目标服务器没法进行反爬限制及预警,

qd520_1314的博客 978

学习协程2: 取消和超时

如果要处理的任务属于 1) CPU 密集型,2) 可能会耗尽线程池资源,3) 需要在不向线程池中添加更多线程的前提下允许线程处理其他任务,那么请使用 yield()。中的超时事件相对于在其块中运行的代码是异步的,并且可以在任何时间发生,甚至在从超时块内部返回之前发生。但是,如果协程在计算中工作并且不检查取消,则无法取消。对于长时间运行的程序,需要进行粒度控制,在合适的时间结束协程。块,如果需要在任何类型的超时时执行一些额外的操作,或者使用与。,但是,如果协程在计算中工作并且不检查取消,则无法取消。

qq_40194724的博客 1160

Rxjava2-Single

Rxjava2-Single Single .create(new SingleOnSubscribe<Integer>() { @Override public void subscribe(SingleEmitter<Integer> emitter) thr...

相公 1703

解决使用kotlin编写OkHttp的网络请求时遇到的OOM问题

一、背景介绍: 问题的出现是因为我的代码中需要进行网络请求,而网络请求又是在一个线程中,间隔几秒就会请求一次,为了方便,就是用的OkHttp进行网络访问,本来,是没问题的,谁知道,某次测试时间长了一点,然后apk就突然闪退了,看了下log,是因为大量创建了线程,栈被撑爆了,OOM,遂卒~,通过网上找资料,最终查明原因是OkHttp的client没有进行单实例封装,每发一次请求,就会创建一个线程池,最终OOM,下面就这个问题,单独写了一个demo进行问题模拟及解决。 二、模拟复现: 1.OkHttp的异步访问

achina2011jy的博客 2029

【Kotlin 协程】协程取消 ① ( 协程作用域取消 | 协程作用域子协程取消 | 通过抛出异常取消协程 | Job#cancel 函数 | 自定义异常取消协程 )

一、协程取消、 二、协程作用域取消、 三、协程作用域子协程取消、 四、通过抛出异常取消协程、 1、Job#cancel 函数、 2、默认异常取消协程、 3、自定义异常取消协程、

让 学习 成为一种 习惯 ( 韩曙亮 の 技术博客 ) 2835
上一篇: 华为“不造车”,只大力研发车载OTA,车载OTA有何魅力
下一篇: Framework 面试过不了?Android 字节跳动大牛的解析字节面试官常问的 Framework 问题
不秃头de程序猿
博客等级 码龄5年 4089粉丝 1478原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值