Kotlin学习之-6.4 Coroutines

本文介绍了Kotlin V1.1中协程的基本概念及其实验性状态。探讨了协程如何通过挂起函数避免线程阻塞,以及协程在Kotlin中的实现原理。此外,还展示了如何使用协程构建延迟序列。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Kotlin学习之-6.4 Coroutines

在Kotlin V1.1中Coroutines 还处在实验阶段

有些接口会做一些耗时的操作例如网络IO请求,文件IO, CPU或者GUP密集的工作等,并且要求调用者阻塞知道操作完成。Coroutines提供了一种能够替换避免阻塞线程的方法并且代价更小、控制性更好的操作:suspension of a coroutine

Coroutines通过把复杂的管理放到代码库中来简化异步编程。程序的逻辑可以在Coroutine中线性的表达,然后使用的库会帮我们解决异步处理的问题。库可以把我们相关的代码包装成回调,订阅给相关的事件,分配到不同线程上执行,最终代码仍然保持和线性顺序执行一样简单。

在其他语言中有很多异步机制可以用来实现Kotlin的coroutines。这包括C#和ECMASCript语言中的async/await,Go语言中channelsselect,还有C#和Python中的generator/yield

阻塞和挂起

通常来说,coroutines是那些可以被挂起但不需要阻塞线程的操作。阻塞线程是非常昂贵的,尤其是在高负载的情况下,因为只有一个数量相对小的线程在实际中持续运行,因此阻塞其中的一个会导致一些重要的工作被延迟。

另一方面,Coroutine挂起几乎是没有损耗的。没有上下文切换或者其他操作系统的需求。并且,挂起还可以通过一个用户库来控制,作为库的作者,我们可以决定在挂起时要做什么并且可以根据我们的需求来优化、添加日志或者拦截相应的操作。

另外一个区别是coroutines不能被随机的指令挂起,而只能在一些特殊的挂起点处挂起,他们会调用特殊标记的函数。

挂起函数

挂起发生在我们调用一个标记了suspend的函数的时候:

suspend fun doSomething(foo: Foo): Bar {
}

这样的函数叫作挂起函数,因为调用这些函数可能会挂起一个coroutine(库可以决定执行是否挂起,如果调用的结果已经可用的话)。 挂起函数可以和普通函数一样有参数和返回值,但是他们只能在coroutines 中或者其他挂起函数中调用。实际上,要启动一个coroutine,必须至少有一个挂起函数,并且通常是匿名,例如一个挂起lambda表达式。 我们看一个例子,一个简化的async()函数(来自kotlinx.coroutines库)

fun<T> async(block: suspend () -> T)

这里,async()是一个普通函数(不是一个挂起函数),但是block参数有一个suspend修饰的函数类型:suspend () -> T。因此,当我们传递一个lambda表达式给async()的时候,他就是一个挂起lambda表达式,我们可以在它里面调用一个挂起函数。

async {
    doSomething(foo)
}

为了继续类比,await()函数可是是一个挂起函数(因此可以在async() 代码块中调用),它会挂起一个coroutine知道有些计算完成并且返回。

async {
    val result = computation.await()
}

更多关于真正async/await函数工作的信息可以在kotlinx.coroutines中找到,点击这里

注意挂起函数await()doSomething不在在一个普通函数被调用,如main()函数

fun main(args: Array<String>) {
    doSomething()
}

还有要注意的是挂起函数可以是虚函数,并且当他们被复写的时候,suspend修饰符必须要指明。

interface Base {
    suspend fun foo()
}

class Derived: Base {
    override suspend fun foo() { }
}

@RestrictsSuspension 注解

扩展函数和lambda表达式也可以标记成suspend,就想普通函数一样。这使得可以创建用户可以扩展的DSL和其他API。在有些情况下库的作者需要放置用户添加新的方式来挂起一个coroutine。

为了达到这个,可以使用@RestrictsSuspension注解。当一个接收类或者接口R是用它注解的时候,所有挂起的扩展函数都需要代理到其他R的成员或者其他扩展上。因为扩展不能相互无线代理(这样程序就无法结束了), 这保障了所有的挂起都发生在调用R的成员的时候,而这些是库的作者可以完全控制的。

这和一种少见的情况相关,当每一个挂起在库中是用一个特殊的方式处理的。例如,当通过buildSequence()函数来实现生成器的时候,我们需要确保任何挂起调用都在coroutine并用调用yield()或者yieldAll()来结束,而没有任何其他函数。 这就是SequenceBuilder 是用@RestrictsSuspension来注解的原因。

@RestrictsSuspension
public abstract class SequenceBuilder<in T> {
}

源代码见Github

coroutine 的内部工作

这里我们不会完全的讲解coroutine的背后的工作原理,但是简要地知道发生了什么还是很重要的。

Coroutines的实现是完全通过一种编译技术,(不需要VM 和OS 的支持)并且挂起是通过代码转换来实现的。 基本上,所有挂起函数(可能会优化,但是这里先不详细展开)都转换成一个状态机它的状态对应了挂起调用的不同状态。然后,在挂起之前,下一个状态存储在一个编译器生成的类的对象和其他相关的局部变量中。当coroutine恢复执行时,局部变量会恢复并且状态机会从挂起之前的状态继续执行。

