Kotlin 多平台使用入门

1. 准备工作

前提条件

  • 具备相关知识,知道如何构建 Jetpack Compose 应用。
  • 具备 Kotlin 使用经验。
  • Swift 语法有基本的了解。

所需条件

学习内容

  • 了解 Kotlin 多平台开发的基础知识。
  • 如何跨平台共享代码。
  • 如何在 Android 和 iOS 上连接共享代码。

2. 进行设置

如要开始备份,请按以下步骤操作:

  1. 克隆 GitHub 代码库
$ git clone https://github.com/android/codelab-android-kmp.git

或者,您也能以 Zip 文件的形式下载该代码库:

  1. Android Studio 中,打开 get-started 项目,其中包含以下分支:
  • main:包含该项目的起始代码,您将在其中做出更改来完成此 Codelab。
  • end:包含此 Codelab 的解决方案代码。

本 Codelab 从 main 分支开始。您可以按照自己的节奏逐步完成此 Codelab。

  1. 如果您想查看解决方案代码,请运行以下命令:
$ git clone -b end https://github.com/android/codelab-android-kmp.git

或者,您也可以下载解决方案代码:

安装 XCode

若要构建并运行此 Codelab 的 iOS 部分,您需有 Xcode 和 iOS 模拟器:

  1. 从 Mac App Store 安装 Xcode(为此,您需要一个 Apple 账号)。
  2. 安装完毕后,启动 Xcode。
  3. 系统会显示一个对话框,其中会指明哪些组件是内置的,哪些组件需要下载。c4aba94d795dabee.png
  4. 检查 iOS 18.4(或更高版本)。
  5. 点击“下载并安装”。
  6. 等待组件安装完毕。

此 Codelab 已通过 Xcode 16.3 进行测试。如果您使用的是任何其他 Xcode 版本,并且遇到了问题,我们建议您下载此 Codelab 中提到的确切版本。

示例应用

此代码库包含一个使用 Jetpack Compose 构建的 Android 应用和一个使用 SwiftUI 构建的 iOS 应用。Android 项目位于 androidApp/ 文件夹中,而 iOS 项目位于 iosApp/ 文件夹中,该文件夹还包含用于通过 Xcode 运行的 KMPGetStartedCodelab.xcodeproj

3. Kotlin 多平台简介

借助 Kotlin Multiplatform (KMP),您只需编写一次代码,即可在多个目标平台(例如 Android、iOS、Web 和桌面)之间共享代码。通过利用 KMP,您可以最大限度地减少代码重复,保持一致性,并显著减少开发时间和工作量。

KMP 不会规定您需要共享代码库的多少部分或哪些部分。您可以自行决定哪些代码部分值得共享。

确定要共享的内容

借助这种共享代码,您可以保持一致性,并减少跨平台的代码重复。许多移动团队首先会共享一组独立的业务逻辑(例如数据库访问、网络访问等)和相关测试,然后随着时间的推移共享更多代码。

许多 Android Jetpack 库都支持 KMP。已实现跨平台的 Jetpack 库可根据目标平台提供多层级支持。如需查看库及其支持级别的完整列表,请参阅文档

例如,作为支持的库之一,Room 数据库会同时支持 Android、iOS 和桌面设备。这样一来,您就可以将数据库创建和相关数据库逻辑移植到通用 KMP 共享模块,同时保留这两个平台上的其他原生代码。

迁移数据库后,下一步可能是共享其他网域逻辑。然后,您还可以考虑使用 Android Jetpack 中的 ViewModel 多平台

如何编写平台专用代码

Kotlin Multiplatform 引入了可实现特定于平台的功能的新技术。

期望和实际声明

expect actual Kotlin 语言功能旨在支持 Kotlin 多平台代码库,并提供完整的 IDE 支持。

如果平台专属行为可封装在单个函数或类中,这种方法是理想的选择。这是一种灵活而强大的方式。例如,一个通用的 expect 类可以具有特定于平台的 actual 对应项,其中包含更多开放的公开修饰符、额外的超类型或不同的参数类型或修饰符。严格的接口 API 无法实现这类变化。此外,expect actual 会静态解析,这意味着平台专属实现会在编译时强制执行。

Kotlin 中的接口和实现方案

如果两个平台都需要遵循类似的 API,但实现方案不同,您可以在共享代码中定义一个接口,以替代预期和实际声明。通过这种方法,您可以使用不同的测试实现,或在运行时切换到其他实现。

此外,接口不需要 Kotlin 专门知识,因此熟悉其他语言接口的开发者可以轻松使用此选项。

接口位于通用共享代码中,实现方案位于原生代码 (Android 或 Swift) 中。

