让您的 Android 应用程序在 iOS 上运行 – 教程
本教程将演示如何将现有的 Android 应用程序转换为跨平台程序,使其能够同时在 Android 和 iOS 上运行。 您将能够同时在同一个地方为 Android 和 iOS 编写代码。
本教程使用了一个示例 Android 应用程序,该程序包含一个用于输入用户名和密码的单屏幕界面。凭据将经过验证并保存到内存数据库中。
为了让您的应用程序同时在 iOS 和 Android 上运行, 您首先需要通过将部分代码移至共享模块来使代码实现跨平台。 之后,您将在 Android 应用程序中使用该跨平台代码,然后在新 iOS 应用程序中使用相同的代码。
如果您不熟悉 Kotlin Multiplatform,请先了解如何从头开始创建跨平台应用程序。
准备开发环境
在快速入门中,完成设置 Kotlin Multiplatform 开发环境的说明。
您需要一台装有 macOS 的 Mac 才能完成本教程中的某些步骤,例如运行 iOS 应用程序。 这是由于 Apple 的要求。
在 Android Studio 中,从版本控制创建一个新项目:
texthttps://github.com/Kotlin/kmp-integration-samplemaster分支包含项目的初始状态 —— 一个简单的 Android 应用程序。 要查看包含 iOS 应用程序和共享模块的最终状态,请切换到final分支。切换到 Project 视图:

使您的代码跨平台
要使您的代码跨平台,您将按照以下步骤操作:
决定哪些代码要进行跨平台处理
决定您的 Android 应用程序中哪些代码更适合在 iOS 中共享,哪些代码应保持原生。一个简单的规则是: 尽可能多地共享您想要复用的内容。业务逻辑在 Android 和 iOS 上通常是相同的, 因此它是复用的绝佳选择。
在您的示例 Android 应用程序中,业务逻辑存储在 com.jetbrains.simplelogin.androidapp.data 软件包中。 您的未来 iOS 应用程序将使用相同的逻辑,因此您也应该将其转换为跨平台。

