【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...

相关推荐
上趣工作室4 分钟前
vue2在el-dialog打开的时候使该el-dialog中的某个输入框获得焦点方法总结
前端·javascript·vue.js
家里有只小肥猫4 分钟前
el-tree 父节点隐藏
前端·javascript·vue.js
fkalis5 分钟前
【海外SRC漏洞挖掘】谷歌语法发现XSS+Waf Bypass
前端·xss
陈随易1 小时前
农村程序员-关于小孩教育的思考
前端·后端·程序员
云深时现月1 小时前
jenkins使用cli发行uni-app到h5
前端·uni-app·jenkins
昨天今天明天好多天1 小时前
【Node.js]
前端·node.js
2401_857610032 小时前
深入探索React合成事件(SyntheticEvent):跨浏览器的事件处理利器
前端·javascript·react.js
雾散声声慢2 小时前
前端开发中怎么把链接转为二维码并展示?
前端
熊的猫2 小时前
DOM 规范 — MutationObserver 接口
前端·javascript·chrome·webpack·前端框架·node.js·ecmascript
天农学子2 小时前
Easyui ComboBox 数据加载完成之后过滤数据
前端·javascript·easyui