
引言
“一致性是关键”这句话经常在分布式系统环境中听到,但在移动和 Web 应用开发中也同样适用。在一个需要在多个平台上无缝运行单一应用程序的世界里,保持跨平台的一致性不仅仅是一个目标,而且是必要的。多年来,开发人员一直面临着一个艰难的选择:为每个平台构建独立的原生应用程序,还是采用跨平台框架。
传统上,为 Android 和 iOS 开发原生应用程序需要双倍的资源:两个代码库、两个工程团队,以及双倍的努力来保持功能和业务逻辑的同步。当应用程序需要一致的 UI 和相同的业务逻辑时,它们会共享驱动它们的代码。
这种需求催生了像 Flutter 和 React Native 这样的强大的跨平台工具。虽然这些工具非常有效,但它们通常伴随着重大的权衡。它们通常要求开发人员使用不同的 UI 渲染引擎(Flutter)或使用桥接(React Native),与原生代码相比,这可能会带来性能损失。
在这种情况下,KMP 提供了一种独特的方法。KMP 不是取代原生平台 SDK,而是与它们一起工作,允许开发人员通过直接使用平台 API 的典型访问方式来共享通用代码。具体来说,这意味着你可以创建一个 KMP 应用程序,通过使用原生 UI 工具包,保留每个平台的外观和感觉,类似于 React Native。但你还可以使用 Compose Multiplatform 走 Flutter 的路子,共享 UI 实现,尽管代价是它在 iOS 上看起来不那么“原生”。
总之,当与 Compose Multiplatform 搭配使用时,KMP 提供了一个引人注目的用例:编译成原生代码以获得出色的性能,类似于 Flutter,并保留使用原生 UI 工具包的选项,这种灵活性也存在于 React Native 中。
KMP正在成为跨平台开发的一种强大的替代方案,提供了一条共享代码的路径,而不会牺牲原生应用程序的性能和感觉。有了 KMP 作为替代方案,它也带来了自己的一系列权衡,本文将深入探讨这些问题。
虽然本文主要关注 Android 和 iOS 开发,但 KMP 的愿景更加广泛。它可以用来构建桌面应用程序(Windows、macOS、Linux)、Web(使用 Kotlin/JavaScript)甚至服务器端应用程序,所有这些都来自相同的共享逻辑。
什么是 Kotlin Multiplatform(KMP)?
KMP 允许开发团队在不同的平台上编写和共享代码,同时借助 Compose Multiplatform 提供构建原生用户界面的选项。它不是一个“全或无”的框架,也并不是简单地抽象掉平台。相反,它是一个复杂的工具,允许你共享应用程序中平台无关的部分,同时保持平台特定部分的原生特性。Android 开发者可以在 Kotlin 中编写 UI 代码,而 iOS 开发者可以在 Objective-C 或 Swift 中编写 UI 代码。但这并不意味着 UI 只能使用原生接口构建,这是一个重要的区别。KMP 提供了灵活性,允许你按照自己的方式构建用户界面。
KMP 的工作原理
KMP 具有特定的项目结构和 expect/actual
机制,使其能够跨平台工作。一个典型的 KMP 项目被划分为如下几个模块:
commonMain
包含通用代码。这段代码编译一次,并在所有目标平台上共享。你可以将业务逻辑、网络调用等放在这个模块中。androidMain
包含 Android 实现。Android 设备的 UI 可以在这个模块中用 Kotlin 编写。iosMain
包含 iOS 实现。iOS 设备的 UI 可以在这个模块中用 Swift 或 Objective-C 编写。
expect/actual
关键字是公共实现和平台特定实现之间的桥梁。在 commonMain
中,你可以期望存在一个类或函数,定义其 API。然后,在 androidMain
和 iosMain
中,你为该声明提供实际的特定于平台的实现。例如,你可能会期望一个函数在 commonMain
中生成一个唯一的标识符(UUID),然后分别在 androidMain
中使用 Android 的 UUID.randomUUID()
和在 iosMain
中使用 iOS 的 NSUUID().uuidString
提供实际的实现。
使用上述结构,可以同时为跨平台构建应用程序,而不会牺牲应用程序的性能。
Kotlin Multiplatform 的优势
降低工程成本
统一架构并允许相同的开发人员为多个平台编写代码可以显著降低工程成本,这是理所当然的。通过将业务逻辑、网络等放在共享的 commonMain
模块中,你消除了两次实现和测试同一功能的需求。这将直接减少重复工作。这也减少了特定于平台的错误,从而产生更健壮和可靠的产品。此外,它简化了质量保证流程,因为核心逻辑只需要测试一次。需要重点指出的是,这个优势也时由 Flutter 和 React Native 提供的。在这方面,KMP 与其他技术相比没有优势。
原生性能
与其他跨平台工具不同,KMP 让开发人员使用平台的原生工具构建用户界面。共享的 Kotlin 代码被编译成目标平台的预期格式,对于 Android 是 JVM 字节码,对于 iOS 是原生二进制文件。与 React Native 不同,运行时没有解释层,这使得应用程序能够实现类似原生的性能。
逐步采用
虽然 Flutter(使用 Dart)和 React Native(使用 Typescript 或 Javascript)需要“全有或全无”的采用方式,但 Kotlin Multiplatform 却不是这样。它可以逐步引入到代码库中,使过渡更加平滑。你可以将 KMP 逐步引入到现有的原生应用程序中,这有助于确保在整个过程中保持稳定。一个常见的起点是将单个功能或特定层迁移到共享的 KMP 模块中。
Android 团队的自然发展
随着谷歌正式将 Kotlin 作为 Android 开发的主要语言,大多数 Android 团队已经在使用它进行构建了。对于这些团队来说,过渡到 KMP 的进入门槛非常低。
Flutter、React Native、KMP:比较
尽管这三种技术都支持跨平台开发,但它们的操作原则不同,特别是在用户界面(UI)和总体理念方面。下面的表格总结了我们上面讨论的主要区别。
KMP 和 Compose Multiplatform 在 iOS 上的当前状态
在历史上,Kotlin 从未被用来开发过 iOS 应用程序。但是有了 KMP 和 Compose Multiplatform,现在这是可能的了。
长期以来,iOS 上的 KMP 故事都是“共享你的逻辑,原生构建你的 UI”。虽然这是一种强大的方法,但它仍然意味着 UI 开发是一个特定于平台的任务。随着 1.8.0 稳定版本的发布,这种情况发生了变化。截至 2025 年 5 月 6 日,面向 iOS 的 Compose Multiplatform 已经达到了一个稳定的、生产就绪的里程碑。
这一里程碑对 KMP 生态系统来说是一个巨大的胜利。Jetpack Compose 是谷歌面向 Android 的现代声明式 UI 工具包。有了 Compose Multiplatform,你现在可以使用完全相同的 Kotlin 代码来定义 Android 和 iOS 的 UI。这意味着你不仅可以共享业务逻辑,还可以共享 UI 本身。该框架现在提供了与 iOS 版本的 Jetpack Compose 相同的功能,使其成为新项目的可行选择。在我看来,Compose Multiplatform 1.8.0 不仅对 iOS 开发有价值,而且还实现了 KMP 的真正价值主张,开发者现在可以引导整个应用程序了。
此外,对于某些屏幕使用原生 Swift/UIKit 进行混合匹配,而对于其他屏幕使用 Compose,提供了最大的灵活性。为此,你依赖于框架提供的特定包装器组件,这些组件利用了 SwiftUI/UIKit 互操作性。例如,要在原生 SwiftUI 应用程序中使用 Compose Multiplatform 屏幕,将你的可组合 UI 包装在 ComposeUIViewController
中。然后,在 SwiftUI 代码中,创建一个 UIViewControllerRepresentable
协议的结构,它将管理控制器。要在 Compose UI 中使用原生 SwiftUI 屏幕,将 SwiftUI 视图托管在 UIHostingController
中,该控制器可以直接嵌入到 Compose 布局中,使用 UIKitView
可组合项。
总的来说,每当采取混合和匹配的方法时,都会有一些控制器可以将不同的 UI 方法嵌入到应用中。虽然可能涉及到陡峭的学习曲线,但这种方法仍然提供了显著的灵活性。
Kotlin Multiplatform 的大用户
KMP 不仅仅是一个理论概念,它正在被奈飞、麦当劳和飞利浦等大型全球公司用于生产。
奈飞(Netflix)被认为是 KMP 的早期和重要采用者,因为他们在 2020 年 10 月就采用了它。他们使用 Kotlin Multiplatform 统一了应用程序的业务逻辑,确保所有用户的功能行为一致,无论设备如何。他们还将其用于他们的内部“Prodicle”应用程序,该应用程序支持电视和电影项目的实体制作。
我们的 Android 和 iOS 工作室应用程序具有共享的架构,两个平台上都有相似或在某些情况下相同的业务逻辑——Netflix
奈飞能够实现 50%的代码与底层平台的解耦。展望未来,奈飞还谈到了将他们的工作室应用程序发展成一个薄 UI 层的可能性,该层共享用 KMP 编写的相同业务逻辑。
麦当劳(McDonald)在应用程序中引入应用内支付时,就开始使用 KMP(早期版本中称为 KMM)。在验证了这种方法之后,他们将 KMP 的使用扩展到了其他服务中。
将 KMM 集成到应用程序流程中,除了在每个平台上正确显示本地化数据和产品所需的业务逻辑之外,还允许我们在一个位置内开发解析和存储逻辑——McDonald’s.
例如,为了处理不同国际地区的客户的不同需求,他们将管理特定地区产品和数据的逻辑集中到一个共享的 KMP 模块中。在这里,他们将 KMP 引入到他们的业务逻辑中,以处理特定于语言环境和设备的产品和数据。
最终,麦当劳重写了他们的整个应用程序以使用 KMP。通过这一战略转变,他们将独立的Android和iOS团队合并成一个更统一、更高效的移动团队。
飞利浦(Philips)在 2018 年采用了 KMP,作为从原生开发转向的一种方式。飞利浦利用 Kotlin Multiplatform 开发一个共享的 SDK,用于设备通信和数据同步,以实现他们的连接消费和个人健康产品的移动应用连接,如牙刷和空气净化器。使用 KMP,他们能够保持共享的业务逻辑。
BLE 接口的差异太大了,以至于你无法设计出一个同时匹配它们的设计。因此,我们将继续保持以原生方式进行这部分工作——Philips。
他们最初在蓝牙低功耗(BLE)的蓝牙 API 上遇到了一些问题。为了解决这个问题,他们保留了蓝牙堆栈的原生实现,这表明开发人员并没有将所有的代码库都锁定在 KMP 中。
我们可以看到,随着越来越多的公司采用 KMP,我们看到 KMP 正在开始兴起。公司开始缓慢起步,然后随着他们开始看到好处,他们的用例也在增加。我建议你查看所有这些案例研究,因为我在本文中只提到了其中的一些精选示例。
挑战和考虑因素
虽然 Kotlin Multiplatform 为跨平台开发提供了一个有希望的方法,但必须承认 KMP 当前还有局限性。
生态系统的成熟度
一个主要挑战是与 Flutter 和 React Native 等现有工具相比,开发者社区和支持生态系统较小。虽然核心功能得到了很好的支持,但寻找 KMP 就绪的库以满足高度特定的需求,如高级蓝牙交互或后台服务,可能仍然需要编写平台特定的实现,正如我们在飞利浦案例中看到的。这有时意味着开发者需要创建自己的原生解决方案包装器,这并不理想。
iOS 团队的学习曲线
对于 iOS 团队来说,采用 Kotlin 和 Gradle 构建系统可能会有一个学习曲线,这些在 Android 生态系统中是标准的,但对大多数 iOS 开发者来说是新的。
Kotlin/Native 的互操作性
Kotlin/Native 与 Swift/Objective-C 之间的互操作性有其自身的限制。Kotlin/Native 通过 Objective-C 与 Swift 提供间接互操作性。例如,一些 Kotlin 语言特性可能在 Objective-C 中没有直接等价物,这就要求开发人员在开发过程中了解这些差异。这个要求可能会成为一大障碍,因为大多数开发人员在开始开发之前并不知道这一点。泛型就是一个例子,其中类型信息在翻译过程中丢失。开发人员需要注意传递的类型,并围绕此进行检查。
工具和开发工作流程
虽然工具正在持续改进,但它仍然不如当前的原生开发工具那样无缝。JetBrains 正在积极开发 KMP 插件,让它变得更容易。
尽管面临的挑战很多,也不容忽视,但这个环境正在迅速变化。公平地说,对 Kotlin 本身的支持已经成熟,并且还在继续增长,KMP 社区是这一趋势的直接受益者。随着采用率的增加,共享知识和开源库的数量也将增加。
结论
跨平台开发一直是在开发效率与原生性能和外观感觉之间权衡的练习。有了 Kotlin Multiplatform,这些权衡在某种程度上减少了。你获得了共享代码库的效率,同时提供了几乎与原生一样好的性能。
虽然核心功能得到了很好的支持,但寻找 KMP 就绪的库以满足高度特定的需求,如高级 BLE 交互或后台位置服务,可能仍然需要编写平台特定的实现。这确实带来了一个问题,因为需要所有这些功能的应用不能仅使用 KMP 构建。但正如前面提到的,这是一个迭代过程,由于 KMP 开发是活跃的,因此将引入更好的支持。
KMP 提供了一条灵活而强大的前进道路,允许团队为多平台世界构建一致的、高性能的应用程序,而无需放弃其原生根基。对 Kotlin 的支持很强大,其多平台能力只会从这里开始增长。
原文链接:
https://www.infoq.com/articles/kotlin-multiplatform-evaluation/
评论