Kotlin Multiplatform 入门

1. 开始之前

前提条件

  • 了解如何构建 Jetpack Compose 应用。
  • 有 Kotlin 使用经验。
  • 基本了解 Swift 语法

您需要准备什么

您将学到什么

  • 了解 Kotlin Multiplatform 的基础知识。
  • 如何在平台之间共享代码。
  • 如何在 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 中提及的 exact 版本。

示例应用

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

3. Kotlin Multiplatform 简介

Kotlin Multiplatform (KMP) 允许您编写一次代码,并在多个目标平台(如 Android、iOS、Web 和桌面端)之间共享。通过利用 KMP,您可以最大程度地减少代码重复,保持一致性,并显著减少开发时间和精力。

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

决定共享哪些内容

这些共享代码使您能够在跨平台时保持一致性并减少重复。许多移动团队首先共享一组离散的业务逻辑(例如,数据库访问、网络访问等)及其相关的测试,然后随着时间的推移共享额外的代码。

许多 Android Jetpack 库支持 KMP。已实现多平台兼容的 Jetpack 库根据目标平台提供多个级别的支持。有关库及其支持级别的完整列表,请参阅文档

例如,其中一个支持的库是 Room 数据库库,它支持 Android、iOS 和桌面端。这允许您将数据库创建和相关的数据库逻辑移植到通用的 KMP 共享模块中,同时保留两个平台上的其他原生代码。

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

如何编写平台特定代码

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

Expect 和 actual 声明

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

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

Kotlin 中的接口和实现

如果两个平台需要遵循相似的 API 但实现不同,您可以在共享代码中定义接口,作为 expect 和 actual 声明的替代方案。此方法使您可以使用不同的测试实现,或在运行时切换到不同的实现。

此外,接口不需要特定的 Kotlin 知识,这使得熟悉其他语言中接口的开发者更容易上手此选项。

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

在某些情况下,您需要编写 KMP 代码中不可用的代码。在这种情况下,您可以在共享代码中定义接口,在 Kotlin 中实现 Android 部分,并在 Swift 中提供 iOS 对应部分。通常,原生实现然后通过依赖注入或直接注入到共享代码中。此策略允许在每个平台上获得定制体验,同时为共享代码维护一个通用接口。

4. 从 Android Studio 打开 Xcode 项目

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

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

  1. 切换项目窗格以使用 Project View4f1a2c2ad988334c.png
  2. [root]/iosApp/ 文件夹中找到 KmpGetStartedCodelab.xcodeproj 文件1357ffb58fe05180.png
  3. 右键点击文件,然后选择 Open InOpen in Associated Application。这将使用 Xcode 打开 iOS 应用。f93dee415dfd40e9.png
  4. 通过点击 ⌘+R 或导航到 Product 菜单并选择 Run,在 Xcode 中运行项目

fff7f322c13ccdc0.png.

5. 添加 KMP 模块

要向您的项目添加 KMP 支持,首先创建一个 shared 模块,用于在平台之间(Android、iOS)重用的代码。

Android Studio 提供了一种使用其 KMP Shared module 模板添加 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 Shared module 模板生成的共享模块包含一些基本的占位函数和测试。这些占位符确保模块从一开始就编译和运行成功。

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

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

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

验证代码对 shared 模块的访问

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

  1. 在 KMPGetStartedCodelab 项目中,打开位于 androidApp/src/main/java/com/example/kmp/getstarted/android/MainActivity.ktMainActivity 文件
  2. 修改内容 Text composable,将 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 bundle 是一个二进制软件包,包含构建多个 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. 展开构建阶段,并在 Shell 下方的文本字段中输入脚本代码
cd "$SRCROOT/.."
./gradlew :shared:embedAndSignAppleFrameworkForXcode

7b6a393e44ddbe60.png

  1. Compile Kotlin Framework 阶段拖动到 Compile Sources 阶段之前27dbe2cf958c986f.png
  2. 通过点击 ⌘+B 或导航到 Product 菜单并选择 Build,在 Xcode 中构建项目。请注意,构建进度显示在 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 classsealed interface
  • 更好地支持 enum class 并在 switch 语句中进行详尽处理
  • 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 Notes,它会添加有关 API 的信息,以便从 Swift 代码中更好地使用它。

8. 恭喜您

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

下一步是什么?

在下一个Codelab 中,学习如何使用 Jetpack Room 在 Android 和 iOS 之间共享数据层。

了解详情