Kotlin Multiplatform Development Help

使用平台特定API

在本文中,你将学习如何在开发多平台应用程序和库时使用平台特定的API。

Kotlin多平台库

在编写使用平台特定API的代码之前,请检查是否可以使用多平台库替代。这类库提供了一个通用的Kotlin API,并为不同平台提供了不同的实现。

已有许多可用的库可用于实现网络、日志记录、分析以及访问设备功能等。更多信息,请参阅此精选列表

预期与实际函数和属性

Kotlin提供了一种语言机制,在开发通用逻辑时访问平台特定的API: 预期与实际声明

通过这种机制,多平台模块的通用源集定义一个预期声明,每个平台源集必须提供与预期声明对应的实际声明。编译器确保通用源集中标记为expect关键字的每个声明在所有目标平台源集中都有对应的标记为actual关键字的声明。

这适用于大多数Kotlin声明,如函数、类、接口、枚举、属性和注解。本节重点介绍使用预期与实际函数和属性。

使用预期与实际函数和属性

在此示例中,你将在通用源集中定义一个预期的platform()函数,并在平台源集中提供实际实现。在为特定平台生成代码时,Kotlin编译器会合并预期与实际声明。它生成一个带有实际实现的platform()函数。预期与实际声明应在同一包中定义,并在生成的平台代码中合并为_一个声明_。生成的平台代码中对预期platform()函数的任何调用都将调用正确的实际实现。

示例:生成UUID

假设你正在使用Kotlin Multiplatform开发iOS和Android应用程序,并希望生成一个通用唯一标识符(UUID)。

为此,在Kotlin Multiplatform模块的通用源集中使用expect关键字声明预期函数randomUUID()不要包含任何实现代码。

// 在通用源集中: expect fun randomUUID(): String

在每个平台特定的源集(iOS和Android)中,为通用模块中预期的randomUUID()函数提供实际实现。使用actual关键字标记这些实际实现。

使用预期与实际声明生成UUID

以下代码片段展示了Android和iOS的实现。平台特定代码使用actual关键字和相同的函数名称:

// 在android源集中: import java.util.* actual fun randomUUID() = UUID.randomUUID().toString()
// 在iOS源集中: import platform.Foundation.NSUUID actual fun randomUUID(): String = NSUUID().UUIDString()

Android实现使用Android上可用的API,而iOS实现使用iOS上可用的API。你可以从Kotlin/Native代码访问iOS API。

在为Android生成平台代码时,Kotlin编译器会自动合并预期与实际声明,并生成一个带有实际Android特定实现的randomUUID()函数。iOS也会重复相同的过程。

为简单起见,本示例及以下示例使用简化的源集名称“common”、“ios”和“android”。通常,这指的是commonMainiosMainandroidMain ,类似的逻辑可以在测试源集commonTestiosTestandroidTest中定义。

与预期与实际函数类似,预期与实际属性允许你在不同平台上使用不同的值。预期与实际函数和属性在简单场景中最有用。

通用代码中的接口

如果平台特定逻辑过于庞大和复杂,你可以通过在通用代码中定义一个接口来表示它,然后在平台源集中提供不同的实现来简化代码。

使用接口

平台源集中的实现使用其对应的依赖项:

// 在commonMain源集中: interface Platform { val name: String }
// 在androidMain源集中: import android.os.Build class AndroidPlatform : Platform { override val name: String = "Android ${Build.VERSION.SDK_INT}" }
// 在iosMain源集中: import platform.UIKit.UIDevice class IOSPlatform : Platform { override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion }

当你需要一个通用接口时,可以通过以下选项之一注入适当的平台实现,每个选项将在下面详细解释:

  • 使用预期与实际函数

  • 通过不同入口点提供实现

  • 使用依赖注入框架

预期与实际函数

定义一个返回该接口值的预期函数,然后定义返回其子类的实际函数:

// 在commonMain源集中: interface Platform expect fun platform(): Platform
// 在androidMain源集中: class AndroidPlatform : Platform actual fun platform() = AndroidPlatform()
// 在iosMain源集中: class IOSPlatform : Platform actual fun platform() = IOSPlatform()

当你在通用代码中调用platform()函数时,它可以处理Platform类型的对象。当你在Android上运行此通用代码时, platform()调用返回AndroidPlatform类的实例。在iOS上运行时, platform()返回IOSPlatform类的实例。

不同入口点

如果你控制入口点,可以在不使用预期与实际声明的情况下构建每个平台工件的实现。为此,在共享的Kotlin Multiplatform模块中定义平台实现,但在平台模块中实例化它们:

// 共享Kotlin Multiplatform模块 // 在commonMain源集中: interface Platform fun application(p: Platform) { // 应用程序逻辑 }
// 在androidMain源集中: class AndroidPlatform : Platform
// 在iosMain源集中: class IOSPlatform : Platform
// 在androidApp平台模块中: import android.app.Application import mysharedpackage.* class MyApp : Application() { override fun onCreate() { super.onCreate() application(AndroidPlatform()) } }
// 在iosApp平台模块中(Swift): import shared @main struct iOSApp : App { init() { application(IOSPlatform()) } }

在Android上,你应该创建AndroidPlatform的实例并将其传递给application()函数,而在iOS上,你应该类似地创建并传递IOSPlatform的实例。这些入口点不必是你的应用程序的入口点,但这是你可以调用共享模块特定功能的地方。

通过预期与实际函数或直接通过入口点提供正确的实现,在简单场景中效果很好。然而,如果你的项目中使用了依赖注入框架,我们建议在简单情况下使用它以确保一致性。

依赖注入框架

现代应用程序通常使用依赖注入(DI)框架来创建松耦合的架构。DI框架允许根据当前环境将依赖项注入组件。

任何支持Kotlin Multiplatform的DI框架都可以帮助你为不同平台注入不同的依赖项。

例如, Koin是一个支持Kotlin Multiplatform的依赖注入框架:

// 在通用源集中: import org.koin.dsl.module interface Platform expect val platformModule: Module
// 在androidMain源集中: class AndroidPlatform : Platform actual val platformModule: Module = module { single<Platform> { AndroidPlatform() } }
// 在iosMain源集中: class IOSPlatform : Platform actual val platformModule = module { single<Platform> { IOSPlatform() } }

在这里,Koin DSL创建了定义注入组件的模块。你使用expect关键字在通用代码中声明一个模块,然后使用actual关键字为每个平台提供平台特定的实现。框架负责在运行时选择正确的实现。

当你使用DI框架时,所有依赖项都通过该框架注入。同样的逻辑适用于处理平台依赖项。如果你已经在项目中使用了DI,我们建议继续使用它,而不是手动使用预期与实际函数。这样可以避免混合两种不同的依赖注入方式。

你也不必总是在Kotlin中实现通用接口。你可以在其他语言中实现它,例如Swift,在另一个_平台模块_中。如果选择这种方法,你应该使用DI框架从iOS平台模块提供实现:

使用依赖注入框架

这种方法仅在你将实现放在平台模块中时才有效。它的可扩展性不高,因为你的Kotlin Multiplatform模块不能自给自足,你需要在不同的模块中实现通用接口。

下一步?

有关预期/实际机制的更多示例和信息,请参阅预期与实际声明

21 四月 2025