一个挂起的coroutine可以存储起来并当做一个对象传递,保留它的挂起状态和局部变量。这种类型的对象是Continuation,并且所有代码转换都描述在这里和经典的Continuation-passing style相对应。结果就是,具体实现中挂起函数使用一个额外的Continuation类型的参数。

更多关于cotoutines如何实现的细节可以在设计文档中找到。关于其他语言中async/await的解释相关,尽管他们的语言特性使得不会像Kotlin的coroutine这样通用。

coroutine的实验状态

coroutine的设计还是实验性的,这意味着它可能会在将来的发布中变化。当用Kotlin v1.1编译coroutine的时候,会有一个默认的警告:coroutine特性是实验性的。 想要移除这个警告,你需要标明opt-in标志位。

由于他的实验状态,在标准库中和coroutine相关的API都放在了kotlin.coroutines.experimental包下。 当设计完成时,实验状态将被移除,最终的API会被移动到kotlin.coroutines包下,并且实验状态的包会被保留下来用来向后兼容旧版本。

重要提示:我们建议库作者遵循同样的规范:添加”experimental” 后缀到你的包后面,如果它暴露了基于coroutine的API,这样你的库就能保持二进制的兼容性。当正式API发布时,遵循以下步骤:

  • 拷贝所有API 到com.example下(不带experimental后缀的包名)
  • 保留实验的包,为了向后兼容性。

这将会减少用户的移植问题。

标准API

coroutine主要包含3部分:

  • 语言支持(挂起函数,如上所述)
  • Kotlin标准库底层核心API
  • 可以直接被用户使用的高层API

底层API: kotlin.coroutines

底层API想对很小并且永远不应该用来做除了构建高层库的事情。它包含两个主要的包:

  • kotlin.coroutines.exmperimental,包含主要类型和基础元素,例如
    • createCoroutine()
    • startCoroutine()
    • suspendCoroutine()
  • kotlin.coroutines.experimental.intrinsics包含更底层的核心接口例如suspendCoroutineOrReturn

更多关于如何使用这些接口的细节,点击这里

kotlin.coroutines中的生成器接口

kotlin.coroutines.experimental包中仅有的应用程序级别函数是

这些接口和kotlin-stadlib一起发布是因为他们和序列相关。实际上,这些函数实现了生成器,例如提供一种简单构造延迟序列的方法:

import kotlin.coroutines.experimental.*

fun main(args: Array<String>) {
    val fibonacciSeq = buildSequence {
        var a = 0
        var b = 1

        yield(1)

        while (true) {
            yield(a + b)

            val tmp = a + b
            a = b
            b = tmp
        }
    }

    // Print the first five Fibonacci numbers
    println(fibonacciSeq.take(8).toList())
}

// output
[1, 1, 2, 3, 5, 8, 13, 21]

这生成了一个延迟加载,可能是无线的斐波那契数列通过创建一个能够生成连续斐波那契数字的函数的coroutine。当遍历这样的序列时,每一步迭代器执行生成下一个数字。因此,我们可以从列表中获取任意有限数量的数列, 例如fibonacciSeq.take(8).toList()返回[1, 1, 2, 3, 5, 8, 13, 21]。并且coroutine代价很低足可以让这种方式非常实用。

为了掩饰序列到底是如何延迟加载的,我们在调用buildSequence()时打印一些调试信息:

import kotlin.coroutines.experimental.*

fun main(args: Array<String>) {
    val lazySeq = buildSequence {
        print("START ")
        for (i in 1..5) {
            yield(i)
            print("STEP ")
        }
        print("END")
    }

    // Print the first three elements of the sequence
    lazySeq.take(3).forEach { print("$it ") }
}

// output
START 1 STEP 2 STEP 3 

运行上面的代码发现如果我们打印前3个元素,那么数字会和STEP的输出信息交织在一起。这意味着实际上计算是延迟的。为了打印1,我们只需要执行到第一个yield(i),并且START也会打印出来。接下来,要输出2,我们需要执行到下一个yield(i),这会输出STEP.输出3的情况类似。最后一个STEPEND不会被输出,因为我们没有请求更多的序列元素。

可以使用yieldAll()函数来一次生成一个集合

import kotlin.coroutines.experimental.*

fun main(args: Array<String>) {
    val lazySeq = buildSequence {
        yield(0)
        yieldAll(1..10) 
    }

    lazySeq.forEach { print("$it ") }
}

// output
0 1 2 3 4 5 6 7 8 9 10

函数buildIterator()buildSequence()工作方式很像,但是返回一个延迟的迭代器。

我们可以给buildSequence()添加自定义逻辑,通过写挂起扩展方法给SequenceBuilder类。

import kotlin.coroutines.experimental.*

suspend fun SequenceBuilder<Int>.yieldIfOdd(x: Int) {
    if (x % 2 != 0) yield(x)
}

val lazySeq = buildSequence {
    for (i in 1..10) yieldIfOdd(i)
}

fun main(args: Array<String>) {
    lazySeq.forEach { print("$it ") }
}

// output
1 3 5 7 9 

其他高级接口: kotlinx.coroutines

