Kotlin多平台共享代码能否读取commonMain资源目录下的JSON文件
Kotlin多平台读取commonMain资源文件通用解决方案
问题根源
你当前的实现存在两个核心问题:
- 使用了JVM平台专属的
ClassLoaderAPI,无法兼容iOS等非JVM平台 - Android端返回null大概率是因为资源未正确配置打包路径、或是资源读取方式不够健壮
实现步骤
1. 先配置shared模块的Gradle打包规则
在shared模块的build.gradle.kts中添加如下配置,确保commonMain下的资源会被打包到各平台产物中:
kotlin { // 你的现有平台配置,比如android、iosX64、iosArm64等 sourceSets { val commonMain by getting { resources.srcDirs("src/commonMain/resources") } // Android端可选配置,确保资源不会被意外排除 val androidMain by getting { resources.srcDirs("src/commonMain/resources") } } // iOS端配置,将common资源打包进Framework的bundle中 listOf( iosX64(), iosArm64(), iosSimulatorArm64() ).forEach { iosTarget -> iosTarget.binaries.framework { baseName = "shared" embedBitcode(org.jetbrains.kotlin.gradle.plugin.mpp.BitcodeEmbeddingMode.DISABLE) // 绑定common资源到iOS bundle val resourcesPath = file("src/commonMain/resources") processResources.configure { from(resourcesPath) } } } }
如果用的是Groovy写法的gradle,自行对应语法调整即可。
2. 实现expect/actual跨平台读取逻辑
commonMain下的声明
expect object JsonStringResource { fun getResourceContent(fileName: String): String? fun getString(key: String): String? }
你可以把json解析逻辑也放在common层,只需要各个平台实现读取文件内容的能力即可。
Android端actual实现
import java.io.InputStream actual object JsonStringResource { actual fun getResourceContent(fileName: String): String? { return try { // 从classLoader读取资源流,比getResource更稳妥 val inputStream: InputStream? = JsonStringResource::class.java.classLoader ?.getResourceAsStream(fileName) inputStream?.bufferedReader()?.use { it.readText() } } catch (e: Exception) { e.printStackTrace() null } } actual fun getString(key: String): String? { val jsonContent = getResourceContent("strings-test.json") ?: return null // 这里可以用kotlinx-serialization解析json取对应key的值,示例省略解析逻辑 return null } }
如果Android端还是读不到,可以尝试把路径改成"/$fileName",也就是加前导斜杠从资源根路径查找。
iOS端actual实现
import platform.Foundation.NSBundle import platform.Foundation.NSString import platform.Foundation.NSUTF8StringEncoding actual object JsonStringResource { actual fun getResourceContent(fileName: String): String? { return try { // 拆分文件名和后缀 val nameWithoutExtension = fileName.substringBeforeLast('.') val extension = fileName.substringAfterLast('.', "") // 从main bundle查找资源路径 val path = NSBundle.mainBundle.pathForResource( name = nameWithoutExtension, ofType = extension ) ?: return null NSString.stringWithContentsOfFile( path = path, encoding = NSUTF8StringEncoding, error = null ) as String? } catch (e: Exception) { e.printStackTrace() null } } actual fun getString(key: String): String? { val jsonContent = getResourceContent("strings-test.json") ?: return null // 同Android端,json解析逻辑放在common层即可 return null } }
注意事项
- 文件名大小写必须完全匹配,iOS文件系统大小写敏感,写错会直接找不到资源
- 建议使用
kotlinx-serialization来解析json内容,它也是KMP全平台兼容的序列化库 - 如果需要支持桌面端等其他平台,参照iOS的思路,调用对应平台的资源读取API实现actual方法即可
内容的提问来源于stack exchange,提问作者Radim Janda
相关产品推荐
相关产品推荐

