Egloo 中Kotlin 多平台中的 expect/actual

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 机制是实现跨平台代码共享的核心技术。通过在共享代码中定义接口契约,并在各平台提供具体实现,我们可以最大化代码复用,同时保持平台特定功能的灵活性。

关键要点:

  1. 使用 expect 在共享代码中声明 API 契约
  2. 使用 actual 在平台特定代码中提供实现
  3. 避免在 actual 对象的方法上使用不必要的修饰符
  4. 保持一致的可见性和类型签名
  5. 最大化共享代码,只将平台特定部分声明为 expect
  6. 遵循单一职责原则,每个 expect 声明都应该有明确的用途

通过正确使用 expect/actual 机制,我们可以构建真正跨平台的 Kotlin 应用,在不同平台上提供一致的功能和最佳的性能。

相关推荐
CYRUS_STUDIO1 小时前
逆向 JNI 函数找不到入口?动态注册定位技巧全解析
android·逆向·源码阅读
用户2018792831675 小时前
《Android 城堡防御战:ProGuard 骑士的代码混淆魔法》
android
用户2018792831675 小时前
🔐 加密特工行动:Android 中的 AES 与 RSA 秘密行动指南
android
liang_jy6 小时前
Android AIDL 原理
android·面试·源码
用户2018792831676 小时前
Android开发的"魔杖"之ADB命令
android
_荒7 小时前
uniapp AI流式问答对话,问答内容支持图片和视频,支持app和H5
android·前端·vue.js
冰糖葫芦三剑客7 小时前
Android录屏截屏事件监听
android
东风西巷7 小时前
LSPatch:免Root Xposed框架,解锁无限可能
android·生活·软件需求
用户2018792831678 小时前
图书馆书架管理员的魔法:TreeMap 的奇幻之旅
android