为跨平台代码创建一个共享模块
用于 iOS 和 Android 的跨平台代码将存储在一个共享模块中。 Android Studio 和 IntelliJ IDEA 都提供了用于创建 Kotlin Multiplatform 共享模块的向导。
创建一个共享模块,以连接现有的 Android 应用程序和您未来的 iOS 应用程序:
在 Android Studio 中,从主菜单选择 File | New | New Module。
在模板列表中,选择 Kotlin Multiplatform Shared Module。 将模块名称保持为
shared并输入软件包名称:textcom.jetbrains.simplelogin.shared点击 Finish。向导将创建一个共享模块,相应地更改构建脚本,并开始 Gradle 同步。
等待同步完成。 您将在
shared目录中看到以下文件结构:
如果您想更好地了解生成的项目布局, 请参阅 Kotlin Multiplatform 项目结构基础。
将
shared/build.gradle.kts中的kotlin.android {}块替换为以下androidLibrary {}块, 因为shared模块将作为 Android 应用程序的一个库使用:kotlinimport org.jetbrains.kotlin.gradle.dsl.JvmTarget kotlin { androidLibrary { namespace = "com.jetbrains.simplelogin.shared" compileSdk = libs.versions.android.compileSdk.get().toInt() compilerOptions { jvmTarget = JvmTarget.JVM_11 } androidResources { enable = true } withHostTestBuilder { } withDeviceTestBuilder { sourceSetTreeName = "test" }.configure { instrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } } //... }
向共享模块添加代码
现在您已经有了一个共享模块, 在 shared/src/commonMain/kotlin/com.jetbrains.simplelogin.shared 目录中添加一些公共代码进行共享:
创建一个新的
Greeting类,代码如下:kotlinpackage com.jetbrains.simplelogin.shared class Greeting { private val platform = getPlatform() fun greet(): String { return "Hello, ${platform.name}!" } }将已创建文件中的代码替换为以下内容:
在
commonMain/Platform.kt中:kotlinpackage com.jetbrains.simplelogin.shared interface Platform { val name: String } expect fun getPlatform(): Platform在
androidMain/Platform.android.kt中:kotlinpackage com.jetbrains.simplelogin.shared import android.os.Build class AndroidPlatform : Platform { override val name: String = "Android ${Build.VERSION.SDK_INT}" } actual fun getPlatform(): Platform = AndroidPlatform()在
iosMain/Platform.ios.kt中:kotlinpackage com.jetbrains.simplelogin.shared import platform.UIKit.UIDevice class IOSPlatform: Platform { override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion } actual fun getPlatform(): Platform = IOSPlatform()
现在您拥有一个通用的 getPlatform() 函数,它返回一个包含平台名称属性的平台特定对象。
向您的 Android 应用程序添加对共享模块的依赖项
要在 Android 应用程序中使用跨平台代码,请将共享模块连接到该应用程序,将业务逻辑代码移动到该模块,并使这些代码实现跨平台。
在
app/build.gradle.kts文件中添加对共享模块的依赖项:kotlindependencies { // ... implementation(project(":shared")) }按照 IDE 的建议或使用 File | Sync Project with Gradle Files 菜单项同步 Gradle 文件。
在
app/src/main/java/目录中,打开com.jetbrains.simplelogin.androidapp.ui.login软件包下的LoginActivity.kt文件。为了确保共享模块已成功连接到您的应用程序,通过在
onCreate()方法中添加Log.i()调用,将greet()函数的结果写入日志:kotlinoverride fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() super.onCreate(savedInstanceState) Log.i("Login Activity", "Hello from shared module: " + (Greeting().greet())) // ... }按照 IDE 的建议导入缺失的类。
在工具栏中,点击运行配置下拉菜单旁边的调试图标:

在 Logcat 工具窗口中,在日志中搜索 "Hello",您将找到来自共享模块的问候语:

使业务逻辑实现跨平台
您现在可以将业务逻辑代码提取到 Kotlin Multiplatform 共享模块的 commonMain 源集中。 这将允许在 Android 和 iOS 上同时使用这些代码。
将业务逻辑代码
com.jetbrains.simplelogin.androidapp.data从app目录移至shared/src/commonMain目录中的com.jetbrains.simplelogin.shared软件包。
当 Android Studio 询问您想做什么时,选择移动软件包,然后批准重构操作。

忽略所有关于平台相关代码的警告,然后点击 Refactor Anyway。

通过将 Android 特定代码替换为跨平台 Kotlin 代码,或使用 expect 和 actual 声明连接到 Android 特定 API 来移除这些代码。详情请参阅以下部分:
使用跨平台代码替换 Android 特定代码
为了让您的代码在 Android 和 iOS 上都能良好运行,请尽可能在移动后的
data目录中将所有 JVM 依赖项替换为 Kotlin 依赖项。在
LoginDataValidator类中,将来自android.utils软件包的Patterns类替换为匹配电子邮件验证模式的 Kotlin 正则表达式:kotlin// 修改前 private fun isEmailValid(email: String) = Patterns.EMAIL_ADDRESS.matcher(email).matches()kotlin// 修改后 private fun isEmailValid(email: String) = emailRegex.matches(email) companion object { private val emailRegex = ("[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" + "\\@" + "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" + "(" + "\\." + "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" + ")+").toRegex() }移除
Patterns类的导入指令:kotlinimport android.util.Patterns在
LoginDataSource类中,将login()函数中的IOException替换为RuntimeException。IOException在 Kotlin/JVM 之外不可用。```kotlin // 修改前 return Result.Error(IOException("Error logging in", e)) ``` ```kotlin // 修改后 return Result.Error(RuntimeException("Error logging in", e)) ```同时也移除
IOException的导入指令:kotlinimport java.io.IOException
实现平台特定的 UUID 生成
在
LoginDataSource类中,fakeUser的通用唯一标识符 (UUID) 是使用java.util.UUID类生成的,该类在 iOS 中不可用。kotlinval fakeUser = LoggedInUser(java.util.UUID.randomUUID().toString(), "Jane Doe")尽管 Kotlin 标准库提供了一个用于生成 UUID 的类, 但为了练习,让我们在这个案例中使用特定于平台的功能。
在共享代码中为
randomUUID()函数提供expect声明,并在相应的源集中为每个平台(Android 和 iOS)提供其actual实现。 您可以了解更多关于连接到特定于平台的 API 的信息。将
login()函数中的java.util.UUID.randomUUID()调用更改为randomUUID()调用,您将为每个平台实现它:kotlinval fakeUser = LoggedInUser(randomUUID(), "Jane Doe")在
shared/src/commonMain目录的com.jetbrains.simplelogin.shared软件包中创建Utils.kt文件,并提供expect声明:kotlinpackage com.jetbrains.simplelogin.shared expect fun randomUUID(): String在
shared/src/androidMain目录的com.jetbrains.simplelogin.shared软件包中创建Utils.android.kt文件,并提供 Android 中randomUUID()的actual实现:kotlinpackage com.jetbrains.simplelogin.shared import java.util.* actual fun randomUUID() = UUID.randomUUID().toString()在
shared/src/iosMain目录的com.jetbrains.simplelogin.shared中创建Utils.ios.kt文件,并提供 iOS 中randomUUID()的actual实现:kotlinpackage com.jetbrains.simplelogin.shared import platform.Foundation.NSUUID actual fun randomUUID(): String = NSUUID().UUIDString()在
shared/src/commonMain目录的LoginDataSource.kt文件中导入randomUUID函数:kotlinimport com.jetbrains.simplelogin.shared.randomUUID
现在,Kotlin 将为 Android 和 iOS 使用特定于平台的 UUID 实现。
在 Android 上运行您的跨平台应用程序
运行 app 运行配置,以确保 Android 应用程序像以前一样工作。

使您的跨平台应用程序在 iOS 上运行
将 Android 应用程序转换为跨平台后,您可以创建一个 iOS 应用程序并在其中复用共享的业务逻辑。
在 Xcode 中创建一个 iOS 项目
在 Xcode 中,点击 File | New | Project。
在对话框中,切换到 iOS 选项卡:

选择 App 模板,然后点击 Next。
作为产品名称,指定 "simpleLoginIOS" 并点击 Next。

作为项目的存储位置,选择存放跨平台应用程序的目录,例如
kmp-integration-sample。在 Android Studio 中,您将获得以下结构:

为了与跨平台项目的其他顶级目录保持一致, 请关闭 Xcode,然后将
simpleLoginIOS目录重命名为iosApp。如果您在 Xcode 打开时重命名文件夹,您会收到警告,并且可能会损坏您的项目。

配置 iOS 项目以使用 KMP 框架
您可以直接设置 iOS 应用程序与 Kotlin Multiplatform 构建的框架之间的集成。
iOS 集成方法概述中介绍了此方法的替代方案(SwiftPM 和 CocoaPods)。
在 Android Studio 中,右键点击
iosApp/simpleLoginIOS.xcodeproj目录并选择 Open In | Open In Associated Application 以在 Xcode 中打开 iOS 项目。在 Xcode 中,在 Project 导航器中点击项目名称来打开 iOS 项目设置。
在左侧的 Targets 部分,选择 simpleLoginIOS,然后点击 Build Phases 选项卡。
点击 + 图标并选择 New Run Script Phase。

在运行脚本字段中粘贴以下脚本:
bashif [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\"" exit 0 fi cd "$SRCROOT/.." ./gradlew :shared:embedAndSignAppleFrameworkForXcode禁用 Based on dependency analysis 选项。 这确保 Xcode 在每次构建期间都运行脚本,并且不会每次都警告缺少输出依赖项。

将 Run Script 阶段向上移动,放置在 Compile Sources 阶段之前:

在 Build Settings 选项卡上,禁用 Build Options 下的 User Script Sandboxing 选项:

如果您有不同于默认
Debug或Release的自定义构建配置,请在 Build Settings 选项卡上,在 User-Defined 下添加KOTLIN_FRAMEWORK_BUILD_TYPE设置,并将其设置为Debug或Release。在 Info 选项卡上,添加一个自定义
CADisableMinimumFrameDurationOnPhone属性并将其设置为YES,以在 iOS 上启用高刷新率。在 Signing & Capabilities 选项卡上,选择您的开发团队,如果尚未创建,请创建一个。 这可以对 KMP 模块生成的
shared框架进行签名。在这里,您还应确保 Bundle Identifier 已设置为唯一值,否则 Xcode 可能会构建失败。
在 Xcode 中构建项目(主菜单中的 Product | Build)。 如果一切配置正确,项目应该能成功构建 (您可以安全地忽略 "build phase will be run during every build" 警告)
如果您在禁用 User Script Sandboxing 选项之前构建过项目,构建可能会失败: Gradle 守护进程可能已被沙箱化,需要重新启动。 在再次构建项目之前,通过在项目目录(在我们的示例中为
kmp-integration-sample)中运行此命令来停止它:shell./gradlew --stop
在 Android Studio 中设置 iOS 运行配置
一旦您确认 Xcode 设置正确,请返回 Android Studio:
在主菜单中选择 File | Sync Project with Gradle Files。Android Studio 会自动生成一个名为 simpleLoginIOS 的运行配置。
Android Studio 自动生成一个名为 simpleLoginIOS 的运行配置,并将
iosApp目录标记为链接的 Xcode 项目。在运行配置列表中,选择 simpleLoginIOS。 选择一个 iOS 模拟器,然后点击 Run 以检查 iOS 运行配置是否正常运行。

在 iOS 项目中使用共享模块
shared/build.gradle.kts 文件将每个 iOS 目标的 binaries.framework.baseName 属性定义为 sharedKit。 这是 Kotlin Multiplatform 为 iOS 应用程序构建供其使用的框架名称。
要测试集成,在 Swift 代码中添加一个对公共代码的调用:
在 Android Studio 中,打开
iosApp/simpleloginIOS/ContentView.swift文件并导入框架:swiftimport sharedKit为了检查它是否已正确连接,更改
ContentView结构的代码以使用来自shared模块的greet()函数:swiftstruct ContentView: View { var body: some View { Text(Greeting().greet()) .padding() } }使用 Android Studio iOS 运行配置运行应用程序以查看结果:

再次更新
ContentView.swift文件中的代码,以使用共享模块中的业务逻辑来渲染应用程序 UI:kotlin在
simpleLoginIOSApp.swift文件中,导入sharedKit模块并指定ContentView()函数的参数:swiftimport SwiftUI import sharedKit @main struct SimpleLoginIOSApp: App { var body: some Scene { WindowGroup { ContentView(viewModel: .init(loginRepository: LoginRepository(dataSource: LoginDataSource()), loginValidator: LoginDataValidator())) } } }再次运行 iOS 运行配置,可以看到 iOS 应用程序显示了登录表单。
输入 "Jane" 作为用户名,输入 "password" 作为密码。
由于您之前已经设置了集成, iOS 应用程序将使用公共代码验证输入:

享受成果 – 只需更新一次逻辑
现在您的应用程序已经是跨平台的了。您可以在 shared 模块中更新业务逻辑,并同时在 Android 和 iOS 上查看结果。
更改用户密码的验证逻辑:"password" 不应是一个有效的选项。 为此,更新
LoginDataValidator类的checkPassword()函数 (要快速找到它,双击 键两次,粘贴类名,然后切换到 Classes 选项卡):kotlinpackage com.jetbrains.simplelogin.shared.data class LoginDataValidator { //... fun checkPassword(password: String): Result { return when { password.length < 5 -> Result.Error("Password must be >5 characters") password.lowercase() == "password" -> Result.Error("Password shouldn't be \"password\"") else -> Result.Success } } //... }从 Android Studio 运行 iOS 和 Android 应用程序以查看更改 (点击红色警告三角形时会出现 iOS 错误消息):

您可以查看本教程的最终代码。
还可以共享什么?
您已经共享了应用程序的业务逻辑,但您也可以决定共享应用程序的其他层。 例如,ViewModel 类代码在 Android 和 iOS 应用程序中几乎相同, 如果您的移动应用程序应具有相同的表现层,您可以共享它。
下一步
将 Android 应用程序转换为跨平台后,您可以继续执行以下操作:
您可以使用 Compose Multiplatform 在所有平台上创建统一的 UI:
您也可以查看社区资源: