Kotlin 多平台中的 expect/actual 机制详解
1. 基本概念
1.1 什么是 expect/actual 机制
expect/actual 是 Kotlin 多平台项目中用于实现平台特定代码的核心机制。它允许我们在共享代码中声明 API 契约(使用 expect
关键字),然后在各个平台特定代码中提供实现(使用 actual
关键字)。
1.2 基本语法
kotlin
// 在共享代码中声明
expect fun platformName(): String
// 在各平台代码中实现
actual fun platformName(): String = "Android" // Android 实现
actual fun platformName(): String = "iOS" // iOS 实现
actual fun platformName(): String = "Web" // Web 实现
2. 多种声明类型
2.1 函数声明
kotlin
// 共享代码
expect fun readFile(path: String): String
// 平台实现
actual fun readFile(path: String): String {
// 平台特定的文件读取实现
}
2.2 属性声明
kotlin
// 共享代码
expect val platformVersion: String
// 平台实现
actual val platformVersion: String = "Android 12"
2.3 类声明
kotlin
// 共享代码
expect class PlatformFile(path: String) {
fun read(): ByteArray
fun write(data: ByteArray)
}
// 平台实现
actual class PlatformFile actual constructor(private val path: String) {
actual fun read(): ByteArray {
// 平台特定实现
}
actual fun write(data: ByteArray) {
// 平台特定实现
}
}
2.4 对象声明
kotlin
// 共享代码
expect object PlatformInfo {
val name: String
fun getVersion(): String
}
// 平台实现
actual object PlatformInfo {
actual val name: String = "Android"
actual fun getVersion(): String = "12.0"
}
2.5 继承模式
kotlin
// 共享代码中的基类
open class BaseLogger {
open fun log(message: String) {
// 共享的日志逻辑
}
}
// 共享代码中的 expect 声明
expect class PlatformLogger : BaseLogger
// 平台实现
actual class PlatformLogger : BaseLogger() {
override fun log(message: String) {
super.log(message)
// 平台特定的额外日志逻辑
}
}
3. 实际项目中的应用案例(基于 Egloo 项目)
3.1 平台特定类型和常量
kotlin
// 在 commonMain 中声明平台特定的类型
internal expect class EglSurface
internal expect class EglContext
internal expect class EglDisplay
internal expect class EglConfig
// 在 commonMain 中声明平台特定的常量
internal expect val EGL_NO_CONTEXT: EglContext
internal expect val EGL_NO_DISPLAY: EglDisplay
internal expect val EGL_SUCCESS: Int
3.2 平台特定 API 函数
kotlin
// 在 commonMain 中声明平台特定的函数
internal expect inline fun eglChooseConfig(
display: EglDisplay,
attributes: IntArray,
configs: Array<EglConfig?>,
configsSize: Int,
numConfigs: IntArray
): Boolean
internal expect inline fun eglInitialize(
display: EglDisplay,
major: IntArray,
minor: IntArray
): Boolean
3.3 工厂模式实现
kotlin
// 在 commonMain 中定义工厂接口
interface GlProgramFactory {
fun createProgram(): GlProgram
}
// 使用 expect/actual 声明工厂提供者
internal expect object GlProgramFactorySingleton {
fun getInstance(): GlProgramFactory
}
// 在平台特定代码中实现
internal actual object GlProgramFactorySingleton {
fun getInstance(): GlProgramFactory {
return GlProgramFactoryImpl()
}
}
3.4 继承与共享实现
kotlin
// EglNativeCore.kt - 共享基类
public open class EglNativeCore internal constructor(
sharedContext: EglContext = EGL_NO_CONTEXT,
flags: Int = 0
) {
// 共享的实现代码
internal open fun release() {
// 共享的清理逻辑
}
}
// 在 commonMain 中声明继承自基类的 expect 类
public expect class EglCore : EglNativeCore
// 在平台特定代码中实现
public actual class EglCore : EglNativeCore {
// 平台特定实现
}
4. 常见陷阱和注意事项
4.1 修饰符使用错误
4.1.1 对象方法的修饰符
kotlin
// 在 commonMain 中
expect object MySingleton {
fun getInstance(): MyClass
}
// ❌ 错误方式 1:不要在方法上使用 actual
actual object MySingleton {
actual fun getInstance(): MyClass // 错误:不需要 actual
}
// ❌ 错误方式 2:不要添加 override
actual object MySingleton {
override fun getInstance(): MyClass // 错误:不需要 override
}
// ✅ 正确方式:不使用任何额外修饰符
actual object MySingleton {
fun getInstance(): MyClass {
return MyClassImpl()
}
}
4.1.2 类成员的修饰符
kotlin
// 在 commonMain 中
expect class PlatformFile(path: String) {
fun read(): ByteArray
}
// 在平台实现中
actual class PlatformFile actual constructor(private val path: String) {
// 这里需要 actual 修饰符,因为是类成员函数
actual fun read(): ByteArray {
// 实现
}
}
4.2 可见性不匹配
kotlin
// 在 commonMain 中
internal expect object Factory
// ✅ 正确:可见性匹配
internal actual object Factory
// ✅ 正确:可见性更开放
public actual object Factory
// ❌ 错误:可见性更严格
private actual object Factory
4.3 缺少平台实现
如果某个平台没有提供 actual 实现,编译器会报错:
arduino
Expected declaration has no actual declaration in module [platform-module] for [target-platform]
4.4 类型签名不匹配
kotlin
// 在 commonMain 中
expect fun process(data: String): Int
// ❌ 错误:返回类型不匹配
actual fun process(data: String): Long = 42L
// ❌ 错误:参数类型不匹配
actual fun process(data: Int): Int = data
5. 最佳实践
5.1 模块化设计
kotlin
// 分离平台特定的常量
internal expect val EGL_SUCCESS: Int
internal expect val EGL_NONE: Int
// 分离平台特定的类型
internal expect class EglSurface
internal expect class EglContext
// 分离平台特定的操作
internal expect inline fun eglInitialize(...): Boolean
5.2 共享代码最大化
kotlin
// 基类包含共享实现
open class BaseProcessor {
// 共享的处理逻辑
fun process(data: String): String {
val preprocessed = preprocess(data)
return platformSpecificProcess(preprocessed)
}
private fun preprocess(data: String): String {
// 共享的预处理逻辑
return data.trim()
}
// 平台特定部分声明为 expect
protected expect fun platformSpecificProcess(data: String): String
}
// 平台实现只需关注特定部分
actual class AndroidProcessor : BaseProcessor() {
actual override fun platformSpecificProcess(data: String): String {
// Android 特定实现
}
}
5.3 可见性控制
kotlin
// 公共 API 保持最小化
public expect class PlatformLogger
// 内部实现使用 internal
internal expect fun logToNativePlatform(message: String)
5.4 使用检查清单
- expect 声明放在 commonMain 源集中
- 所有目标平台都提供了 actual 实现
- 没有在对象方法上使用多余的 actual/override 修饰符
- 可见性修饰符正确匹配
- 类型签名在所有平台上保持一致
6. 性能考虑
6.1 内联函数
kotlin
// 使用 inline 提高性能,避免函数调用开销
internal expect inline fun computeHash(data: ByteArray): Int
6.2 编译时解析
expect/actual 机制是在编译时解析的,没有运行时开销。编译器会为每个平台生成包含正确实现的代码。
6.3 单例模式优化
kotlin
// 在共享代码中声明单例
expect object PlatformSingleton {
fun getInstance(): Platform
}
// 在平台实现中高效实现
actual object PlatformSingleton {
// 平台特定的高效单例实现
private val instance by lazy { PlatformImpl() }
fun getInstance(): Platform = instance
}
7. 测试策略
7.1 共享测试
kotlin
// commonTest
class CommonTests {
@Test
fun testFactoryInterface() {
val factory = GlProgramFactorySingleton.getInstance()
assertNotNull(factory)
// 测试共享接口行为
}
}
7.2 平台特定测试
kotlin
// androidTest
class AndroidTests {
@Test
fun testFactoryImplementation() {
val factory = GlProgramFactorySingleton.getInstance()
assertTrue(factory is GlProgramFactoryImpl)
// 测试平台特定实现细节
}
}
7.3 模拟平台依赖
kotlin
// 在测试中替换平台实现
class MockPlatformFile : PlatformFile {
override fun read(): ByteArray = "test data".toByteArray()
override fun write(data: ByteArray) { /* 不执行实际写入 */ }
}
8. 项目结构设计
8.1 典型的源集结构
bash
src/
├── commonMain/ # 共享代码
├── androidMain/ # Android 平台通用代码
├── androidJvmMain/ # Android JVM 特定代码
├── androidNativeMain/ # Android Native 通用代码
├── iosMain/ # iOS 平台代码
├── jsMain/ # JavaScript 平台代码
└── jvmMain/ # JVM 平台代码
8.2 依赖管理
kotlin
// build.gradle.kts
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
// 共享依赖
}
}
val androidMain by getting {
dependencies {
// Android 特定依赖
}
}
val iosMain by getting {
dependencies {
// iOS 特定依赖
}
}
}
}
总结
Kotlin 多平台中的 expect/actual 机制是实现跨平台代码共享的核心技术。通过在共享代码中定义接口契约,并在各平台提供具体实现,我们可以最大化代码复用,同时保持平台特定功能的灵活性。
关键要点:
- 使用 expect 在共享代码中声明 API 契约
- 使用 actual 在平台特定代码中提供实现
- 避免在 actual 对象的方法上使用不必要的修饰符
- 保持一致的可见性和类型签名
- 最大化共享代码,只将平台特定部分声明为 expect
- 遵循单一职责原则,每个 expect 声明都应该有明确的用途
通过正确使用 expect/actual 机制,我们可以构建真正跨平台的 Kotlin 应用,在不同平台上提供一致的功能和最佳的性能。