MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Kotlin中的多平台项目开发实战

2023-01-224.1k 阅读

Kotlin 多平台项目基础

Kotlin 多平台(KMP)允许开发者使用 Kotlin 编写可以在多个平台上运行的代码,包括 JVM(Java 虚拟机)、iOS、Android、JavaScript 以及原生的桌面平台。这种跨平台的能力极大地提高了代码的复用性,减少了不同平台之间重复代码的编写。

1. 项目结构

在 Kotlin 多平台项目中,典型的项目结构包含共享代码模块和针对各平台的特定模块。共享代码模块存放可以跨平台复用的代码,而平台特定模块则处理与平台相关的逻辑。 例如,创建一个简单的 KMP 项目结构如下:

my-kmp-project
├── commonMain
│   └── kotlin
│       └── com
│           └── example
│               └── shared
│                   ├── Shared.kt
├── androidMain
│   └── kotlin
│       └── com
│           └── example
│               └── android
│                   ├── AndroidPlatform.kt
├── iosMain
│   └── kotlin
│       └── com
│           └── example
│               └── ios
│                   ├── IOSPlatform.kt

commonMain 目录下的代码是共享代码,androidMainiosMain 目录下的代码分别是 Android 和 iOS 平台特定的代码。

2. 共享代码编写

在共享代码模块中,可以定义通用的数据模型、业务逻辑和工具函数。 例如,定义一个简单的数据类和一个计算函数:

// commonMain/kotlin/com/example/shared/Shared.kt
package com.example.shared

data class User(val name: String, val age: Int)

fun calculateSum(a: Int, b: Int): Int {
    return a + b
}

这里的 User 数据类和 calculateSum 函数可以在不同平台上复用。

配置 Kotlin 多平台项目

1. Gradle 配置

Kotlin 多平台项目通常使用 Gradle 进行构建。在 build.gradle.kts 文件中,需要配置项目的相关依赖和构建参数。

plugins {
    kotlin("multiplatform") version "1.7.20"
}

kotlin {
    android()
    iosX64()
    iosArm64()
    iosSimulatorArm64()

    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("org.jetbrains.kotlin:kotlin-stdlib-common")
            }
        }
        val androidMain by getting {
            dependencies {
                implementation("org.jetbrains.kotlin:kotlin-stdlib")
                implementation("androidx.core:core-ktx:1.9.0")
            }
        }
        val iosMain by getting {
            dependencies {
                implementation("org.jetbrains.kotlin:kotlin-stdlib-native")
            }
        }
    }
}

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
    kotlinOptions.jvmTarget = "1.8"
}

上述配置中,使用 kotlin("multiplatform") 插件,并且配置了 Android 和 iOS 相关的目标平台。sourceSets 中分别配置了不同平台的依赖。

2. 依赖管理

在多平台项目中,有些依赖可能是共享的,有些则是平台特定的。共享依赖放在 commonMaindependencies 中,平台特定依赖放在各自平台的 sourceSets 中。 例如,对于日志库,在 commonMain 中可以使用 kotlinx-logging 的通用部分,而在 Android 平台可以使用 android.util.Log 进行更具体的日志输出。

// commonMain/kotlin/com/example/shared/Logger.kt
package com.example.shared

import kotlinx.logging.Logger
import kotlinx.logging.Logging

object SharedLogger : Logging {
    override val logger: Logger
        get() = Logger("SharedLogger")
}

// androidMain/kotlin/com/example/android/AndroidLogger.kt
package com.example.android

import android.util.Log
import com.example.shared.SharedLogger

object AndroidLogger {
    fun log(message: String) {
        SharedLogger.logger.info { message }
        Log.d("AndroidLogger", message)
    }
}

Android 平台开发

1. 集成共享代码到 Android 项目

在 Android 项目中,可以直接使用共享代码模块中的类和函数。首先,在 Android 项目的 settings.gradle.kts 中包含共享代码模块:

include(":shared")
project(":shared").projectDir = File("../my-kmp-project")

然后在 Android 模块的 build.gradle.kts 中添加对共享代码模块的依赖:

