破解 Kotlin 协程(8) - Android 篇

Android 使用 kotlin 协程 我写这篇blog的原因是网上很多都是用的coroutines旧版本,但正式版本进行了修改。因为我也是才开始学习coroutines,因此只涉及使用。 第一步:添加依赖 implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.0' 在我写的时候,kotlinx-coroutines-android:1.1.0版本,是... 阅读详情

破解 Kotlin 协程(8) - Android 篇

在这里插入图片描述

关键词:Kotlin 协程 Android Anko

Android 上面使用协程来替代回调或者 RxJava 实际上是一件非常轻松的事儿,我们甚至可以在更大的范围内结合 UI 的生命周期做控制协程的执行状态~

1. 配置依赖

我们曾经提到过,如果在 Android 上做开发,那么我们需要引入

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutine_version'

复制

这个框架里面包含了 Android 专属的 Dispatcher,我们可以通过 Dispatchers.Main 来拿到这个实例;也包含了 MainScope,用于与 Android 作用域相结合。

Anko 也提供了一些比较方便的方法,例如 onClick 等等,如果需要,也可以引入它的依赖:

//提供 onClick 类似的便捷的 listener,接收 suspend Lambda 表达式
implementation "org.jetbrains.anko:anko-sdk27-coroutines:$anko_version"
//提供 bg 、asReference,尚未没有跟进 kotlin 1.3 的正式版协程,不过代码比较简单,如果需要可以自己改造
implementation "org.jetbrains.anko:anko-coroutines:$anko_version"

复制

简单来说:

  • kotlinx-coroutines-android 这个框架是必选项,主要提供了专属调度器
  • anko-sdk27-coroutines 是可选项,提供了一些 UI 组件更为简洁的扩展,例如 onClick,但它也有自己的问题,我们后面详细探讨
  • anko-coroutines 仅供参考,现阶段(2019.4)由于尚未跟进 1.3 正式版协程,因此在 1.3 之后的版本中尽量不要使用,提供的两个方法都比较简单,如果需要,可自行改造使用。

协程的原理和用法我们已经探讨了很多了,关于 Android 上面的协程使用,我们就只给出几点实践的建议。

2. UI 生命周期作用域

Android 开发经常想到的一点就是让发出去的请求能够在当前 UI 或者 Activity 退出或者销毁的时候能够自动取消,我们在用 RxJava 的时候也有过各种各样的方案来解决这个问题。

2.1 使用 MainScope

协程有一个很天然的特性能刚够支持这一点,那就是作用域。官方也提供了 MainScope 这个函数,我们具体看下它的使用方法:

val mainScope = MainScope()
launchButton.setOnClickListener {
    mainScope.launch {
        log(1)
        textView.text = async(Dispatchers.IO) {
            log(2)
            delay(1000)
            log(3)
            "Hello1111"
        }.await()
        log(4)
    }
}

我们发现它其实与其他的 CoroutineScope 用起来没什么不一样的地方,通过同一个叫 mainScope 的实例启动的协程,都会遵循它的作用域定义,那么 MainScope 的定义时怎样的呢?

public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)

原来就是 SupervisorJob 整合了 Dispatchers.Main 而已,它的异常传播是自上而下的,这一点与 supervisorScope 的行为一致,此外,作用域内的调度是基于 Android 主线程的调度器的,因此作用域内除非明确声明调度器,协程体都调度在主线程执行。因此上述示例的运行结果如下:

2019-04-29 06:51:00.657 D: [main] 1
2019-04-29 06:51:00.659 D: [DefaultDispatcher-worker-1] 2
2019-04-29 06:51:01.662 D: [DefaultDispatcher-worker-2] 3
2019-04-29 06:51:01.664 D: [main] 4

如果我们在触发前面的操作之后立即在其他位置触发作用域的取消,那么该作用域内的协程将不再继续执行:

val mainScope = MainScope()

launchButton.setOnClickListener {
    mainScope.launch {
        ...
    }
}

cancelButton.setOnClickListener {
    mainScope.cancel()
    log("MainScope is cancelled.")
}

如果我们快速依次点击上面的两个按钮,结果就显而易见了:

2019-04-29 07:12:20.625 D: [main] 1
2019-04-29 07:12:20.629 D: [DefaultDispatcher-worker-2] 2
2019-04-29 07:12:21.046 D: [main] MainScope is cancelled.

