【KMP踩坑记录】Parcelize插件没法用了?

背景

KMP跨平台是一门新兴的技术,在此时入局未免会碰到各式各样的坑,而本系列文章将记录我使用 KMP 以来所踩的坑,作为记录,同时也帮助大家入局 KMP。

爬坑

一、K2编译器 + Parcelize + KMP 无法正常使用

背景:看到这个问题,大家肯定很疑惑,Parcelable 是Android 平台的特性,但是为什么需要在 KMP 上使用呢?

事情是这样的,Compose Multiplatform 作为一个从 Android 平台中迁移的 UI 框架,不可避免地会使用到 Android 的特性,像是 rememberSaveable默认情况下在Android 中的实现就是将内容存到 SavableStateRegistry中,并在 UI复原时从中读取复原。

如果我们不特地声明一个Saver,存基础数据例如 Int、Long、String当然没有问题,但是如果是一个数据类型,会自动将它作为一个 Parcelable对象存储。但是问题来了,common模块并没有 Parcelable这一个概念

那能不能声明一个 common 模块 Parcelable,它在 Android 平台上就正常实现,其他平台就空实现呢?答案是可以的。

kotlin 复制代码
// common package
expect interface CommonParcelable

@OptIn(ExperimentalMultiplatform::class)
@OptionalExpectation
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
expect annotation class CommonParcelize()

// android package
actual typealias CommonParcelable = android.os.Parcelable

// For parcelaze plugin
actual typealias CommonParcelize = kotlinx.parcelize.Parcelize

编写以上代码之后我们就可以对我们 common 模块的数据类使用了:

kotlin 复制代码
@CommonParcelize
@Stable
data class NoteItem(
    val id: Long,
    val title: String,
    val content: String
): CommonParcelable

// use it
var noteItems: List<NoteItem> by rememberSaveable {
    mutableStateOf(emptyList())
}

就这么简单几行代码就可以正常运行。使用默认的Saver把数据类当 Parcelable 存储而不用自己手动自定义 Saver真的很方便。

但是,这种写法在 K2 编译器无法使用了。

Kotlin 编译器有两个模块,其中一个是 Kotlin 语法模块,用于理解编写的代码,另一个是编译模块,用于将 Kotlin 代码生成特定平台可执行的产物。例如 Bytecode,JavaScript,Executable,Wasm。他们也称为 frontend 和 backend。

K2 Compiler 重写了 frontend,将编译次数降为一次,使编译拥有更优的性能,然而在编译期无法访问平台特性的代码,例如平台代码的类型、注解等等。

这将直接导致了 Parcelize 插件在使用 K2 的 Kotlin Multiplatform 项目不可用,由于 Parcelize 插件是 Android 平台特供的,需要检测 Android 平台代码的注解并生成代码,而 KMP 的 common 代码是不与平台相关的,这将导致现存的 expect/actual 标识的注解将无法在 K2 compilier 上使用。

而目前Parcelize插件官方的解决方式是增加编译参数,触发平台代码检测,从而使特定注解和其使用的地方增加一次平台代码检测,使 Parcelize 插件生效。

kotlin 复制代码
freeCompilerArgs.addAll("-P", "plugin:org.jetbrains.kotlin.parcelize:additionalAnnotation=example.CommonParcelize")

而这种方式也是有副作用的,增加了一次平台代码检测,也增加了编译时间。这也算是编译时间换编码时间

二、 Gradle二次混淆问题

gradle 8.5 以上的 library module 需要将 isMinifyEnabled 设置为 false,否则会造成多次混淆,即:App module将代码混淆一遍,Library 也将代码混淆一遍,最直接的现象是 release 包跑起来找不到类而闪退。

三、KMP Desktop找不到Class

在面向 Desktop 平台的产物中,我们会带一个JDK 到程序中。

一个 Hello World 程序直接达到了 200MB

而问题就出在了这个JDK,由于Gradle 无法自动检测你运行时实际上需要使用到哪些类,打进去的 JDK 是有删减的,当某个类没有打包进程序中,在运行时将会报错。

所以可以在打包前检测一下自己需要哪些类,又或者报错时通过错误信息查看哪些类丢失。在 Gradle 脚本中声明:

kotlin 复制代码
nativeDistributions {
    modules("java.sql")
}

当然,如果你懒,又或者你并不在意包体大小,可以直接声明把整个 JDK 打进包里,避免运行时异常。

kotlin 复制代码
nativeDistributions {
    includeAllModules = true
}

后记

由于新技术的的资料较少,部分内容未必准确,欢迎勘误。当然,Kotlin 迭代非常快,当你看到这篇文时,可能某些缺陷已经被修复,请以最新的为准。

参考

github.com/JetBrains/c...

kotlinlang.org/docs/whatsn...

issuetracker.google.com/issues/3157...

相关推荐
阿洵Rain39 分钟前
【Linux】环境变量
android·linux·javascript
学地理的小胖砸2 小时前
【GEE的Python API】
大数据·开发语言·前端·python·遥感·地图学·地理信息科学
hong1616882 小时前
PhpStorm中配置调试功能
android·ide·phpstorm
垦利不2 小时前
css总结
前端·css·html
八月的雨季 最後的冰吻3 小时前
C--字符串函数处理总结
c语言·前端·算法
6230_3 小时前
关于HTTP通讯流程知识点补充—常见状态码及常见请求方式
前端·javascript·网络·网络协议·学习·http·html
pan_junbiao4 小时前
Vue组件:使用$emit()方法监听子组件事件
前端·javascript·vue.js
DongGei4 小时前
安卓-音频焦点
android·微信·音视频
正在绘制中4 小时前
如何部署Vue+Springboot项目
前端·vue.js·spring boot
Keep striving5 小时前
SpringMVC基于注解使用:国际化
java·前端·spring·servlet·tomcat·maven