dependencies {
    implementation(project(":shared"))
}

这样就可以在 Android 代码中使用共享代码了。

2. Android 平台特定逻辑

在 Android 平台上,除了使用共享代码,还需要处理一些与 Android 相关的逻辑,比如 UI 展示、权限管理等。 例如,创建一个简单的 Android Activity 来展示共享代码中计算的结果:

package com.example.androidapp

import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.example.shared.calculateSum

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val resultTextView = findViewById<TextView>(R.id.resultTextView)
        val sum = calculateSum(5, 3)
        resultTextView.text = "The sum is: $sum"
    }
}

同时,在 Android 平台上还可以根据需求扩展共享代码的功能。比如,在共享代码中有一个 User 数据类,在 Android 平台上可以添加一些与本地存储相关的功能:

// androidMain/kotlin/com/example/android/UserExtensions.kt
package com.example.android

import android.content.Context
import android.content.SharedPreferences
import com.example.shared.User

fun User.saveToSharedPreferences(context: Context) {
    val sharedPreferences: SharedPreferences = context.getSharedPreferences("user_preferences", Context.MODE_PRIVATE)
    val editor = sharedPreferences.edit()
    editor.putString("name", name)
    editor.putInt("age", age)
    editor.apply()
}

fun getSavedUser(context: Context): User? {
    val sharedPreferences: SharedPreferences = context.getSharedPreferences("user_preferences", Context.MODE_PRIVATE)
    val name = sharedPreferences.getString("name", null)
    val age = sharedPreferences.getInt("age", -1)
    if (name != null && age != -1) {
        return User(name, age)
    }
    return null
}

iOS 平台开发

1. 集成共享代码到 iOS 项目

在 iOS 项目中集成共享代码,首先需要将 Kotlin 生成的框架添加到 Xcode 项目中。在 Kotlin 多平台项目构建完成后,会在 build/bin/iosX64/debugFramework 等目录下生成相应的框架。 将生成的框架拖入到 Xcode 项目中,并确保在 Build Phases -> Link Binary With Libraries 中添加了该框架。 然后,在 Swift 代码中可以使用 Kotlin 共享代码。例如,创建一个 Swift 函数来调用共享代码中的 calculateSum 函数:

import shared

func calculateSumInSwift() -> Int {
    return SharedKt.calculateSum(a: 5, b: 3)
}

这里 SharedKt 是 Kotlin 共享代码生成的 Swift 接口类。

2. iOS 平台特定逻辑

在 iOS 平台上,同样需要处理与 iOS 相关的逻辑,如界面布局、用户交互等。例如,创建一个简单的 iOS ViewController 来展示共享代码的计算结果:

import UIKit
import shared

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let sum = SharedKt.calculateSum(a: 5, b: 3)
        let label = UILabel(frame: CGRect(x: 100, y: 100, width: 200, height: 50))
        label.text = "The sum is: \(sum)"
        view.addSubview(label)
    }
}

在 iOS 平台上也可以扩展共享代码的功能。比如,对于共享代码中的 User 数据类,可以添加与 iOS 本地存储相关的功能:

import Foundation
import shared

extension User {
    func saveToUserDefaults() {
        let defaults = UserDefaults.standard
        defaults.set(name, forKey: "name")
        defaults.set(age, forKey: "age")
    }

    static func getSavedUser() -> User? {
        let defaults = UserDefaults.standard
        guard let name = defaults.string(forKey: "name"),
              let age = defaults.object(forKey: "age") as? Int else {
            return nil
        }
        return User(name: name, age: age)
    }
}

Kotlin 多平台项目中的网络请求

1. 共享网络请求逻辑

在 Kotlin 多平台项目中,可以使用 Ktor 框架来实现共享的网络请求逻辑。首先,在 commonMain 中添加 Ktor 依赖:

val commonMain by getting {
    dependencies {
        implementation("io.ktor:ktor-client-core:2.2.3")
        implementation("io.ktor:ktor-client-content-negotiation:2.2.3")
        implementation("io.ktor:ktor-serialization-kotlinx-json:2.2.3")
        implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
    }
}