2.2 构造带有作用域的抽象 Activity

尽管我们前面体验了 MainScope 发现它可以很方便的控制所有它范围内的协程的取消,以及能够无缝将异步任务切回主线程,这都是我们想要的特性,不过写法上还是不够美观。

官方推荐我们定义一个抽象的 Activity,例如:

abstract class ScopedActivity: Activity(), CoroutineScope by MainScope(){
    override fun onDestroy() {
        super.onDestroy()
        cancel()
    }
}

这样在 Activity 退出的时候,对应的作用域就会被取消,所有在该 Activity 中发起的请求都会被取消掉。使用时,只需要继承这个抽象类即可:

class CoroutineActivity : ScopedActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_coroutine)
        launchButton.setOnClickListener {
            launch { // 直接调用 ScopedActivity 也就是 MainScope 的方法
                ...
            }
        }
    }

    suspend fun anotherOps() = coroutineScope {
        ...
    }
}

除了在当前 Activity 内部获得 MainScope 的能力外,还可以将这个 Scope 实例传递给其他需要的模块,例如 Presenter 通常也需要与 Activity 保持同样的生命周期,因此必要时也可以将该作用域传递过去:

class CoroutinePresenter(private val scope: CoroutineScope): CoroutineScope by scope{
    fun getUserData(){
        launch { ... }
    }
}

多数情况下, Presenter 的方法也会被 Activity 直接调用,因此也可以将 Presenter 的方法生命成 suspend 方法,然后用 coroutineScope 嵌套作用域,这样 MainScope 被取消后,嵌套的子作用域一样也会被取消,进而达到取消全部子协程的目的:

class CoroutinePresenter {
    suspend fun getUserData() = coroutineScope {
        launch { ... }
    }
}

2.3 更友好地为 Activity 提供作用域

抽象类很多时候会打破我们的继承体系,这对于开发体验的伤害还是很大的,因此我们是不是可以考虑构造一个接口,只要 Activity 实现这个接口就可以拥有作用域以及自动取消的能力呢?

首先我们定义一个接口:

interface ScopedActivity {
    val scope: CoroutineScope
}

我们有一个朴实的愿望就是希望实现这个接口就可以自动获得作用域,不过问题来了,这个 scope 成员要怎么实现呢?留给接口实现方的话显然不是很理想,自己实现吧,又碍于自己是个接口,因此我们只能这样处理:

interface MainScoped {
    companion object {
        internal val scopeMap = IdentityHashMap<MainScoped, MainScope>()
    }
    val mainScope: CoroutineScope
        get() = scopeMap[this as Activity]!!
}

接下来的事情就是在合适的实际去创建和取消对应的作用域了,我们接着定义两个方法:

interface MainScoped {
    ...
    fun createScope(){
        //或者改为 lazy 实现,即用到时再创建
        val activity = this as Activity
        scopeMap[activity] ?: MainScope().also { scopeMap[activity] = it }
    }

    fun destroyScope(){
        scopeMap.remove(this as Activity)?.cancel()
    }
}

因为我们需要 Activity 去实现这个接口,因此直接强转即可,当然如果考虑健壮性,可以做一些异常处理,这里作为示例仅提供核心实现。

接下来就是考虑在哪儿完成创建和取消呢?显然这件事儿用 Application.ActivityLifecycleCallbacks最合适不过了:

class ActivityLifecycleCallbackImpl: Application.ActivityLifecycleCallbacks {
    ...
    override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
        (activity as? MainScoped)?.createScope()
    }

    override fun onActivityDestroyed(activity: Activity) {
        (activity as? MainScoped)?.destroyScope()
    }
}

剩下的就是在 Application 里面注册一下这个监听了,这个大家都会,我就不给出代码了。

我们看下如何使用:

class CoroutineActivity : Activity(), MainScoped {
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        launchButton.setOnClickListener {            
            scope.launch {
                ...
            }
        }
    }
}

我们也可以增加一些有用的方法来简化这个操作:

interface MainScoped {
    ...
    fun <T> withScope(block: CoroutineScope.() -> T) = with(scope, block)
}

这样在 Activity 当中还可以这样写:

withScope {
    launch { ... }
}

注意,示例当中用到了 IdentityHashMap,这表明对于 scope 的读写是非线程安全的,因此不要在其他线程试图去获取它的值,除非你引入第三方或者自己实现一个 IdentityConcurrentHashMap,即便如此,从设计上 scope也不太应该在其他线程访问。

