前言
说到跨平台,我们很容易联想到 ReactNative
、Flutter
等业内比较有名的框架,通过在不同平台复用一套代码,从而提高生产力,同时保证各端逻辑的一致性,他们确实做到了。但是,目前的跨平台框架都无法做到完美复用,比如双端 UI 上的差异,导致还需要写很多适配代码,还有一个比较核心的问题,无法媲美原生性能。
简介
那 Kotlin Multiplatform Mobile(以下简称 KMM)又是什么呢,先看下官方的介绍
Kotlin Multiplatform Mobile 是一个用于 iOS 和 Android 应用开发的 SDK,允许您为网络、数据存储和分析以及 Android 和 iOS 应用的其他逻辑维护一个共享代码库。
不同于 Flutter
这样的完整跨平台框架,KMM 不包含渲染引擎,不支持 UI 层的代码共享,而像网络请求、数据解析和存储,以及一些 UI 无关的业务逻辑,都可以使用 KMM 共享代码。
有些同学可能也听说过 KMP、KN等,它们和 KMM 又是什么关系?
简单来说:
- KMP 一般指的就是 `Kotlin Mutiplatform`,可以认为是 Kotlin 跨平台的全集,包括 KMM 移动端跨平台和 `Kotlin JS` Web 跨平台
- KN 一般指的是 `Kotlin Native`,KN 是将 Kotlin 编译为 Native 二进制文件的技术,甚至可以在没有虚拟机的情况下运行,例如 KMM 上的 iOS 就是使用了 KN 的能力
- KMM 是利用了 JVM 和 KN 能力实现的针对 Android 和 iOS 平台的 Kotlin 框架:Android(`Kotlin/JVM`)和 iOS(`Kotlin/Naitve`)
熟悉 Android 开发的同学应该有所了解,KMM 并不是什么新鲜玩意,它在2020年就发布了第一个版本,基于 Kotlin 1.4 版本,不过由于一直处于实验阶段,因此使用和了解的人比较少。
那么为什么今天又提起它了,原因是它在2022年10月发布了 Beta 版本,同时 Android 官方宣布 Jetpack 开始要支持 KMM 了,目前 Collections 和 DataStore 已经可以通过依赖 -dev01
版本在多平台上使用,也就意味着 KMM 的社区支持有了官方保证,另外根据 JetBrains
的官方信息,如无意外,KMM 的稳定版会在 2023 年发布,因此距离 KMM 可以投入生产已经很接近了!
结构
在开始之前,我们先来了解一下 KMM 的开发模式。
每个 KMM 项目都包含三个模块:
-
Shared
是一个 Kotlin 模块,其中包含 Android 和 iOS 应用程序通用的逻辑,是在平台之间共享的代码。它使用 Gradle 作为构建系统,帮助您自动执行构建过程。共享模块内置到 Android 库和 iOS 框架中。 -
AndroidApp
是一个内置到 Android 应用程序中的 Kotlin 模块。它使用 Gradle 作为构建系统,AndroidApp
模块依赖于共享模块,并将共享模块用作常规的 Android 库。 -
iOSApp
是一个内置到 iOS 应用程序中的 Xcode 项目。它依赖于并使用共享模块作为 iOS 框架。共享模块可以用作常规框架或 CocoaPods 依赖项。
Shared
模块由三个 SourceSet(源集) 组成:androidMain
、iosMain
和 commonMain
。源集是一个 Gradle 概念,用于逻辑上组合在一起的许多文件,其中每个组都有自己的依赖项。在 KMM 中,共享模块中的不同源集可以针对不同的平台。
尝试
作为逻辑层跨平台,我们主要关心网络请求、数据解析、数据缓存、多线程等问题。
这里用一个简单的 demo 来尝试一下 KMM,功能是展示天气信息,支持从网络和数据库缓存获取数据,网络获取成功后对数据进行缓存,然后即可展示缓存数据。这里除了UI之外,全部用 KMM 实现。
数据定义
KMM 支持使用 kotlinx.serialization
插件对数据进行序列化和反序列化。
用过 Gson 的同学应该很熟悉,和 Gson 的使用方式几乎完全一样,除了导入的包名不同。
网络请求
在 KMM 中,我们使用 Ktor
来进行网络请求。
Ktor 是一个轻松构建联网应用(web 应用、 HTTP 服务、 移动应用以及浏览器应用)的框架。 现代的联网应用需要异步化来提供最佳的用户体验,而 Kotlin 协程为此提供了极其简便的方式。
Ktor 的目标是为联网应用提供端到端的多平台应用框架,虽然还没有完全实现。目前支持 JVM 客户端与服务器场景,以及 JavaScript、iOS 与 Android 客户端,而我们(官方)正努力将服务端支持引入到原生(native) 环境,并将客户端支持引入到其他原生平台。
可以看出,Ktor
就是为了跨平台而生的。
使用非常简单
通过泛型直接反序列化为实体,非常方便。
有些同学可能要问了,JSON 序列化效率太低了,能不能用 ProtoBuf
?
当然可以,Ktor
目前已经支持 JSON、XML、CBOR 和 ProtoBuf 序列化格式,只需替换依赖即可,由于 demo 使用的 API 接口使用的是 JSON 格式,因此不再演示 Protobuf
格式的解析方式。
数据缓存
前面提到,Jetpack 的 DataStore
组件已经支持 KMM,今天我们来看一下,在 KMM 中如何通过 DB 缓存数据。
这里我们要借助 SQLDelight
这个开源框架,它出自 Square
,与 OkHttp 同源。
SQLDelight 从您的 SQL 语句生成类型安全的 Kotlin API。它在编译时验证您的 Schema、Statements 和 Migrations,并提供如 Autocomplete 和 Refactoring 等 IDE 功能,使编写和维护 SQL 变得简单。
首先,我们要定义 SQL 文件,用于声明表结构和 CRUD 语句,然后 SQLDelight
插件会帮我们自动生成代码。
为了方便,我们的数据库中仅定义一个 json 列,不再定义每个属性,数据库存储前先通过 JSON 序列化。这里仅用于演示 SQLDelight
的使用,请大家不要效仿。
Sync 一下,即可在 build 目录中看到生成的文件,这里不再一一展示。
值得一提的是,由于在不同平台上 SQL 的实现也不一样,因此需要在 androidMain
和 iosMain
源集中分别创建数据库驱动的实现,不过这些 SQLDelight
已经封装好了,我们只需要导入即可。
这里需要用到 KMM 中的一个常用关键字 expect
,声明了 expect
的类,即表示需要在各个平台上分别实现,详见 Connect to platform-specific APIs | Kotlin。
commonMain
中声明 DatabaseDriverFactory
androidMain
和 iosMain
分别实现,并添加 actual
关键字
最后,创建 Database
类来封装对数据库的操作
至此,我们已经可以使用 KMM 完成天气数据的网络获取和数据库缓存。
UI 的代码这里不再展示,我们来看一下最终效果
小结
从可用性方面来说,在 KMM 上可以使用开源框架进行网络请求、数据缓存等,常见的数据格式都已经支持,并且这些开源库都已经提供稳定版本;从易用性方面来说,作为 Androider,几乎是0成本上手,且开源框架的使用都非常简单,甚至比 Android 原生的开源框架(OkHttp
、Room
等)使用更简单,iOSer 可能需要一定的学习成本。综上,我认为 KMM 在功能上已经可以满足基础的逻辑层跨平台诉求,在使用上也非常简单。
最后附上 demo 源码。
Features
易上手
由于使用 Kotlin 语言,因此 Android 同学不需要学习成本,iOS 同学需要了解 Kotlin 的语法,即便如此,作为一门现代语言,Kotlin 还是非常容易上手的!
性能
作为跨平台框架,性能是一个关键指标。
在 Android 上,Kotlin 代码会被编译成 JVM 字节码,即 class 文件,最终打包到 dex 中,因此可以认为和原生性能一致。
在 iOS 上,Kotlin/Native
编译器会将 Kotlin 代码转换为所谓的 LLVM IR
,这与 Swift
编译器的形式相同,最终,会使用和 Swift
相同的工具链,将这个 LLVM IR
会被转换为原生可执行的二进制文件。
官方的流程图
由于使用原生同样的编译形式,KMM 相比其他跨平台框架,在性能上更有优势。使用根据 JetBrains
官方的描述,KMM 目前已经非常接近原生应用的性能,甚至已经可以和原生持平,而且,他们还没有使用所有的优化手段,未来, KMM 的性能将有进一步提升。
可能有些同学会问,KMM 与 Rust 性能对比如何?
原生互操作
不论是在 React Native
还是 Flutter
上,JavaScript
/Dart
与原生代码相互操作都是非常麻烦的,需要通过“桥接通讯”来实现。
由于 KMM 可以编译为和原生库一样的形式,因此,可以和原生开发语言直接相互调用。
同时,KMM 代码也可以很容易的打包为 Library,提供给其他 APP 使用。
轻量
由于 KMM 最终的编译产物是基于双端标准组件输出,因此无需内置多套引擎 (runtime),包体积增量更少,同时 iOS 端审核被拒风险也比较小。
UI 跨平台*
你以为 KMM 会止步于逻辑层跨平台吗?
NO,根据官方规划,KMM 的 UI 跨平台功能已经在 KMM 的计划之中,并附带了一张效果图
关注 Compose 的同学应该知道,Compose Multiplatform
UI 跨平台框架在 2021 年底就已经发布了稳定版,支持 Android、Web 和桌面的 UI 跨平台,只是迟迟没有支持 iOS,现在只需要解决在 iOS 上的复用,虽然目前还没有任何阶段性成果,但是也值得期待!
有些同学可能对 Jetpack Compose
和 Compose Multiplatform
还不太了解,这里简单介绍下
`Jetpack Compose` 是 Google 针对 Android 推出的新一代声明式 UI 工具包,完全基于 Kotlin 打造,天然具备了跨平台的使用基础。
JetBrains 以 `Jetpack Compose`(后文简称 `compose-android`)为基础,相继发布了 `compose-desktop` 和 `compose-web` ,使 Compose 可以运行在更多不同平台。
小结
KMM 使用 Kotlin 语言,对 Android 开发者十分友好。
在性能方面,由于目前使用 KMM 的产品比较少,因此没有找到真实可信的测试数据,我们姑且相信官方介绍。
根据官方的描述,KMM 在 iOS 和 Android 上分别都会转为和原生一致的中间产物,因此接近原生性能,而且 KMM 团队后续还会着重优化 KMM 的性能,未来可期!
另外,可以和原生语言直接相互调用也是一大优势,同时基于 Kotlin 到原生的转换能力,KMM UI 跨平台能力同样值得期待!
不足
并发
在 Android 上,我们可以很方便的使用基于 JVM 线程池的 Kotlin Coroutines 来进行并发操作,但由于 Kotlin/Native 和 JVM 的内存模型差异,导致 iOS 上的协程只能使用单一后台线程,官方有一篇博客专门对 Kotlin/Native 的内存管理做了介绍 。
对此,Kotlin 团队重新设计了 Kotlin/Native 上的内存管理器,并在 Kotlin 1.7.20
版本默认启用
新的 Kotlin/Native 自动内存管理器解除了线程之间对象共享的现有限制,并提供完全无泄漏的并发编程原语,这些原语是安全的,不需要开发人员的任何特殊管理或注释。
看起来 Kotlin/Native 已经解决了并发的问题,不过是否彻底解决还需要线上产品来验证。
总结
本文主要介绍了 KMM 是什么,通过一个 demo 演示 KMM 跨平台复用代码的能力,包括网络请求、解析和数据缓存等,目前开源社区还不够完善,仅能满足基础功能,不过有 Android Jetpack 的加入,相信开源组件会越来越丰富。
根据 KMM 的编译形式和官方介绍,可以看出 KMM 在性能上接近原生,同时 KMM 支持和原生代码互操作,KMM UI 跨平台在官方计划之中,值得期待。但是目前 KMM 在并发操作上还存在不足,在 iOS 上的协程存在性能问题,官方仍然在优化中。