Kotlin中的多平台项目开发实战
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
目录下的代码是共享代码,androidMain
和 iosMain
目录下的代码分别是 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. 依赖管理
在多平台项目中,有些依赖可能是共享的,有些则是平台特定的。共享依赖放在 commonMain
的 dependencies
中,平台特定依赖放在各自平台的 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()
}
}
这里使用 Ktor
的 HttpClient
进行网络请求,并通过 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 平台上,可以使用 JUnit
和 Espresso
进行 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 跨平台的优势,提高开发效率和代码复用性。