按照这个思路,我提供了一套更加完善的方案,不仅支持 Activity 还支持 support-fragment 版本在 25.1.0 以上的版本的 Fragment,并且类似于 Anko 提供了一些有用的基于 MainScope 的 listener 扩展,源码地址:kotlin-coroutines-android(https://github.com/enbandari/kotlin-coroutines-android),引入这个框架即可使用:

api 'com.bennyhuo.kotlin:coroutines-android-mainscope:1.0'

3. 谨慎使用 GlobalScope

3.1 GlobalScope 存在什么问题

我们之前做例子经常使用 GlobalScope,但 GlobalScope 不会继承外部作用域,因此大家使用时一定要注意,如果在使用了绑定生命周期的 MainScope 之后,内部再使用 GlobalScope 启动协程,意味着 MainScope 就不会起到应有的作用。

这里需要小心的是如果使用了一些没有依赖作用域的构造器,那么一定要小心。例如 Anko 当中的 onClick扩展:

fun View.onClick(
        context: CoroutineContext = Dispatchers.Main,
        handler: suspend CoroutineScope.(v: View) -> Unit
) {
    setOnClickListener { v ->
        GlobalScope.launch(context, CoroutineStart.DEFAULT) {
            handler(v)
        }
    }
}

也许我们也就是图个方便,毕竟 onClick 写起来可比 setOnClickListener 要少很多字符,同时名称上看也更加有事件机制的味道,但隐藏的风险就是通过 onClick 启动的协程并不会随着 Activity 的销毁而被取消,其中的风险需要自己思考清楚。

当然,Anko 会这么做的根本原因在于 OnClickListener 根本拿不到有生命周期加持的作用域。不用 GlobalScope 就无法启动协程,怎么办?结合我们前面给出的例子,其实这个事儿完全有别的解法:

interface MainScoped {
    ...
    fun View.onClickSuspend(handler: suspend CoroutineScope.(v: View) -> Unit) {
        setOnClickListener { v ->
            scope.launch {   handler(v)   }
        }
    }
}

我们在前面定义的 MainScoped 接口中,可以通过 scope 拿到有生命周期加持的 MainScope 实例,那么直接用它启动协程来运行 OnClickListener 问题不就解决了嘛。所以这里的关键点在于如何拿到作用域。

这样的 listener 我已经为大家在框架中定义好啦,请参见 2.3。

3.2 协程版 AutoDisposable

当然除了直接使用一个合适的作用域来启动协程之外,我们还有别的办法来确保协程及时被取消。

大家一定用过 RxJava,也一定知道用 RxJava 发了个任务,任务还没结束页面就被关闭了,如果任务迟迟不回来,页面就会被泄露;如果任务后面回来了,执行回调更新 UI 的时候也会大概率空指针。

因此大家一定会用到 Uber 的开源框架 AutoDispose(https://github.com/uber/AutoDispose)。它其实就是利用 View 的 OnAttachStateChangeListener ,当 View 被拿下的时候,我们就取消所有之前用 RxJava 发出去的请求。

static final class Listener extends MainThreadDisposable implements View.OnAttachStateChangeListener {
    private final View view;
    private final CompletableObserver observer;

    Listener(View view, CompletableObserver observer) {
      this.view = view;
      this.observer = observer;
    }

    @Override public void onViewAttachedToWindow(View v) { }

    @Override public void onViewDetachedFromWindow(View v) {
      if (!isDisposed()) {
      //看到没看到没看到没?
        observer.onComplete();
      }
    }

    @Override protected void onDispose() {
      view.removeOnAttachStateChangeListener(this);
    }
  }

考虑到前面提到的 Anko 扩展 onClick 无法取消协程的问题,我们也可以搞一个 onClickAutoDisposable。

fun View.onClickAutoDisposable (
        context: CoroutineContext = Dispatchers.Main,
        handler: suspend CoroutineScope.(v: View) -> Unit
) {
    setOnClickListener { v ->
        GlobalScope.launch(context, CoroutineStart.DEFAULT) {
            handler(v)
        }.asAutoDisposable(v)
    }
}

我们知道 launch 会启动一个 Job,因此我们可以通过 asAutoDisposable 来将其转换成支持自动取消的类型:

fun Job.asAutoDisposable(view: View) = AutoDisposableJob(view, this)

复制

那么 AutoDisposableJob 的实现只要参考 AutoDisposable 的实现依样画葫芦就好了 :

class AutoDisposableJob(private val view: View, private val wrapped: Job)
    //我们实现了 Job 这个接口,但没有直接实现它的方法,而是用 wrapped 这个成员去代理这个接口
     : Job by wrapped, OnAttachStateChangeListener {
    override fun onViewAttachedToWindow(v: View?) = Unit

    override fun onViewDetachedFromWindow(v: View?) {
        //当 View 被移除的时候,取消协程
        cancel()
        view.removeOnAttachStateChangeListener(this)
    }

    private fun isViewAttached() =
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && view.isAttachedToWindow || view.windowToken != null

    init {
        if(isViewAttached()) {
            view.addOnAttachStateChangeListener(this)
        } else {
            cancel()
        }

        //协程执行完毕时要及时移除 listener 免得造成泄露
        invokeOnCompletion() {
            view.removeOnAttachStateChangeListener(this)
        }
    }
}

这样的话,我们就可以使用这个扩展了:

button.onClickAutoDisposable{
    try {
        val req = Request()
        val resp = async { sendRequest(req) }.await()
        updateUI(resp)
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

当 button 这个对象从 window 上撤下来的时候,我们的协程就会收到 cancel 的指令,尽管这种情况下协程的执行不会跟随 Activity 的 onDestroy 而取消,但它与 View 的点击事件紧密结合,即便 Activity 没有被销毁, View 本身被移除时也会直接将监听中的协程取消掉。

如果大家想要用这个扩展,我已经帮大家放到 jcenter 啦,直接使用:

api "com.bennyhuo.kotlin:coroutines-android-autodisposable:1.0"

复制

添加到依赖当中即可使用,源码同样在这里:kotlin-coroutines-android(https://github.com/enbandari/kotlin-coroutines-android),别忘了 star 一波哟。

4. 合理使用调度器

在 Android 上使用协程,更多的就是简化异步逻辑的写法,使用场景更多与 RxJava 类似。在使用 RxJava 的时候,我就发现有不少开发者仅仅用到了它的切线程的功能,而且由于本身 RxJava 切线程 API 简单易用,还会造成很多无脑线程切换的操作,这样实际上是不好的。那么使用协程就更要注意这个问题了,因为协程切换线程的方式被 RxJava 更简洁,更透明,本来这是好事情,就怕被滥用。

比较推荐的写法是,绝大多数 UI 逻辑在 UI 线程中处理,即使在 UI 中用 Dispatchers.Main 来启动协程,如果涉及到一些 io 操作,使用 async 将其调度到 Dispatchers.IO 上,结果返回时协程会帮我们切回到主线程——这非常类似 Nodejs 这样的单线程的工作模式。

对于一些 UI 不相关的逻辑,例如批量离线数据下载任务,通常默认的调度器就足够使用了。

5. 小结

这一篇文章,主要是基于我们前面讲了的理论知识,进一步往 Android 的具体实战角度迁移,相比其他类型的应用,Android 作为 UI 程序最大的特点就是异步要协调好 UI 的生命周期,协程也不例外。一旦我们把协程的作用域规则以及协程与 UI 生命周期的关系熟稔于心,那么相信大家使用协程时一定会得心应手的。

Kotlin 协程学习资料可以扫描下方二维码免费领取!

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

第一章 Kotlin协程的基础介绍

            ● 协程是什么

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

            ● Kotlin协程的基础用法

img

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

            ● 协程调度器

            ● 协程上下文

            ● 协程启动模式

            ● 协程作用域

            ● 挂起函数

img

第三章 kotlin协程的异常处理

            ● 协程异常的产生流程

            ● 协程的异常处理

img

第四章 kotlin协程在Android中的基础应用

            ● Android使用kotlin协程

            ● 在Activity与Framgent中使用协程

            ● ViewModel中使用协程

            ● 其他环境下使用协程

img

第五章 kotlin协程的网络请求封装

            ● 协程的常用环境

            ● 协程在网络请求下的封装及使用

            ● 高阶函数方式

            ● 多状态函数返回值方式
Kotlin:lifecycleScope与GlobalScope以及MainScope的区别,详细分析为什么在Android中推荐使用lifecycleScope! 目录简要GlobalScopeMainScope()lifecycleScopelifecycleScope剩余问题分析(感兴趣的可以继续看) 简要 首先简要介绍一下kotlin协程作用域的三种类型。 类型 产生方式 异常传播特征 顶级作用域 GlobalScope创建 异常不向外传播。异常到达顶级作用域后,如果还没有被处理,会抛给当前的exceptionHandler,如果没有则给当前线程的uncaughtExceptionHandler 协同作用域 Job嵌套、coroutineSc 阅读详情

相关推荐

Android开发者用Cursor写Kotlin的真实体验与避坑指南

Kotlin作为强类型、高表达力的JVM语言,其协程、DSL和编译器插件生态为AI编程工具带来独特机遇与挑战。AI代码生成的核心原理是基于上下文窗口的模式匹配与语法补全,技术价值在于显著降低样板代码编写成本,尤其在数据转换、算法实现等逻辑层;但在Android框架集成场景(如lifecycleScope绑定、Room注解、WorkManager调度)中,因缺乏完整项目语义理解而易出错。典型应用场景包括快速构建Repository骨架、生成Excel解析逻辑、补全Flow操作符等,但必须配合人工核验关键生命周

weixin_34059951的博客 771

Android Kotlin 协程(coroutine )入门篇

ps:文章会很长 协同程序(coroutine)实在是太好用了,所以我们来学习吧!

我怀里的猫的博客 9470

安卓原生开发为何是初创企业的生存基础设施

安卓原生开发是移动应用开发的基础技术路径,指基于Android SDK、直接调用系统API构建高性能、高可控性客户端的能力。其核心原理在于绕过Web容器或跨端中间层,实现对硬件传感器、后台服务、离线存储及厂商推送等底层能力的稳定访问。相比H5、小程序或跨平台方案,原生开发在弱网适配、消息送达率、启动性能和隐私合规等方面具备不可替代的技术价值。典型应用场景包括社区团购实时订单同步、智能硬件蓝牙联动、车载Android Auto集成、金融类App安全保活等强交互、低延迟、高可靠性需求领域。对于资源有限的初创企业

weixin_30776545的博客 452

Android(Java)项目支持Kotlin语言开发

在项目根目录下build.gradle配置,增加对Kotlin 1.9.20版本的支持:id ‘org.jetbrains.kotlin.android’ version “1.9.20” apply false。id ‘org.jetbrains.kotlin.android’ version “1.9.20” apply false //支持Kotlin。id ‘org.jetbrains.kotlin.android’ //支持Kotlin。// 支持 Java 8。

董云飞的博客 3025

Grok Bot安卓开放预注册,用Kotlin实现AI对话客户端

AI对话助手正从网页工具演变为移动端常驻应用,这种形态迁移对开发者意味着新的技术挑战。理解流式对话在弱网下的稳定性、会话本地缓存与断线恢复机制,是构建安卓AI Bot应用的基础。基于SSE协议的大模型接口接入、Kotlin协程与StateFlow驱动的消息渲染,构成了此类应用的典型技术架构。从预注册机制看产品验证逻辑,用极简代码实现完整的对话链路,可以帮助开发者快速掌握移动端接入大模型API的核心方法。无论你的目标是参与Grok Bot预注册,还是自主开发一款AI闲聊助手,理解这套工程范式都能让你更从容地应

weixin_34405557的博客 401

安卓逆向工程实战:从CrackMes入门到工具链与破解思路详解

逆向工程是分析软件内部工作原理、理解其逻辑与实现细节的关键技术,广泛应用于软件安全评估、漏洞挖掘与CTF竞赛等领域。其核心原理在于通过静态与动态分析相结合的方法,解析程序的代码结构与运行时行为。掌握这项技术对于提升软件安全防护能力、进行合法安全测试具有重要价值。在移动安全领域,安卓应用逆向是常见应用场景,常涉及对APK文件、DEX字节码及Native层代码的分析。本文聚焦于通过CrackMes(破解练习)这一实战平台,系统介绍安卓逆向的完整工具链与核心破解思路,其中将重点阐述如何运用Frida进行动态插桩以

weixin_33858485的博客 361

LCG项目中的协程应用:Kotlin协程在Android开发中的10个高效实践

Kotlin协程作为Android开发中的异步编程利器,在LCG项目中得到了充分的应用和验证。LCG项目是一个基于MVVM架构的吾爱破解论坛第三方Android应用,它巧妙地将Kotlin协程与Android组件结合,实现了流畅的用户体验和高效的网络请求处理。本文将深入探讨LCG项目中协程的实际应用场景,并分享10个值得借鉴的协程实践技巧。 ## 🚀 为什么LCG项目选择Kotlin协程?

gitblog_00590的博客 394

了解安卓,了解JDK、SDK、NDK,了解gradle

Android是一个开源的,基于Linux的自由及开放源代码的操作系统。主要使用于移动设备,如智能手机和平板电脑。

qq_45926254的博客 4932

阿里P7大佬教你破解 Kotlin 协程(7) - 序列生成器篇

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

Android_XG的博客 211

Kotlin 协程:从基础概念到开发实践

上一篇文章深入理解Android多线程开发:场景应用与解决方案解析针对Android开发中的多线程应用场景和相应的解决方案做了一个梳理。资源复用和优化切线程任务编排并结合示例说明了Kotlin协程在处理上述问题时的优势。Kotlin协程自从2018年底成为kotlin语言的正式特性后,到现在已经5个年头了。而kotlin这门语言也在最近正式推出了2.0版本,带来了更好的支持多平台开发以及更快的编译速度。我自己算是比较早就开始尝试使用协程来解决开发中的各种需求。

喻志强的博客 1635

Modbus TCP 通信(C#)

【代码】Modbus TCP 通信(C#)

weixin_56229359的博客 1151

LCG吾爱破解论坛APP:开源Android客户端全面评测与使用指南

LCG吾爱破解论坛APP是一款专为技术爱好者打造的**开源Android客户端应用**,提供了浏览、评论、收藏等完整功能。作为**吾爱破解论坛第三方APP**,这款开源Android应用让用户在移动端也能享受完整的技术交流体验。 ## 📱 应用简介与核心功能 LCG是一款基于Kotlin开发的**开源Android客户端**,采用MVVM架构设计,界面参考了知乎、虎扑等主流应用。这款**吾爱

gitblog_00512的博客 613

Rockchip rv1126平台在gstreamer1中添加OpenCv实现视频流负片的效果

rv1126 gstreamer opencv negetive effect

Aisy_D的专栏 1463

LCG项目技术栈详解:Coil替换Glide、HtmlTextView重写、AppGuard崩溃重启机制

**LCG项目**作为吾爱破解论坛的第三方Android应用,采用了现代化的技术栈来提升用户体验和稳定性。本文将深入解析该项目的三个关键技术点:使用Coil替代Glide进行图片加载优化、重写HtmlTextView实现富文本渲染,以及AppGuard崩溃自动重启机制。无论你是Android开发新手还是经验丰富的开发者,这些技术方案都值得学习和借鉴。 ## 📱 Coil替换Glide:图片加载

gitblog_01084的博客 567

Win11只读模式无法关闭的终极解决方案

通过以上方法,我们应该能够解决Windows 11中文件夹无法取消只读状态的问题。如果问题仍然存在,请确保你的账户具有足够的权限来修改文件夹属性,并考虑是否有其他安全软件或策略阻止了权限更改。在操作过程中,请务必小心谨慎,避免误操作导致数据丢失或系统损坏。希望本文能够帮助到大家!

mmoo_python的博客 1万+

PX4实战指南:通过遥控器通道映射实现多舵机PWM精准控制

本文详细介绍了通过PX4飞控的AUX PWM接口实现遥控器通道映射控制多舵机的实战指南。内容包括硬件选型与连接、软件参数配置、通道映射方法以及常见问题排查,帮助开发者精准实现无人机舵机PWM控制,适用于云台、机械臂等扩展功能开发。

ee345的博客 304

Kotlin实现高效验证码识别:OCR与深度学习方案详解

验证码识别是反爬虫与自动化测试的核心技术,涉及OCR光学字符识别和深度学习等关键技术。传统方案多采用Python,但在JVM生态中Kotlin凭借协程并发优势和Java互操作性展现出更高性能。通过集成Tesseract OCR引擎和TensorFlow Lite,开发者可以构建多引擎融合的验证码识别系统。在工程实践中,图像预处理(如二值化、字符分割)和性能优化(如缓存机制)对提升识别率至关重要。Kotlin协程特性特别适合高并发验证码处理场景,实测显示其执行效率比Python快2-3倍,在电商爬虫等需要实时

cunfuteng7334的博客 502

第21章 分层架构软件测试

技术人员和客户代表对代码服务相关的技术进行详细的交流,由此确定代码审计的方案(哪些代码要审计、用什么方式审计、审计的时间、审计的要求等)代码审计报告提交和沟通之后,跟开发人员针对代码审查发现的问题进行修改,然后代码审查人员进行回归的检查,然后提交复查的报告;本层不需要了解其它层的实现细节,只需要考虑与本层相关的两层之间的接口和调用的情况。然后对客户要求的功能点进行人工的代码审查,对源代码的扫描结果进行人工的分析和确认;结合自动化扫描的结果和人工审查的结果生成测试对象的代码审查报告,最后提交给客户;

李黏黏的博客 3272

开发8年的老Android才知道,写给正在求职的安卓开发

前言 最近发现大家都喜欢看面试相关的文章,我也跟一波风,总结了一下我面试中所遇到的问题总结,分享一下面试中被问的最多的一些问题。 希望对正在找工作的朋友提供一些帮助。 好了话不多说,进入正题。 从初中级到高级,移动端程序员的进阶宝典 想要成为一名优秀的Android开发,你需要一份完备的 知识体系,在这里,让我们一起成长为自己所想的那样。 下面我们就以 Android 开发为例,从硬技能和软技能两方面,梳理大前端趋势下移动端程序员从初级到高级进阶所需的技能点,并且推荐一些实用的书籍资料。 (一) 硬技能:

m0_56051002的博客 146

异步bus交互(一)— 两级DFF同步器

异步bus交互(一)— 两级DFF同步器 跨时钟域处理 & 亚稳态处理 1.问题产生 现在的芯片(比如SOC,片上系统)集成度和复杂度越来越高,通常一颗芯片上会有许多不同的信号工作在不同的时钟频率下。比如SOC芯片中的CPU通常会工作在一个频率上,总线信号(比如DRAM BUS)会工作在另一个时钟频率下,而普通的信号又会工作在另外的时钟频率下。这3个不同时钟频率下工作的信号往往需要相互沟通和传递信号。 不同时钟域下的信号传递就涉及到跨时钟域信号处理,因为相互之间的频率、相位不一样,如果不做处理或者

qq_44816673的博客 5121

Materials studio中的简单聚合物的建立及盒子的弛豫

涉及模块 Amorphous Cell-construction Forcite-caculation Build Polymers-Homopolymer

快乐学术猿 3万+

Android单元测试困局破解,基于Kotlin的4大自动化测试模式全解析

破解Android单元测试难题,详解基于Kotlin的四大自动化测试模式。涵盖UI测试、集成测试等场景,结合Kotlin测试开发优势,提升稳定性与效率。方法实用,架构清晰,值得收藏。

QuickDebug的博客 319

Spring AI Alibaba实战训练营-28 手把手教你用SAA+ Nacos 构建分布式 MCP 应用

本文介绍如何使用Spring AI Alibaba和Nacos构建分布式MCP应用,实现AI模型与工具服务的动态发现和调用。主要内容包括:1) 项目架构设计,包含MCP Server、Nacos注册中心和MCP Client三个核心组件;2) 环境准备要求,如Java 17+、Maven 3.6+、Nacos 3.1.0+等;3) 详细实施步骤:启动Nacos服务、注册MCP服务端、配置MCP网关聚合服务、设置MCP客户端发现机制。通过这种架构,开发者可以轻松扩展新工具服务,无需修改客户端代码,实现服务动态

program哲学 311

LCG吾爱破解APP开发环境搭建:从零开始构建Android客户端

LCG吾爱破解APP是一款功能强大的第三方安卓应用,专为技术爱好者打造。本文将详细介绍如何从零开始搭建其开发环境,让你轻松开启Android客户端的构建之旅。 ## 一、准备工作:开发环境基础配置 在开始搭建LCG吾爱破解APP开发环境前,确保你的系统满足以下要求: - **操作系统**:Linux(推荐)、Windows或macOS - **JDK版本**:建议安装JDK 8或更高版本 -

gitblog_00057的博客 810

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

上一篇: 阿里P7大佬教你破解 Kotlin 协程(7) - 序列生成器篇
下一篇: 看完这篇华为的 Android 面试题和八股文,这个金三银四不愁了
不秃头de程序猿
博客等级 码龄5年 4089粉丝 1478原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值