在某些情况下,您需要编写无法通过 KMP 代码提供的代码。在这种情况下,您可以在共享代码中定义接口,在 Kotlin 中为 Android 实现该接口,并在 Swift 中提供 iOS 对应项。通常,原生实现会通过依赖项注入或直接注入到共享代码中。此策略可在每个平台上提供自定义体验,同时为共享代码保留通用接口。

4. 通过 Android Studio 打开 Xcode 项目

安装 Xcode 后,您应确保自己可以运行 iOS 应用。

您可以直接在 Android Studio 中打开 iOS 项目。

  1. 切换“Project”窗格以使用Project View4f1a2c2ad988334c.png
  2. [root]/iosApp/ 文件夹中找到 KmpGetStartedCodelab.xcodeproj 文件 1357ffb58fe05180.png
  3. 右键点击该文件,然后依次选择 Open InOpen in Associated Application。这会在 Xcode 中打开 iOS 应用。f93dee415dfd40e9.png
  4. 在 Xcode 中运行项目,方法是按 ⌘+R,或前往“Product”菜单并选择“Run”

fff7f322c13ccdc0.png

5. 添加 KMP 模块

如需向项目添加 KMP 支持,请先为要在各个平台(Android、iOS)上重复使用的代码创建一个 shared 模块。

Android Studio 提供了一种使用 KMP 共享模块模板添加 Kotlin Multiplatform 模块的方式。

如需在 Android Studio 中创建 KMP 模块,请执行以下操作:

  1. 依次前往 File > New > New Module > Kotlin Multiplatform Shared Module
  2. 将软件包更改为 com.example.kmp.shared
  3. 点击 Finish4ef605dcc1fe6301.png
  4. 模块创建完成且 Gradle 同步完成后,项目中会显示一个新的 shared 模块。若要查看下方显示的视图,您可能需从 Android View 切换到 Project View。

eb58eea4e534dab2.png

由 KMP 共享模块模板生成的共享模块包含一些基本占位符函数和测试。这些占位符可确保模块从一开始就成功编译和运行。

重要提示:请注意 iosApp 文件夹与 iosMain 文件夹之间的区别。iosApp 文件夹包含独立的 iOS 应用代码,而 iosMain 是您刚刚添加的 KMP 共享模块的一部分。iosApp 包含 Swift 代码,而 iosMain 包含特定于 iOS 平台的 KMP 代码。

首先,您需要将新的共享模块作为依赖项链接到 :androidApp Gradle 模块中,以便应用能够使用共享代码:

  1. 打开 androidApp/build.gradle.kts 文件
  2. 在依赖项代码块中添加 shared 模块依赖项,如下所示
dependencies {
    ...
    implementation(projects.shared)
}
  1. 将项目与 Gradle 文件同步 c4a6ca72cf444e6e.png

验证代码对 shared 模块的访问权限

为了验证 Android 应用是否可以访问 shared 模块中的代码,我们将对应用进行简单更新。

  1. 在 KMPGetStartedCodelab 项目中,打开 androidApp/src/main/java/com/example/kmp/getstarted/android/MainActivity.kt 中的 MainActivity 文件
  2. 修改 Text 可组合项的内容,以在显示的字符串中添加 platform() 信息。
Text(
  "Hello ${platform()}",
)
  1. 点击键盘上的 ⌥(option)+return,然后选择 Import function 'platform'da301d17884eaef9.png
  2. 构建应用并在 Android 设备或模拟器上运行应用。

此更新会检查应用是否可以从 shared 模块调用 platform() 函数,该函数在 Android 平台上运行时应返回 "Android"

9828afe63d7cd9da.png

6. 将共享模块设置为 iOS 应用

Swift 无法像 Android 应用那样直接使用 Kotlin 模块,需要生成已编译的二进制框架 (XCFramework bundle)。XCFramework 文件捆绑包是一种二进制软件包,其中包含为多个 Apple 平台构建所需的框架和库。

共享库的分发方式

Android Studio 中的新模块模板已配置共享模块,以便为每个 iOS 架构生成框架。您可以在 shared 模块的 build.gradle.kts 文件中找到以下代码。

val xcfName = "sharedKit"

iosX64 {
  binaries.framework {
    baseName = xcfName
  }
}

iosArm64 {
  binaries.framework {
    baseName = xcfName
  }
}

iosSimulatorArm64 {
  binaries.framework {
    baseName = xcfName
  }
}

此步骤涉及设置 Xcode 以运行脚本来生成 Kotlin 框架,以及在 iOS 应用中调用 platform() 函数。