然后,创建一个共享的网络请求类:

package com.example.shared

import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json

class NetworkService {
    private val client = HttpClient {
        install(ContentNegotiation) {
            json(Json {
                prettyPrint = true
                isLenient = true
            })
        }
    }

    suspend fun <T> get(url: String, responseType: Class<T>): T {
        return client.get(url).body()
    }
}

这里使用 KtorHttpClient 进行网络请求,并通过 ContentNegotiation 插件进行 JSON 数据的序列化和反序列化。

2. 平台特定的网络配置

在 Android 平台上,Ktor 可以使用 OkHttp 作为底层引擎,以利用 Android 系统的网络优化。在 androidMain 中添加 OkHttp 依赖:

val androidMain by getting {
    dependencies {
        implementation("io.ktor:ktor-client-okhttp:2.2.3")
    }
}

然后,在 Android 平台上配置 HttpClient 使用 OkHttp:

package com.example.android

import com.example.shared.NetworkService
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*

class AndroidNetworkService : NetworkService() {
    override val client: HttpClient = HttpClient(OkHttp) {
        install(ContentNegotiation) {
            json()
        }
    }
}

在 iOS 平台上,Ktor 可以使用 NSURLSession 作为底层引擎。在 iosMain 中添加相应依赖:

val iosMain by getting {
    dependencies {
        implementation("io.ktor:ktor-client-ios:2.2.3")
    }
}

并在 iOS 平台上配置 HttpClient 使用 NSURLSession

package com.example.ios

import com.example.shared.NetworkService
import io.ktor.client.*
import io.ktor.client.engine.ios.*

class IOSNetworkService : NetworkService() {
    override val client: HttpClient = HttpClient(Ios) {
        install(ContentNegotiation) {
            json()
        }
    }
}

这样,在不同平台上可以根据平台特性优化网络请求,同时共享大部分的网络请求逻辑。

Kotlin 多平台项目中的数据持久化

1. 共享数据模型与持久化接口

在 Kotlin 多平台项目中,首先在共享代码中定义数据模型和持久化接口。例如,对于之前的 User 数据类,定义一个持久化接口:

package com.example.shared

interface UserStorage {
    fun saveUser(user: User)
    fun getUser(): User?
}

这样,不同平台可以根据自身的存储方式实现这个接口。

2. Android 平台的数据持久化实现

在 Android 平台上,可以使用 SharedPreferences 来实现 UserStorage 接口:

package com.example.android

import android.content.Context
import android.content.SharedPreferences
import com.example.shared.User
import com.example.shared.UserStorage

class AndroidUserStorage(private val context: Context) : UserStorage {
    private val sharedPreferences: SharedPreferences = context.getSharedPreferences("user_preferences", Context.MODE_PRIVATE)

    override fun saveUser(user: User) {
        val editor = sharedPreferences.edit()
        editor.putString("name", user.name)
        editor.putInt("age", user.age)
        editor.apply()
    }

    override fun getUser(): User? {
        val name = sharedPreferences.getString("name", null)
        val age = sharedPreferences.getInt("age", -1)
        if (name != null && age != -1) {
            return User(name, age)
        }
        return null
    }
}

3. iOS 平台的数据持久化实现

在 iOS 平台上,可以使用 UserDefaults 来实现 UserStorage 接口:

import Foundation
import shared

class IOSUserStorage: UserStorage {
    func saveUser(_ user: User) {
        let defaults = UserDefaults.standard
        defaults.set(user.name, forKey: "name")
        defaults.set(user.age, forKey: "age")
    }

    func getUser() -> User? {
        let defaults = UserDefaults.standard
        guard let name = defaults.string(forKey: "name"),
              let age = defaults.object(forKey: "age") as? Int else {
            return nil
        }
        return User(name: name, age: age)
    }
}

通过这种方式,在不同平台上实现了针对相同数据模型的不同持久化方式,同时共享了数据模型和持久化接口的定义。

Kotlin 多平台项目的测试

1. 共享测试代码

