Android 问题00_IncompatibleComposeRuntimeVersionException
Android 问题00 —— IncompatibleComposeRuntimeVersionException
问题背景
在真实项目过程中开发 SDK,需要相应的开发一个 DEMO App,一方面测试 SDK 功能,另一方面根据实际场景构建一个可用的 App,模拟现实用途。这个过程中发现 App 开发用的还是老旧的 XML 布局格式,然后使用 Activity/Fragment 形式加载(Android 刚出生时的布局/页面加载方式)。我打算引入 Compose,一种较新的 UI 构建库,目的是想组内的开发同事来逐步认识更高效的布局/页面开发方式,同时期望项目内的组员可以多关注一些 Android 的新技术。为什么引入 Compose? 在文章最后说明。这里讲引入过程中遇到的问题。
先来看项目结构,是一个简单的两个 modules 组成的 Project。
这个 Project 中 app module 依赖于 sdk module,sdk 包含主要的业务功能。现在主要针对这个 Project 作 Compose 的引入,原始的工程只应用 Java 语言,layout 的加载方式是最老的 xml 方式。Compose 已经推出了很久,Kotlin 也是 Android 首推的开发语言,用以构建 layout,确是高效,且更加安全。
下来就是引入 Compose 的问题了。
开始情况,考虑的是只在 app 模块中引入 Compose,sdk 有 Kotlin 业务代码,没有 Compose 构建的 UI,因此一开始时就有应用 Kotlin。
// sdk module
plugins {
id 'com.android.library'
// ...
id 'org.jetbrains.kotlin.android' // Kotlin
}
/// ....
具体问题
各层级的 build.gradle
文件内 kotlin/compose 相关配置如下。
在 Project 项目级 build.gradle
中配置 KGP
。
// Root Project
buildscript {
repositories {
// ...
}
dependencies {
classpath libs.androidx.navigation.navigation.safe.args.gradle.plugin
classpath libs.google.services
classpath libs.fat.aar
// ...
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0" // 建议与 IDE settings 中的版本一致
}
// ...
}
plugins {
id 'com.android.application' version '7.4.2' apply false
id 'com.android.library' version '7.4.2' apply false
// ...
id 'org.jetbrains.kotlin.android' version '1.9.0' apply false // 指定 android kotlin 版本
}
// ...
如上面的文件配置,在 buildscript{}
的 dependencies{}
block 中配置 KGP 版本 org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0
。 这个版本值建议与 “Android Studio > File > Settings > Other Settings > Kotlin Compiler” 设置标签页中的默认版本一致。
app 模块级 build.gradle
文件配置
plugins {
id "com.android.application"
id 'org.jetbrains.kotlin.android' // 应用 KGP 插件
}
android {
// ...
buildFeatures {
compose true // 开启 Compose
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.1" // 显示指定 Kotlin compiler 版本。
}
kotlinOptions {
jvmTarget = "11"
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.9.0" // 显示设置 Kotlin 版本 1.9.0.
implementation platform('androidx.compose:compose-bom:2024.01.00') // 设置 BoM 库管理版本。设置了这个库之后,如下面的 compose ui 库就不需要指定版本号,均由 BoM 库管理版本。
implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.compiler:compiler:1.5.1'
implementation 'androidx.activity:activity-compose:1.6.0'
implementation 'androidx.compose.material3:material3'
// ...
}
上面的设置中
plugins{}
中设置应用 kotlin 插件。buildFeatures{}
中开启 compose。composeOptions{}
中显示设置 kotlin 编译器版本,以兼容 kotlin 1.9.0 版本。kotlinOptions{}
中设置目标 JDK 版本,用以生成字节码规范的版本。dependencies{}
中设置了 compose 有关的库。这里重点提下compose-bom
这个库。
sdk 模块级 build.gradle
文件配置
plugins {
id 'com.android.library'
// ...
id 'org.jetbrains.kotlin.android' // 应用 KGP 插件
}
上述 3 个 build.gradle
文件中的初始配置就这样完成了。
接着,在 IDE 中发起构建(build)后,等待构建结果。会在 console 中看到错误信息,下面是完整的 exception 信息。
> Task :sdk:compileDebugKotlin FAILED
e: androidx.compose.compiler.plugins.kotlin.IncompatibleComposeRuntimeVersionException: The Compose Compiler requires the Compose Runtime to be on the class path, but none could be found. The compose compiler plugin you are using (version 1.5.1) expects a minimum runtime version of 1.0.0.
at androidx.compose.compiler.plugins.kotlin.VersionChecker.noRuntimeOnClasspathError(VersionChecker.kt:172)
at androidx.compose.compiler.plugins.kotlin.VersionChecker.check(VersionChecker.kt:149)
at androidx.compose.compiler.plugins.kotlin.ComposeIrGenerationExtension.generate(ComposeIrGenerationExtension.kt:68)
at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.convertToIr$lambda$1(JvmIrCodegenFactory.kt:222)
at org.jetbrains.kotlin.psi2ir.Psi2IrTranslator.generateModuleFragment(Psi2IrTranslator.kt:107)
at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.convertToIr(JvmIrCodegenFactory.kt:255)
at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.convertToIr(JvmIrCodegenFactory.kt:59)
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.convertToIr(KotlinToJVMBytecodeCompiler.kt:224)
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:101)
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli$default(KotlinToJVMBytecodeCompiler.kt:47)
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:168)
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:53)
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:100)
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:46)
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:460)
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:62)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:476)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:399)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:280)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:124)
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:636)
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:101)
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1598)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360)
at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)
at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:712)
at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:587)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:828)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:705)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:704)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at java.base/java.lang.Thread.run(Thread.java:833)
Errors were stored into <path_to_project>/.gradle/kotlin/errors/errors-1737440966298.log
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':sdk:compileDebugKotlin'.
> A failure occurred while executing org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction
> Internal compiler error. See log for more details
按照错误提示,理解的应该是 Kotlin Compiler 找不到匹配的 runtime 库,且在 classpath 路径中没有找到需要的 runtime。 此时可以从两个角度考虑这个问题。
-
在 classpath 路径中确是了 kotlin 编译器需要的 runtime 环境。
因为 Kotlin 运行在 JVM 平台上,会考虑到可能在 classpath 路径下缺少相应的 runtime 库。从 Android IDE 灵活性上来讲,这个 runtime 库不应该直接放到 JVM 的 classpath 路径中,也不会手动将 runtime 库文件放到 classpath 路径下。因此这个错误原因可能性不大。
-
在
dependencies{}
配置中缺少了 runtime 的库依赖项。在
BoM
库管理的版本中有 compose runtime 库,在 app 模块级build.gradle
文件的dependencies{}
块中显示设置 compose runtime 库。dependencies { // ... implementation 'androidx.compose.runtime:runtime:1.5.1' // 显示指定 runtime 库和版本值,版本值与 Kotlin Compiler 值一致。 }
解决方案
根据错误信息提示,我只在 app/build.gradle
中添加了 runtime 库依赖。然后继续构建(build),遇到上面同样的提示信息。这个时候就与项目的结构有关系了,sdk/build.gradle
开始时配置了 kotlin android 插件的应用,没有指定任何 runtime 等库的依赖项。尝试在 sdk/build.gradle
中配置 compose runtime 等依赖项。
dependencies {
// ...
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.9.0" // 显示指定 Kotlin 版本
implementation platform('androidx.compose:compose-bom:2024.01.00') // BoM 版本
implementation 'androidx.compose.runtime:runtime:1.5.1' // 增加 runtime 配置
}
这样之后再构建(build),就可以通过了。
这里有一个更加优地使用 BoM 库的方法。从 BoM 2024.01.00
管理的库列表中看到 runtime 库的版本号是 1.6.0。
这个版本的 runtime 库所支持的 Kotlin Compiler 版本高于设置的 kotlinCompilerExtensionVersion
Kotlin Compiler 版本 1.5.1,在不显示指定 runtime 库的版本号的情况下,构建(build)无法通过。因此最好的方式是配置支持 Kotlin Compiler 1.5.1 的 BoM 版本,这个 BoM 版本是 2023.09.01
。 配置这个版本的 BoM 之后,在配置 runtime 时就不需要再指定版本号。
dependencies {
// ...
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.9.0" // 显示指定 Kotlin 版本
implementation platform('androidx.compose:compose-bom:2023.09.01') // BoM 版本
implementation 'androidx.compose.runtime:runtime'
}
这样发起构建(build)也可以通过编译。
总的来讲,在模块级的 build.gradle
文件中 apply kotlin android 插件后,同时设置 runtime 库提供运行时环境。
另外,这里设置的 KT/Compose 不是 2.0 版本,因为 2.0 版本开始 Compose, Compiler 需要分开独立开始管理。
为什么 Compose
Jetpack Compose 是用于构建原生 Android 界面的新工具包。它使用更少的代码、强大的工具和直观的 Kotlin API,可以帮助您简化并加快 Android 界面开发,打造生动而精彩的应用。它可让您更快速、更轻松地构建 Android 界面。在使用后,你会明显体会到它的优势。
- 精简代码
- 直观易读
- 加速开发
- 功能强大
BoM(Bill of Materials)物料清单
借助 Compose 物料清单 (BOM),开发时只需要设置了 BoM 的版本,即可管理所有 Compose 库版本。BoM 本身包含指向不同 Compose 库的稳定版的链接,以便它们能够很好地协同工作。在应用中使用 BoM 时,开发者无需向 Compose 库依赖项本身添加任何版本。更新 BoM 版本时,开发者使用的所有库都会自动更新到新版本。
更详细信息,在这里查看。