如需使用共享库,您需要按照以下步骤将 Kotlin 框架连接到 iOS 项目:

  1. 在 Xcode 中打开 iOS 项目(之前提到的 iosApp 目录),然后在项目导航器中双击项目名称 94047b06db4a3b6f.png 以打开项目设置
  2. 在项目设置的 Build Phases 标签页中,点击 + 并选择 New Run Script Phase。这会在所有其他阶段之后添加一个新的“运行脚本”阶段。d4907a9cb0a5ac6e.png
  3. 双击 Run Script 标题以重命名该标题。将默认的 Run Script 名称更改为 Compile Kotlin Framework,以便了解此阶段的功能。
  4. 展开 build 阶段,然后在Shell 下方的文本字段中输入脚本代码:
cd "$SRCROOT/.."
./gradlew :shared:embedAndSignAppleFrameworkForXcode

7b6a393e44ddbe60.png

  1. Compile Kotlin Framework 阶段拖到 Compile Sources 阶段之前27dbe2cf958c986f.png
  2. 在 Xcode 中构建项目,方法是点击 ⌘ + B 或转到Product menu并选择 Build。请注意,构建进度会显示在 Xcode 的顶部。bb0f9cb0f96d1f89.png

如果一切设置正确无误,项目将成功构建。

bb9b12d5f6ad0bac.png

通过这种方式设置运行脚本构建阶段,您可以从 Xcode 编译 iOS 项目,而无需切换到其他工具来编译共享模块。

验证代码对 shared 模块的访问权限

如需验证 iOS 应用能否成功访问 shared 模块中的代码,您需要对该应用进行与您为 Android 应用所做的相同简单更新。

  1. 在 iOS 项目的 Xcode 中,打开位于 Sources/View/ContentView.swiftContentView.swift 文件
  2. 在文件顶部添加 import sharedKit
  3. 修改 Text 视图,以便在显示的字符串中使用 \(Platform_iosKt.platform()) 包含 Platform_iosKt.platform() 信息。

文件的最终结果如下所示:

import SwiftUI
import sharedKit 

struct ContentView: View {
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            
            Text("Hello, \(Platform_iosKt.platform())!")
        }
        .padding()
    }
}
  1. 点击 ⌘+R 运行应用,或前往 Product 菜单并点击 Run

此更新会检查 iOS 应用是否可以从共享模块调用 platform() 函数,该函数在 iOS 平台上运行时应返回 "iOS"

8024b5cc4bcd3f84.png

7. 添加 Swift/Kotlin 接口增强器 (SKIE)

默认情况下,Kotlin 生成的原生接口是一个 Objective-C 标头。Swift 与 Objective-C 直接兼容,但 Objective-C 不包含 Swift 或 Kotlin 的所有现代功能。

这也是为什么在上一个示例中您无法直接在 Swift 代码中使用 platform() 调用的原因 - KMP 无法生成全局函数,因为 Objective-C 不支持全局函数,只支持封装在类中的静态函数,因此您需要添加 Platform_iosKt

为了使接口更适合 Swift,您可以使用 Swift/Kotlin Interface Enhancer (SKIE) 工具来改进 :shared 模块的 Swift 接口。

SKIE 的常见功能包括:

  • 更好地支持默认参数
  • 更好地支持密封层次结构 (sealed class, sealed interface)
  • switch 语句中使用了详尽处理,从而更好地支持 enum class
  • FlowAsyncSequence 之间的互操作性
  • suspend funasync func 之间的互操作性
  1. co.touchlab.skie Gradle 插件添加到 libs.versions.toml 文件中:
[versions]
skie = "0.10.1"

[plugins]
skie = { id = "co.touchlab.skie", version.ref = "skie" }
  1. 将插件添加到根 build.gradle.kts 文件中
plugins {
   ...
   alias(libs.plugins.skie) apply false
}
  1. 将插件添加到 :shared 模块 build.gradle.kts 文件中:
plugins {
   ...
   alias(libs.plugins.skie)
}
  1. Gradle 同步项目

移除静态函数调用

现在,当您重新构建 iOS 应用时,可能不会立即注意到任何变化,但您可以移除 Platform_iosKt 前缀,并让 platform() 函数充当全局函数。

Text("Hello, KMP! \(platform())")

这是因为 SKIE(以及其他功能)利用了 Swift API 备注,后者会添加有关 API 的信息,以便更好地从 Swift 代码中使用 API。

8. 恭喜

恭喜,您已向 Android 和 iOS 项目添加了第一个共享 Kotlin Multiplatform 代码。虽然这只是一个最基本的起点,但您现在可开始探索更多高级功能和使用案例,以便通过 KMP 共享代码。

后续操作

在下一篇codelab 中,了解如何使用 Jetpack Room 在 Android 和 iOS 之间共享数据层。

了解详情