在 Kotlin 多平台项目中,可以编写共享的测试代码来测试共享代码模块中的功能。使用 kotlin.test 库来编写测试。首先,在 commonTest 中添加依赖:

val commonTest by getting {
    dependencies {
        implementation(kotlin("test-common"))
        implementation(kotlin("test-annotations-common"))
    }
}

然后,编写一个简单的测试类来测试 calculateSum 函数:

package com.example.shared

import kotlin.test.Test
import kotlin.test.assertEquals

class SharedCodeTest {
    @Test
    fun testCalculateSum() {
        val result = calculateSum(2, 3)
        assertEquals(5, result)
    }
}

2. 平台特定测试

除了共享测试代码,还可以编写平台特定的测试。例如,在 Android 平台上,可以使用 JUnitEspresso 进行 UI 测试和 Android 特定功能的测试。 在 androidTest 中添加依赖:

val androidTest by getting {
    dependencies {
        implementation("junit:junit:4.13.2")
        implementation("androidx.test.ext:junit:1.1.5")
        implementation("androidx.test.espresso:espresso-core:3.5.1")
    }
}

然后,编写一个简单的 Android 特定测试:

package com.example.androidapp

import android.view.View
import android.widget.TextView
import androidx.test.espresso.Espresso
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.rule.ActivityTestRule
import com.example.androidapp.MainActivity
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class AndroidSpecificTest {
    @get:Rule
    val activityRule = ActivityTestRule(MainActivity::class.java)

    @Test
    fun testTextViewIsDisplayed() {
        Espresso.onView(ViewMatchers.withId(R.id.resultTextView))
           .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
    }
}

在 iOS 平台上,可以使用 XCTest 进行 iOS 特定功能的测试。在 Xcode 项目中创建一个新的 XCTest 类:

import XCTest
import shared

class IOSTests: XCTestCase {
    func testCalculateSumInSwift() {
        let result = calculateSumInSwift()
        XCTAssertEqual(result, 8)
    }
}

通过这种方式,对共享代码和平台特定代码都进行了有效的测试。

Kotlin 多平台项目的优化与注意事项

1. 性能优化

在 Kotlin 多平台项目中,性能优化非常重要。对于共享代码,尽量避免使用平台特定的高级特性,以免影响跨平台的性能。例如,在共享代码中避免使用过于复杂的 Android 或 iOS 特定的集合操作,而是使用 Kotlin 标准库中的通用集合操作。 在网络请求方面,合理设置缓存策略,特别是在多平台共享的网络请求逻辑中。在 Android 平台上,可以利用 OkHttp 的缓存功能,在 iOS 平台上可以使用 NSURLCache。 对于数据持久化,尽量减少频繁的读写操作。例如,在 Android 中,如果使用 SharedPreferences,可以批量提交编辑操作,而不是每次修改都立即提交。

2. 兼容性与版本管理

在 Kotlin 多平台项目中,要注意不同平台的兼容性。例如,在使用一些第三方库时,要确保这些库在各个目标平台上都能正常工作。同时,要密切关注 Kotlin 版本以及相关插件和依赖库的版本更新。 在更新 Kotlin 版本时,要仔细阅读官方文档中的迁移指南,确保项目能够顺利升级。对于第三方库,要定期检查是否有新的版本发布,并且在更新时进行充分的测试,以避免引入兼容性问题。

3. 代码结构与维护

保持良好的代码结构对于 Kotlin 多平台项目的维护至关重要。在共享代码模块中,要将不同功能的代码进行合理的模块化,比如将数据模型、业务逻辑、网络请求等分别放在不同的包中。 对于平台特定代码,要清晰地划分与共享代码的边界。同时,要使用良好的命名规范,使得代码易于理解和维护。在项目开发过程中,要定期进行代码审查,及时发现并解决潜在的问题。

通过以上对 Kotlin 多平台项目开发的各个方面的详细介绍,包括项目基础、配置、各平台开发、网络请求、数据持久化、测试以及优化与注意事项等,希望开发者能够顺利地进行 Kotlin 多平台项目的开发,充分发挥 Kotlin 跨平台的优势,提高开发效率和代码复用性。