只有和coroutine相关的核心接口可以在Kotlin标准库中存在。这包含了大部分的核心基础内容和接口,这些可能会被coroutine相关的库使用。

大多数应用层基于coroutines的接口都发布在一个单独的库中:kotlinx.coroutines。这个库包含:

  • 平台无关的异步编程接口kotlinx-coorutines-core
    • 这个模块包含Go语言风格的支持select操作的管道和其他一些方便的基础元素
    • 这个库的详细使用说明在这里
  • 基于JDK 8 的CompletableFuture的接口:kotlinx-coroutines-jdk8
  • 基于JDK 7 的非阻塞IO(NIO)接口:kotlinx-coroutines-nio
  • 支持Swing(kotlinx-coroutins-swing)和JavaFx(`kotlinx-coroutines-javafx)
  • 支持RxJava:kotlinx-coroutines-rx

这些库既提供方便的API使得普通工作更加简单,并且还提供了端到端的如何构建基于coroutine库的例子


PS,我会坚持把这个系列写完,有问题可以留言交流,也关注专栏Kotlin for Android Kotlin安卓开发

[versions] ######## Codes denpendency versions ######## # Build plugins: androidGradlePlugin = "8.2.2" oapm = "3.0.8.1" obuild = "1.7.0" protobufPlugin = "0.9.4" androidToolsR8 = "8.2.42" # Kotlin libs: kotlin = "1.9.22" kotlinCoroutines = "1.7.3" kotlinSerializationJson = "1.6.2" koin = "3.4.3" koinCoroutines = "3.4.1" # Android libs: chips = "1.0.0" # Androidx libs: coreKtx = "1.12.0" appcompat = "1.6.1" room = "2.4.3" pagingKtx = "3.2.1" fragmentKtx = "1.4.1" lifecycle = "2.6.2" recyclerview = "1.2.1" constraintlayout = "2.1.3" # Google libs: material = "1.11.0" protobuf = "3.23.0" protobufProtoc = "3.10.0" gson = "2.10.1" # Oplus libs: addon = "15.0.34.17-preview" apiAdapter = "13.3.5" couiSupport = "15.0.28" couiMaterialcolor = "1.0.1" olint = "1.2.5" trackinglib = "1.0.3" feedbackCdp = "15.0.4" feedbackEnvDomestic = "1.0.1" synergysdk = "14.1.10" castsdk = "4.1.4" oplusSettings = "2.1.1" oapsSdkDownload = "3.1.2" sauaar = "3.0.3" sysapi = "13.0.6" push = "3.5.3" #dmpSdk = "2.0.0-betafab8c2d-SNAPSHOT" dmpSdk = "2.1.1" dmpSdkExport = "2.3.0-20240903.080150-1" aiunit = "1.1.0" stdid = "1.0.8" oworkSync = "1.0.2-beta" appfeature = "1.0.2" oplusTrack = "3.0.13" oplusCloudconfig = "3.4.8" oplusCloudconfigTest = "1.0.0" heytapCloudconfigTest = "0.0.7" pantanalCardSeedling = "3.0.1" oplusBackup = "2.0.0" onetSdk = "14.4.60" dfsSdk = "1.0.1-alpha62bb292-SNAPSHOT" ptcSdk = "1.0.1-alpha7066ba4-SNAPSHOT" accessorySdk = "3.0.35" oplusTBLPlayer = "1.7.9PRO" remoteControlSdk = "15.0.0-alphacd8fa13-SNAPSHOT" workmanagerversion = "2.7.0" # HeyTap libs: addonAdapter = "11.2.5" cloudDrive = "1.1.8" cloudBase = "1.1.3" ucAccountSDK = "2.5.1" heytapTrack = "1.1.3.1" heytapNearxUtils = "1.0.8.security" heytapProtobuff = "2.2.0.222" heytapCloudconfig = "2.4.2.9" adOverseas = "2.24.0" tapHttpVerSion = "4.9.3.8.1" heytapCloudkitCommonVersion="2.2.10" heytapCloudkitSyncVersion="2.2.10" # 3rd parts libs: findbugs = "3.0.2" okio = "3.7.0" okhttp3 = "4.12.0" glide = "4.16.0" wechatSdk = "6.8.24" retrofit2 = "2.9.0" retrofit2Protobuf = "2.9.0" commonsCompress = "1.21" commonsLogging = "1.3.0" commonsIo = "2.15.1" commonsCodec = "1.6" zip4j = "2.11.5" unrar = "1.0.0" gnuCrypto = "2.0.1" tika = "2.8.0" cryptography = "2.1.1" slf4j = "2.0.10" rxjava = "3.1.8" wpsSdkSnapshot = "20210524-SNAPSHOT" maxVersion = "12.6.1" googleAdapter = "23.6.0.1" facebookAdapter = "6.18.0.1" mytargetAdapter = "5.27.0.0" bytedanceAdapter = "6.4.0.3.0" # FileManager inner libs: fileManagerThumbnail = "15.12.34-alpha7640294-SNAPSHOT" fileManagerThumbnailWpsCompat = "15.11.0-beta1a8f9af-SNAPSHOT" fileManagerSimulateClick = "15.11.4-beta7146442-SNAPSHOT" fileManagerDragDrop = "15.11.0-beta1a8f9af-SNAPSHOT" ######## Unit test denpendency versions ######## # Base test libs: jacoco = "0.8.7" junit = "4.13.2" mockito = "3.11.2" mockk = "1.12.1" kotlinCoroutinesTest = "1.6.0" robolectric = "4.10" powermock = "2.0.9" # Androidx test libs: androidxCoreTest = "2.1.0" # 3rd parts libs: json = "20231013" # riskctrl riskctrl="1.1.6" ######## AndroidTest denpendency versions ######## # Androidx test libs: espresso = "3.5.1" testRunner = "1.4.0" testRules = "1.4.0" testTruth = "1.4.0" androidxJunit = "1.1.5" uiautomator = "2.2.0" roomTesting = "2.2.5" # Oplus test libs: oTestLib = "1.4.1" oTestCoverage = "2.0.6@aar" [libraries] ######## Codes denpendencies ######## # Build plugins: android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "androidGradlePlugin" } kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } kotlin-serialization-plugin = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" } oplus-oapm-plugin = { module = "com.oppo.test:oapm-perf", version.ref = "oapm" } oplus-build-plugin = { module = "com.inno.buildplugin:build-plugin", version.ref = "obuild" } google-protobuf-plugin = { module = "com.google.protobuf:protobuf-gradle-plugin", version.ref = "protobufPlugin" } android-tools-r8 = { module = "com.android.tools:r8", version.ref = "androidToolsR8" } # Kotlin libs: kotlin-stdlib-common = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } kotlin-stdlib-jdk8 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinCoroutines" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinSerializationJson" } koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" } koin-core-coroutines = { module = "io.insert-koin:koin-core-coroutines", version.ref = "koinCoroutines" } koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" } # Android libs: android-ex-chips = { module = "com.android.ex.chips:chips", version.ref = "chips" } # Androidx libs: androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" } androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" } androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" } androidx-room-paging = { module = "androidx.room:room-paging", version.ref = "room" } androidx-paging-ktx = { module = "androidx.paging:paging-runtime-ktx", version.ref = "pagingKtx" } androidx-fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "fragmentKtx" } androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" } androidx-lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycle" } androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycle" } androidx-lifecycle-viewmodel-savedstate = { module = "androidx.lifecycle:lifecycle-viewmodel-savedstate", version.ref = "lifecycle" } androidx-lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "lifecycle" } androidx-lifecycle-service = { module = "androidx.lifecycle:lifecycle-service", version.ref = "lifecycle" } androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" } androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" } androidx-workmanager-ktx = {module = "androidx.work:work-runtime-ktx", version.ref = "workmanagerversion"} google-material = { module = "com.google.android.material:material", version.ref = "material" } google-protobuf-java = { module = "com.google.protobuf:protobuf-java", version.ref = "protobuf" } google-protobuf-protoc = { module = "com.google.protobuf:protoc", version.ref = "protobufProtoc" } google-gson = { module = "com.google.code.gson:gson", version.ref = "gson" } # Oplus libs: oplus-check-olint = { module = "com.sectools.check:olint", version.ref = "olint" } oplus-appcompat-base = { module = "com.oplus.appcompat:base", version.ref = "couiSupport" } oplus-appcompat-controls = { module = "com.oplus.appcompat:controls", version.ref = "couiSupport" } oplus-appcompat-bars = { module = "com.oplus.appcompat:bars", version.ref = "couiSupport" } oplus-appcompat-lists = { module = "com.oplus.appcompat:lists", version.ref = "couiSupport" } oplus-appcompat-panel = { module = "com.oplus.appcompat:panel", version.ref = "couiSupport" } oplus-appcompat-responsiveui = { module = "com.oplus.appcompat:responsiveui", version.ref = "couiSupport" } oplus-appcompat-component = { module = "com.oplus.appcompat:component", version.ref = "couiSupport" } oplus-material-color = { module = "com.oplus.materialcolor:coui-material-color", version.ref = "couiMaterialcolor" } oplus-tool-trackinglib = { module = "com.oplus.tool.trackinglib:trackinglib", version.ref = "trackinglib" } oplus-feedback-cdp = { module = "com.customer.feedback.sdk:feedback-cdp", version.ref = "feedbackCdp" } oplus-feedback-env-domestic = { module = "com.customer.feedback.sdk:feedback-env-domestic", version.ref = "feedbackEnvDomestic" } oplus-synergy-compat = { module = "com.oplus.synergysdk:synergysdkcompat", version.ref = "synergysdk" } oplus-cast-sdk = { module = "com.oplus.cast.api:castsdk", version.ref = "castsdk" } oplus-appprovider-settings = { module = "com.oplus.appprovider:settings-oplus", version.ref = "oplusSettings" } oplus-sau-coui = { module = "com.oplus.sauaar:coui-sauaar", version.ref = "sauaar" } oplus-aiunit-core = { module = "com.oplus.aiunit.open:core", version.ref = "aiunit" } oplus-aiunit-nlp = { module = "com.oplus.aiunit.open:nlp", version.ref = "aiunit" } oplus-stdid-sdk = { module = "com.oplus.stdid.sdk:sdk", version.ref = "stdid" } oplus-owork-sync = { module = "com.oplus.owork:sync-sdk", version.ref = "oworkSync" } oplus-coreapp-appfeature = { module = "com.oplus.coreapp.appfeature:AppFeatureHelper", version.ref = "appfeature" } oplus-statistics-track = { module = "com.oplus.statistics:track", version.ref = "oplusTrack" } oplus-cloudconfig-base = { module = "com.oplus.nearx:cloudconfig", version.ref = "oplusCloudconfig" } oplus-cloudconfig-env = { module = "com.oplus.nearx:cloudconfig-env", version.ref = "oplusCloudconfig" } oplus-cloudconfig-testenv = { module = "com.nearx.test:env", version.ref = "oplusCloudconfigTest" } heytap-cloudconfig-testenv = { module = "com.heytap.test:env", version.ref = "heytapCloudconfigTest"} oplus-pantanal-card-seedling = { module = "com.oplus.pantanal.card:seedling-support-internal", version.ref = "pantanalCardSeedling" } oplus-backup-sdk = { module = "com.oplus.backup:backup-sdk", version.ref = "oplusBackup" } oplus-onet-sdk = { module = "com.oplus.onet:sdk", version.ref = "onetSdk" } oplus-dfs-sdk = { module = "com.oplus.dfs:dfssdk", version.ref = "dfsSdk" } oplus-ptc-sdk-core = { module = "com.oplus.pantaconnect.sdk:core", version.ref = "ptcSdk" } oplus-ptc-sdk-connect = { module = "com.oplus.pantaconnect.sdk:connectionservice", version.ref = "ptcSdk" } oplus-ptc-sdk-ext = { module = "com.oplus.pantaconnect.sdk:extensions", version.ref = "ptcSdk" } oplus-ptc-sdk-protocol = { module = "com.oplus.pantaconnect.sdk:protocol-plugin", version.ref = "ptcSdk" } oplus-accessory-sdk = { module = "com.heytap.accessory:sdk", version.ref = "accessorySdk" } oplus-addon-sdk = { module = "com.oplus.sdk:addon", version.ref = "addon" } oplus-api-adapter-compat = { module = "com.oplus.support:api-adapter-compat", version.ref = "apiAdapter" } oplus-api-adapter-oplus = { module = "com.oplus.support:api-adapter-oplus", version.ref = "apiAdapter" } oplus-sysapi = { module = "com.oplus.appplatform:sysapi", version.ref = "sysapi" } oplus-dmp-sdk = { module = "com.oplus.dmp.sdk:connect", version.ref = "dmpSdk" } oplus-dmp-sdk-export = { module = "com.oplus.dmp.sdk:connect-export", version.ref = "dmpSdkExport" } oplus-tbl-player = { module = "com.oplus.TBLPlayer:CommonPlayer", version.ref = "oplusTBLPlayer" } oplus-remotecontrol-sdk = { module = "com.oplus.remotecontrol:remotecontrolsdk", version.ref = "remoteControlSdk" } oplus-push = { module = "com.heytap.msp:push", version.ref = "push" } # HeyTap libs: heytap-addon-adapter = { module = "com.heytap.support:addon-adapter", version.ref = "addonAdapter" } heytap-cloud-drive = { module = "com.heytap.cloud:clouddisksdk", version.ref = "cloudDrive" } heytap-cloud-base = { module = "com.heytap.cloud:base", version.ref = "cloudBase" } heytap-account-uc = { module = "com.heytap.accountsdk:UCAccountSDK_Base_heytap", version.ref = "ucAccountSDK" } heytap-nearx-track = { module = "com.heytap.nearx:track", version.ref = "heytapTrack" } heytap-nearx-utils = { module = "com.heytap.nearx:utils", version.ref = "heytapNearxUtils" } heytap-nearx-protobuff = { module = "com.heytap.nearx.protobuff:wire-runtime-isolate", version.ref = "heytapProtobuff" } heytap-cloudconfig-base = { module = "com.heytap.nearx:cloudconfig", version.ref = "heytapCloudconfig" } heytap-cloudconfig-env = { module = "com.heytap.nearx:cloudconfig-env-oversea", version.ref = "heytapCloudconfig" } heytap-cloudconfig-area = { module = "com.heytap.nearx:cloudconfig-area", version.ref = "heytapCloudconfig" } opos-ad-overseas = { module = "com.opos.ad:overseas-ad-global-pub", version.ref = "adOverseas" } #opos-ad-overseas = { module = "com.opos.ad:overseas-ad-global-dev", version.ref = "adOverseas" } taphttp = { module = "com.heytap.nearx:taphttp", version.ref = "tapHttpVerSion" } taphttp-domestic = { module = "com.heytap.nearx:taphttp-env", version.ref = "tapHttpVerSion" } taphttp-env = { module = "com.heytap.nearx:taphttp-env-oversea", version.ref = "tapHttpVerSion" }#HttpDns需要 oppo-marker-oaps-download = { module = "com.oppo.market:oaps_sdk_download", version.ref = "oapsSdkDownload" } heytap-cloudkit-common = { module = "com.heytap.cloudkit.libcommon:libcommon", version.ref = "heytapCloudkitCommonVersion"} heytap-cloudkit-sync = { module = "com.heytap.cloudkit.libsync:libsync", version.ref = "heytapCloudkitSyncVersion"} gis-riskctrl = { module = "com.heytap.gis:riskctrl", version.ref = "riskctrl" } # 3rd parts libs: findbugs = { module = "com.google.code.findbugs:jsr305", version.ref = "findbugs" } okio = { module = "com.squareup.okio:okio", version.ref = "okio" } squareup-okhttp3-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp3" } squareup-okhttp3-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp3" } squareup-retrofit2-retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit2" } squareup-retrofit2-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit2" } squareup-retrofit2-adapter = { module = "com.squareup.retrofit2:adapter-rxjava3", version.ref = "retrofit2" } squareup-retrofit2-protobuf = { module = "com.squareup.retrofit2:converter-protobuf", version.ref = "retrofit2Protobuf" } bumptech-glide-compiler = { module = "com.github.bumptech.glide:compiler", version.ref = "glide" } bumptech-glide-base = { module = "com.github.bumptech.glide:glide", version.ref = "glide" } tencent-sdk-wechat = { module = "com.tencent.mm.opensdk:wechat-sdk-android", version.ref = "wechatSdk" } apache-commons-compress = { module = "org.apache.commons:commons-compress", version.ref = "commonsCompress" } apache-commons-logging = { module = "commons-logging:commons-logging", version.ref = "commonsLogging" } apache-commons-io = { module = "commons-io:commons-io", version.ref = "commonsIo" } apache-commons-codec = { module = "commons-codec:commons-codec", version.ref = "commonsCodec" } apache-tika-core = { module = "org.apache.tika:tika-core", version.ref = "tika" } lingala-zip4j = { module = "net.lingala.zip4j:zip4j", version.ref = "zip4j" } innosystec-unrar-java = { module = "de.innosystec.unrar:java-unrar", version.ref = "unrar" } gnu-crypto = { module = "gnu.crypto:crypto", version.ref = "gnuCrypto" } allawn-crypto-android = { module = "com.allawn.cryptography:crypto-android-sdk", version.ref = "cryptography" } slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } rxjava = { module = "io.reactivex.rxjava3:rxjava", version.ref = "rxjava" } wps-sdk-snapshot = { module = "com.wps.sdk:snapshot", version.ref = "wpsSdkSnapshot" } # FileManager Inner libs: oplus-filemanager-thumbnail = { module = "com.oplus.filemanager:thumbnail", version.ref = "fileManagerThumbnail" } oplus-filemanager-thumbnailWpsCompat = { module = "com.oplus.filemanager:thumbnail_wps_compat", version.ref = "fileManagerThumbnailWpsCompat" } oplus-filemanager-simulateClickEngine = { module = "com.oplus.filemanager:simulate_click_engine", version.ref = "fileManagerSimulateClick" } oplus-filemanager-dragDrop = { module = "com.oplus.filemanager:dragdrop", version.ref = "fileManagerDragDrop" } max-applovin-sdk = { module = "com.applovin:applovin-sdk", version.ref = "maxVersion" } mediation-google-adapter = { module = "com.applovin.mediation:google-adapter", version.ref = "googleAdapter" } mediation-facebook-adapter = { module = "com.applovin.mediation:facebook-adapter", version.ref = "facebookAdapter" } mediation-mytarget-adapter = { module = "com.applovin.mediation:mytarget-adapter", version.ref = "mytargetAdapter" } mediation-bytedance-adapter = { module = "com.applovin.mediation:bytedance-adapter", version.ref = "bytedanceAdapter" } ######## Unit test denpendencies ######## junit-base = { module = "junit:junit", version.ref = "junit" } mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito" } mockito-inline = { module = "org.mockito:mockito-inline", version.ref = "mockito" } mockk-base = { module = "io.mockk:mockk", version.ref = "mockk" } mockk-android = { module = "io.mockk:mockk-android", version.ref = "mockk" } mockk-agnet = { module = "io.mockk:mockk-agent-jvm", version.ref = "mockk" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinCoroutinesTest" } robolectric-base = { module = "org.robolectric:robolectric", version.ref = "robolectric" } powermock-module-junit4-base = { module = "org.powermock:powermock-module-junit4", version.ref = "powermock" } powermock-module-junit4-rule = { module = "org.powermock:powermock-module-junit4-rule", version.ref = "powermock" } powermock-api-mockito2 = { module = "org.powermock:powermock-api-mockito2", version.ref = "powermock" } powermock-classloading-xstream = { module = "org.powermock:powermock-classloading-xstream", version.ref = "powermock" } androidx-arch-core-testing = { module = "androidx.arch.core:core-testing", version.ref = "androidxCoreTest" } json-base = { module = "org.json:json", version.ref = "json" } ######## AndroidTest denpendencies ######## androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" } androidx-test-espresso-intents = { module = "androidx.test.espresso:espresso-intents", version.ref = "espresso" } androidx-test-espresso-web = { module = "androidx.test.espresso:espresso-web", version.ref = "espresso" } androidx-test-espresso-idling-concurrent = { module = "androidx.test.espresso.idling:idling-concurrent", version.ref = "espresso" } androidx-test-espresso-idling-resource = { module = "androidx.test.espresso:espresso-idling-resource", version.ref = "espresso" } androidx-test-runner = { module = "androidx.test:runner", version.ref = "testRunner" } androidx-test-rules = { module = "androidx.test:rules", version.ref = "testRules" } androidx-test-junit = { module = "androidx.test.ext:junit", version.ref = "androidxJunit" } androidx-test-truth = { module = "androidx.test.ext:truth", version.ref = "testTruth" } androidx-test-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version.ref = "uiautomator" } androidx-room-testing = { module = "androidx.room:room-testing", version.ref = "roomTesting" } mockito-android = { module = "org.mockito:mockito-android", version.ref = "mockito" } otest-testLib = { module = "otestPlatform:testLib", version.ref = "oTestLib" } otest-coverageLib = { module = "otestPlatform:coverageLibTest", version.ref = "oTestCoverage" } koin-test = { module = "io.insert-koin:koin-test", version.ref = "koin" } koin-test-junit4 = { module = "io.insert-koin:koin-test-junit4", version.ref = "koin" } seedling-sdk = { module = "com.oplus.pantanal.card:seedling-support-internal", version.ref = "koin" } [bundles] ######## Codes denpendencies ######## kotlin = ["kotlin-stdlib-common", "kotlin-stdlib-jdk8", "kotlinx-coroutines-android"] oplus-cloudconfig = ["oplus-cloudconfig-base", "oplus-cloudconfig-env"] oplus-feedback-domestic = ["oplus-feedback-cdp", "oplus-feedback-env-domestic"] heytap-cloudconfig = ["heytap-cloudconfig-base", "heytap-cloudconfig-env", "heytap-cloudconfig-area"] heytap-clouddrive = ["heytap-cloud-drive", "heytap-cloud-base"] squareup-okhttp3 = ["squareup-okhttp3-okhttp", "squareup-okhttp3-logging"] squareup-retrofit2 = [ "squareup-retrofit2-retrofit", "squareup-retrofit2-gson", "squareup-retrofit2-adapter", "rxjava" ] ######## Unit test denpendencies ######## test-mockito = ["mockito-core", "mockito-inline"] test-mockk = ["mockk-base", "mockk-android", "mockk-agnet"] test-powermock = [ "powermock-module-junit4-base", "powermock-module-junit4-rule", "powermock-api-mockito2", "powermock-classloading-xstream" ] ######## AndroidTest denpendencies ######## android-test-suit = [ "androidx-test-runner", "androidx-test-rules", "androidx-test-junit", "androidx-test-truth" ] android-test-espresso = [ "androidx-test-espresso-core", "androidx-test-espresso-intents", "androidx-test-espresso-web", "androidx-test-espresso-idling-concurrent", "androidx-test-espresso-idling-resource" ]这是配置目录,这个版本的AGP对应的Java、sdk、gradle版本怎么设置
最新发布
07-19
安卓报错Duplicate class org.jetbrains.org.objectweb.asm.util.CheckSignatureAdapter$State found in modules jetified-kotlin-compiler-embeddable-1.4.10 (org.jetbrains.kotlin:kotlin-compiler-embeddable:1.4.10) and jetified-kotlin-gradle-plugin-1.4.10 (org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10) Duplicate class org.jetbrains.org.objectweb.asm.util.Printer found in modules jetified-kotlin-compiler-embeddable-1.4.10 (org.jetbrains.kotlin:kotlin-compiler-embeddable:1.4.10) and jetified-kotlin-gradle-plugin-1.4.10 (org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10) Duplicate class org.jetbrains.org.objectweb.asm.util.Textifiable found in modules jetified-kotlin-compiler-embeddable-1.4.10 (org.jetbrains.kotlin:kotlin-compiler-embeddable:1.4.10) and jetified-kotlin-gradle-plugin-1.4.10 (org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10) Duplicate class org.jetbrains.org.objectweb.asm.util.Textifier found in modules jetified-kotlin-compiler-embeddable-1.4.10 (org.jetbrains.kotlin:kotlin-compiler-embeddable:1.4.10) and jetified-kotlin-gradle-plugin-1.4.10 (org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10) Duplicate class org.jetbrains.org.objectweb.asm.util.TraceAnnotationVisitor found in modules jetified-kotlin-compiler-embeddable-1.4.10 (org.jetbrains.kotlin:kotlin-compiler-embeddable:1.4.10) and jetified-kotlin-gradle-plugin-1.4.10 (org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10) Duplicate class org.jetbrains.org.objectweb.asm.util.TraceClassVisitor found in modules jetified-kotlin-compiler-embeddable-1.4.10 (org.jetbrains.kotlin:kotlin-compiler-embeddable:1.4.10) and jetified-kotlin-gradle-plugin-1.4.10 (org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10) Duplicate class org.jetbrains.org.objectweb.asm.util.TraceFieldVisitor found in modules jetified-kotlin-compiler-embeddable-1.4.10 (org.jetbrains.kotlin:kotlin-compiler-embeddable:1.4.10) and jetified-kotlin-gradle-plugin-1.4.10 (org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10) Duplicate class org.jetbrains.org.objectweb.asm.util.TraceMethodVisitor found in modules jetified-kotlin-compiler-embeddable-1.4.10 (org.jetbrains.kotlin:kotlin-compiler-embeddable:1.4.10) and jetified-kotlin-gradle-plugin-1.4.10 (org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10) Duplicate class org.jetbrains.org.objectweb.asm.util.TraceModuleVisitor found in modules jetified-kotlin-compiler-embeddable-1.4.10 (org.jetbrains.kotlin:kotlin-compiler-embeddable:1.4.10) and jetified-kotlin-gradle-plugin-1.4.10 (org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10) Duplicate class org.jetbrains.org.objectweb.asm.util.TraceSignatureVisitor found in modules jetified-kotlin-compiler-embeddable-1.4.10 (org.jetbrains.kotlin:kotlin-compiler-embeddable:1.4.10) and jetified-kotlin-gradle-plugin-1.4.10 (org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10)
04-01
<think>嗯,用户遇到了安卓项目中的重复类错误,涉及多个org.jetbrains.org.objectweb.asm.util下的类。首先,我需要理解这个问题的根本原因。错误信息显示,kotlin-compiler-embeddable和kotlin-gradle-plugin这两个依赖包含了相同的类,导致冲突。这可能是因为这两个库都依赖了不同版本的ASM库,或者它们内部打包了相同类但不同版本。 接下来,我应该考虑常见的解决方法。通常,这类问题可以通过排除重复的依赖来解决。用户需要在构建文件中检查这两个模块的依赖关系,找出哪个模块引入了冲突的ASM类,然后排除掉其中一个。不过,用户提到这两个模块都是来自Kotlin的1.4.10版本,可能这时候需要确认Kotlin插件和Gradle版本是否兼容,或者是否需要升级到更高版本,因为旧版本可能存在已知的依赖冲突问题。 另外,可能需要检查项目中的其他依赖是否间接引入了不同版本的ASM库。使用Gradle的依赖树分析工具(如./gradlew app:dependencies)可以帮助识别冲突的来源。如果发现有多个版本的ASM,可以强制指定使用某个版本,或者排除其他版本。 还有可能是Gradle插件本身的问题,尤其是在使用旧版本的Kotlin插件时。查看官方文档或问题追踪系统,看看是否有相关的问题和解决方案。例如,升级到Kotlin 1.5或更高版本可能已经修复了这个问题,因为JetBrains可能在后续版本中调整了依赖管理。 用户还需要确认项目的Gradle配置是否正确,比如是否在多个地方重复声明了Kotlin插件,或者是否有第三方库强制覆盖了ASM的版本。有时候,插件之间的兼容性问题也会导致此类错误,需要逐一排查。 最后,如果排除依赖或升级版本无效,可能需要更深入分析依赖树,或者寻求社区和官方支持。提供详细的错误日志和构建文件配置会有助于更准确地定位问题。</think>这个错误是由于项目中存在重复的类定义导致的,具体涉及两个Kotlin相关模块中的ASM工具类冲突。以下是逐步分析和解决方案: **根本原因分析** 1. 冲突模块:`kotlin-compiler-embeddable`和`kotlin-gradle-plugin`都包含`org.jetbrains.org.objectweb.asm.util`包下的同名类 2. 典型触发场景:当使用较旧版本的Kotlin插件(如1.4.x)时,其内部依赖的ASM库存在重复打包 **解决方案步骤** 1. **统一Kotlin版本** ```gradle // 在项目根build.gradle中确保所有Kotlin组件版本一致 buildscript { ext.kotlin_version = '1.8.22' // 推荐使用1.7.20+ dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } ``` 2. **排除重复依赖** ```gradle // 在app模块的build.gradle中添加排除规则 configurations { all*.exclude group: 'org.jetbrains.org.objectweb.asm', module: 'asm-util' all*.exclude group: 'org.jetbrains', module: 'kotlin-compiler-embeddable' } ``` 3. **强制使用统一ASM版本** ```gradle // 在项目根build.gradle中添加全局约束 configurations.all { resolutionStrategy { force 'org.ow2.asm:asm:9.4' force 'org.ow2.asm:asm-util:9.4' } } ``` 4. **清理构建缓存** ```bash ./gradlew cleanBuildCache ./gradlew --stop rm -rf $HOME/.gradle/caches/ ``` **验证方法** 1. 查看依赖树: ```bash ./gradlew :app:dependencies --configuration runtimeClasspath ``` 2. 确认输出中不再出现`Duplicate classes`警告 **升级建议** - 若项目允许,建议升级到Kotlin 1.8.x+,该问题在较新版本中已通过优化依赖管理得到解决 - 检查Android Gradle Plugin版本是否与Kotlin版本兼容(参考官方兼容性文档) **技术背景** 该问题源于Kotlin编译器内部使用的ASM库(Java字节码操作框架)在多个子模块中的重复打包。在构建时,Gradle的依赖解析器会发现同一类的多个实例,违反JVM的类唯一性原则。通过依赖排除和版本强制可以确保类加载器只获取单一版本。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值