跨平台还能怎么玩?试试 Kotlin Multiplatform Mobile

前言

说到跨平台,我们很容易联想到 ReactNativeFlutter 等业内比较有名的框架,通过在不同平台复用一套代码,从而提高生产力,同时保证各端逻辑的一致性,他们确实做到了。但是,目前的跨平台框架都无法做到完美复用,比如双端 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 依赖项。
image.png

image.png

Shared 模块由三个 SourceSet(源集) 组成:androidMainiosMaincommonMain。源集是一个 Gradle 概念,用于逻辑上组合在一起的许多文件,其中每个组都有自己的依赖项。在 KMM 中,共享模块中的不同源集可以针对不同的平台。

尝试

作为逻辑层跨平台,我们主要关心网络请求、数据解析、数据缓存、多线程等问题。

这里用一个简单的 demo 来尝试一下 KMM,功能是展示天气信息,支持从网络和数据库缓存获取数据,网络获取成功后对数据进行缓存,然后即可展示缓存数据。这里除了UI之外,全部用 KMM 实现。

数据定义

KMM 支持使用 kotlinx.serialization 插件对数据进行序列化和反序列化。

image.png

用过 Gson 的同学应该很熟悉,和 Gson 的使用方式几乎完全一样,除了导入的包名不同。

网络请求

在 KMM 中,我们使用 Ktor 来进行网络请求。

Ktor 是一个轻松构建联网应用(web 应用、 HTTP 服务、 移动应用以及浏览器应用)的框架。 现代的联网应用需要异步化来提供最佳的用户体验,而 Kotlin 协程为此提供了极其简便的方式。
Ktor 的目标是为联网应用提供端到端的多平台应用框架,虽然还没有完全实现。目前支持 JVM 客户端与服务器场景,以及 JavaScript、iOS 与 Android 客户端,而我们(官方)正努力将服务端支持引入到原生(native) 环境,并将客户端支持引入到其他原生平台。

可以看出,Ktor 就是为了跨平台而生的。

使用非常简单

image.png

通过泛型直接反序列化为实体,非常方便。

有些同学可能要问了,JSON 序列化效率太低了,能不能用 ProtoBuf

当然可以,Ktor 目前已经支持 JSONXMLCBORProtoBuf 序列化格式,只需替换依赖即可,由于 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 插件会帮我们自动生成代码。

image.png

为了方便,我们的数据库中仅定义一个 json 列,不再定义每个属性,数据库存储前先通过 JSON 序列化。这里仅用于演示 SQLDelight 的使用,请大家不要效仿。

Sync 一下,即可在 build 目录中看到生成的文件,这里不再一一展示。

image.png

值得一提的是,由于在不同平台上 SQL 的实现也不一样,因此需要在 androidMainiosMain 源集中分别创建数据库驱动的实现,不过这些 SQLDelight 已经封装好了,我们只需要导入即可。

这里需要用到 KMM 中的一个常用关键字 expect,声明了 expect 的类,即表示需要在各个平台上分别实现,详见 Connect to platform-specific APIs | Kotlin。

commonMain 中声明 DatabaseDriverFactory

image.png

androidMainiosMain 分别实现,并添加 actual 关键字

image.png

最后,创建 Database 类来封装对数据库的操作

image.png

至此,我们已经可以使用 KMM 完成天气数据的网络获取和数据库缓存。

image.png

UI 的代码这里不再展示,我们来看一下最终效果

image.gif

小结

从可用性方面来说,在 KMM 上可以使用开源框架进行网络请求、数据缓存等,常见的数据格式都已经支持,并且这些开源库都已经提供稳定版本;从易用性方面来说,作为 Androider,几乎是0成本上手,且开源框架的使用都非常简单,甚至比 Android 原生的开源框架(OkHttpRoom 等)使用更简单,iOSer 可能需要一定的学习成本。综上,我认为 KMM 在功能上已经可以满足基础的逻辑层跨平台诉求,在使用上也非常简单。

最后附上 demo 源码。

Features

易上手

由于使用 Kotlin 语言,因此 Android 同学不需要学习成本,iOS 同学需要了解 Kotlin 的语法,即便如此,作为一门现代语言,Kotlin 还是非常容易上手的!

性能

作为跨平台框架,性能是一个关键指标。

在 Android 上,Kotlin 代码会被编译成 JVM 字节码,即 class 文件,最终打包到 dex 中,因此可以认为和原生性能一致。

image.png

在 iOS 上,Kotlin/Native 编译器会将 Kotlin 代码转换为所谓的 LLVM IR,这与 Swift 编译器的形式相同,最终,会使用和 Swift 相同的工具链,将这个 LLVM IR 会被转换为原生可执行的二进制文件。

image.png

官方的流程图

image.png

由于使用原生同样的编译形式,KMM 相比其他跨平台框架,在性能上更有优势。使用根据 JetBrains 官方的描述,KMM 目前已经非常接近原生应用的性能,甚至已经可以和原生持平,而且,他们还没有使用所有的优化手段,未来, KMM 的性能将有进一步提升。

可能有些同学会问,KMM 与 Rust 性能对比如何?

原生互操作

不论是在 React Native 还是 Flutter 上,JavaScript/Dart 与原生代码相互操作都是非常麻烦的,需要通过“桥接通讯”来实现。

由于 KMM 可以编译为和原生库一样的形式,因此,可以和原生开发语言直接相互调用
同时,KMM 代码也可以很容易的打包为 Library,提供给其他 APP 使用。

image.png

轻量

由于 KMM 最终的编译产物是基于双端标准组件输出,因此无需内置多套引擎 (runtime),包体积增量更少,同时 iOS 端审核被拒风险也比较小。

UI 跨平台*

你以为 KMM 会止步于逻辑层跨平台吗?

NO,根据官方规划,KMM 的 UI 跨平台功能已经在 KMM 的计划之中,并附带了一张效果图

image.png

关注 Compose 的同学应该知道,Compose Multiplatform UI 跨平台框架在 2021 年底就已经发布了稳定版,支持 Android、Web 和桌面的 UI 跨平台,只是迟迟没有支持 iOS,现在只需要解决在 iOS 上的复用,虽然目前还没有任何阶段性成果,但是也值得期待!

有些同学可能对 Jetpack ComposeCompose 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 上的协程存在性能问题,官方仍然在优化中。

你可能感兴趣的:(跨平台还能怎么玩?试试 Kotlin Multiplatform Mobile)