KMM & Compose Multiplatform 跨平臺(tái)開(kāi)發(fā)入門(mén)指南:構(gòu)建高效的移動(dòng)應(yīng)用

原創(chuàng) 轉(zhuǎn)載請(qǐng)聯(lián)系作者

1. 介紹

引言

在移動(dòng)應(yīng)用開(kāi)發(fā)領(lǐng)域,Kotlin Multiplatform Mobile (KMM) 和 Compose Multiplatform 的結(jié)合正在成為一種強(qiáng)大的解決方案。它們不僅解決了傳統(tǒng)跨平臺(tái)開(kāi)發(fā)中的諸多痛點(diǎn),還提供了許多獨(dú)特的優(yōu)勢(shì),使得開(kāi)發(fā)者能夠更加高效地構(gòu)建和維護(hù)跨平臺(tái)應(yīng)用。
以下是它的主要優(yōu)勢(shì):

  • 代碼復(fù)用
    KMM 和 Compose Multiplatform 允許開(kāi)發(fā)者在 Android 和 iOS 之間共享大部分代碼,包括業(yè)務(wù)邏輯和 UI 組件。
  • 性能優(yōu)化
    與某些其他跨平臺(tái)框架不同,KMM 生成的代碼是直接運(yùn)行在目標(biāo)平臺(tái)的原生環(huán)境中的。這意味著開(kāi)發(fā)者可以享受到與原生開(kāi)發(fā)相同的性能和系統(tǒng)級(jí)優(yōu)化。
  • 平臺(tái)特性和靈活性
    KMM 通過(guò) expect 和 actual 關(guān)鍵字,允許開(kāi)發(fā)者為不同的平臺(tái)編寫(xiě)特定的實(shí)現(xiàn)。這種靈活性使得開(kāi)發(fā)者可以充分利用各個(gè)平臺(tái)的特性,而不會(huì)妥協(xié)于跨平臺(tái)的限制。Compose Multiplatform 同樣支持這種靈活性,確保 UI 設(shè)計(jì)可以根據(jù)平臺(tái)的需求進(jìn)行優(yōu)化。
  • 一致的開(kāi)發(fā)體驗(yàn)
    Kotlin 作為一種現(xiàn)代語(yǔ)言,擁有簡(jiǎn)潔的語(yǔ)法和強(qiáng)大的功能特性,深受開(kāi)發(fā)者喜愛(ài)。利用 Kotlin 和 Compose Multiplatform,開(kāi)發(fā)者能夠在相同的開(kāi)發(fā)環(huán)境中進(jìn)行多平臺(tái)開(kāi)發(fā),提升了開(kāi)發(fā)者的體驗(yàn)和生產(chǎn)力。使用 Android Studio 和 Xcode 無(wú)縫集成,開(kāi)發(fā)者可以在熟悉的 IDE 中進(jìn)行跨平臺(tái)開(kāi)發(fā)和調(diào)試。
  • 現(xiàn)代化的 UI 構(gòu)建
    Compose Multiplatform 基于聲明式編程范式,簡(jiǎn)化了復(fù)雜的 UI 構(gòu)建。開(kāi)發(fā)者可以通過(guò)簡(jiǎn)明的代碼定義動(dòng)態(tài)界面,減少了樣板代碼和 UI 更新的復(fù)雜度。這種現(xiàn)代化的 UI 構(gòu)建方式,不僅使代碼更加清晰和易于維護(hù),還提升了開(kāi)發(fā)效率。

使用 KMM 和 Compose Multiplatform,開(kāi)發(fā)團(tuán)隊(duì)可以在保證高性能和平臺(tái)特性的同時(shí),實(shí)現(xiàn)代碼的最大復(fù)用和開(kāi)發(fā)效率的提升。這種結(jié)合不僅為項(xiàng)目帶來(lái)了顯著的優(yōu)勢(shì),還為開(kāi)發(fā)者提供了創(chuàng)新和靈活的開(kāi)發(fā)體驗(yàn)。接下來(lái),讓我們進(jìn)入 KMM + Compose Multiplatform 的學(xué)習(xí)與使用。

基礎(chǔ)概念

什么是 Kotlin Multiplatform (KMP)?
Kotlin Multiplatform (KMP) 是 JetBrains 提供的一項(xiàng)功能,允許使用 Kotlin 編寫(xiě)可以在多個(gè)平臺(tái)上運(yùn)行的代碼。KMP 的目標(biāo)是通過(guò) 共享業(yè)務(wù)邏輯代碼 來(lái)減少不同平臺(tái)開(kāi)發(fā)的重復(fù)工作。
KMP 支持的平臺(tái)包括但不限于:
JVM:用于服務(wù)器端開(kāi)發(fā)以及 Android 開(kāi)發(fā)。
JavaScript:用于前端 Web 開(kāi)發(fā)。
原生平臺(tái) (Native):如 iOS、Windows、macOS、Linux 等。

KMP@2x.png

什么是 Kotlin Multiplatform Mobile (KMM)?
Kotlin Multiplatform Mobile (KMM) 是專注于移動(dòng)平臺(tái)的一種 KMP 實(shí)現(xiàn)。KMM 讓開(kāi)發(fā)者能夠使用 Kotlin 編寫(xiě)可以在 Android 和 iOS 上運(yùn)行的共享代碼,主要關(guān)注點(diǎn)是在移動(dòng)設(shè)備上的應(yīng)用開(kāi)發(fā)。
簡(jiǎn)單來(lái)說(shuō),KMM 是 KMP 的一個(gè)子集或特化實(shí)現(xiàn),專注于移動(dòng)開(kāi)發(fā)。

什么是 Compose multiform?
Compose Multiplatform 是 JetBrains 開(kāi)發(fā)的一種跨平臺(tái)用戶界面 (UI) 框架,它基于 Kotlin 和 Jetpack Compose 的設(shè)計(jì)理念,旨在通過(guò)聲明式編程范式簡(jiǎn)化 跨平臺(tái) UI 構(gòu)建。

什么是 Kotlin/Native、Kotlin/JVM、Kotlin/JS?與 KMP 的關(guān)系?
Kotlin 是一種現(xiàn)代化的編程語(yǔ)言,由 JetBrains 開(kāi)發(fā),支持多平臺(tái)開(kāi)發(fā),包括 JVM、JavaScript 和原生平臺(tái)。為了更高效地開(kāi)發(fā)跨平臺(tái)應(yīng)用,Kotlin 提供了三種主要的編譯器后端:Kotlin/JVM、Kotlin/JS 和 Kotlin/Native。
Kotlin/JVM 是最初的 Kotlin 編譯器后端,也是最常用的一種。它將 Kotlin 代碼編譯成可以在 Java 虛擬機(jī) (JVM) 上運(yùn)行的字節(jié)碼。由于 Kotlin 與 Java 高度互操作,Kotlin/JVM 可以直接利用現(xiàn)有的 Java 庫(kù)和框架,使得開(kāi)發(fā)者能夠輕松地將 Kotlin 與 Java 項(xiàng)目集成。使用場(chǎng)景有 Android 開(kāi)發(fā)與服務(wù)器開(kāi)發(fā)。
Kotlin/JS 將 Kotlin 編譯為 JavaScript 代碼,從而可以在瀏覽器或 Node.js 環(huán)境中運(yùn)行。Kotlin/JS 使得前端開(kāi)發(fā)者可以使用 Kotlin 編寫(xiě)網(wǎng)頁(yè)應(yīng)用,同時(shí)充分利用現(xiàn)有的 JavaScript 庫(kù)和框架。使用場(chǎng)景是 Web 前端開(kāi)發(fā)。
Kotlin/Native 將 Kotlin 編譯為原生二進(jìn)制代碼,可以直接運(yùn)行在不依賴 JVM 或 JavaScript 引擎的環(huán)境中。Kotlin/Native 支持多種平臺(tái),包括 iOS、Windows、MacOS、Linux、嵌入式設(shè)備等,甚至于 Android、鴻蒙也可以使用 KN 跨平臺(tái)開(kāi)發(fā)(需要編寫(xiě) JNI or NAPI 橋接代碼)。
KMP 結(jié)合了 Kotlin/JVM、Kotlin/JS 和 Kotlin/Native 的優(yōu)勢(shì),使開(kāi)發(fā)者能夠在統(tǒng)一的代碼庫(kù)中,針對(duì)不同平臺(tái)進(jìn)行開(kāi)發(fā),實(shí)現(xiàn)跨平臺(tái)方案。

2. KMM 環(huán)境搭建

KDoctor

kdoctor 是一個(gè)用于驗(yàn)證 Kotlin Multiplatform Mobile (KMM) 開(kāi)發(fā)環(huán)境是否正確配置的命令行工具。它會(huì)檢查系統(tǒng)上的各種依賴和配置,確保已經(jīng)安裝并正確配置了所有必要的軟件,例如 Android Studio、Xcode、CocoaPods 等。如果是第一次設(shè)置 KMM 環(huán)境,強(qiáng)烈建議使用 kdoctor 來(lái)確認(rèn)一切都已正確配置。

安裝 kdoctor

對(duì)于 macOS 系統(tǒng):

  1. 確保 Homebrew 已安裝
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"  
  1. 安裝 kdoctor
brew install kdoctor  

使用 kdoctor 進(jìn)行環(huán)境檢查

安裝完 kdoctor 后,通過(guò)以下命令檢查 KMM 開(kāi)發(fā)環(huán)境:

kdoctor  

kdoctor 將檢查以下內(nèi)容:

JDK:是否安裝了 Java Development Kit。
Android Studio:是否安裝了 Android Studio 和必要的組件。
Xcode:是否安裝了 Xcode 和命令行工具。
CocoaPods:是否安裝了 CocoaPods(用于管理 iOS 依賴)。

運(yùn)行 kdoctor 后的示例輸出:

$ kdoctor  
[ ? ] Checking the Java version (11.0.8).   
[ ? ] Checking the Android Studio installation.   
[ ? ] Checking the Xcode installation.   
[ ? ] Checking the CocoaPods installation.   
Conclusion:
  ? Your operation system is ready for Kotlin Multiplatform Mobile Development!

如果 kdoctor 報(bào)告某些組件未正確安裝或配置,按照指導(dǎo)信息進(jìn)行相應(yīng)的操作來(lái)解決問(wèn)題。
在這個(gè)示例輸出中,kdoctor 表明所有必要的軟件都已正確安裝,已準(zhǔn)備好進(jìn)行 KMM 開(kāi)發(fā)。

Kotlin Multiplatform Plugin

在 Android Studio 中安裝 Kotlin Multiplatform 插件:

  1. 打開(kāi) Android Studio。
  2. 進(jìn)入 Settings -> Plugins。
  3. 搜索并安裝 Kotlin Multiplatform 插件。
  4. 重啟 Android Studio 以激活插件。

3. 創(chuàng)建 KMM 項(xiàng)目

新建項(xiàng)目

  1. 打開(kāi) Android Studio,選擇 New Project。
  2. 在項(xiàng)目模板中,選擇 Kotlin Multiplatform App。


    項(xiàng)目模板選擇
  3. 配置項(xiàng)目名稱、保存路徑和包名。


    配置項(xiàng)目

    這里 iOS 依賴管理我選擇使用 CocoaPods,可以簡(jiǎn)化依賴管理和配置,尤其是在需要集成大量第三方 iOS 庫(kù)的項(xiàng)目中。

  4. 點(diǎn)擊 Finish。

項(xiàng)目結(jié)構(gòu)

生成的 KMM 項(xiàng)目包含 Android 和 iOS 兩個(gè)平臺(tái)的代碼,以及一個(gè)共享代碼模塊:

MyKMMApp/  
 ├── androidApp/         # Android 殼工程
 ├── iosApp/             # iOS 殼工程
 ├── shared/             # 共享代碼模塊,目前僅包含業(yè)務(wù)邏輯,未來(lái)還會(huì)在此添加 UI 組件、資源等
 │   ├── src/  
 │   │   ├── commonMain/ # 共享邏輯  
 │   │   ├── androidMain/  # Android 平臺(tái)代碼  
 │   │   ├── iosMain/     # iOS 平臺(tái)代碼  
 └── build.gradle        # 項(xiàng)目級(jí) build 文件

配置 build.gradle

在 shared 模塊的 build.gradle.kts 文件中,配置 Kotlin Multiplatform 插件和依賴項(xiàng):

plugins {
    alias(libs.plugins.kotlinMultiplatform)
    alias(libs.plugins.kotlinCocoapods)
    alias(libs.plugins.androidLibrary)
}

kotlin {
    androidTarget {
        compilations.all {
            kotlinOptions {
                jvmTarget = "1.8"
            }
        }
    }
    iosX64()
    iosArm64()
    iosSimulatorArm64()

    cocoapods {
        summary = "Some description for the Shared Module"
        homepage = "Link to the Shared Module homepage"
        version = "1.0"
        ios.deploymentTarget = "16.0"
        podfile = project.file("../iosApp/Podfile")
        framework {
            baseName = "shared"
            isStatic = true
        }
    }
    
    sourceSets {
        val commonMain by getting {
            dependencies {
                //put your multiplatform dependencies here
            }
        }
        commonTest.dependencies {
            implementation(libs.kotlin.test)
        }
        val androidMain by getting {
            dependencies {
                 // Android 平臺(tái)依賴
            }
        }

        val iosX64Main by getting
        val iosArm64Main by getting
        val iosSimulatorArm64Main by getting
        val iosMain by creating {
            dependsOn(commonMain)
            iosX64Main.dependsOn(this)
            iosArm64Main.dependsOn(this)
            iosSimulatorArm64Main.dependsOn(this)

            dependencies {
                // iOS 平臺(tái)依賴
            }
        }
    }
}

android {
    namespace = "com.dixon.app.kmmsample"
    compileSdk = 34
    defaultConfig {
        minSdk = 24
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
}

4. 添加 Compose Multiplatform 依賴

上述項(xiàng)目?jī)H支持業(yè)務(wù)邏輯共享,支持 UI 共享需要添加 Compose Multiplatform 依賴。

依賴管理和 Gradle 配置

  1. 配置所需依賴
    在 gradle - libs.versions.toml 文件中添加所需依賴。
[versions]
...
[versions]
agp = "8.2.2"
kotlin = "1.9.20"
compose = "1.5.4"
compose-compiler = "1.5.4"
compose-material3 = "1.1.2"
androidx-activityCompose = "1.8.0"
# compose multiplatform support 
compose-plugin = "1.6.1"

[libraries]
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose" }
compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" }
compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" }
compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose" }
compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "compose-material3" }

[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
androidLibrary = { id = "com.android.library", version.ref = "agp" }
kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotlinCocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", version.ref = "kotlin" }
# compose multiplatform support
composeLibrary = { id = 'org.jetbrains.compose', version.ref = "compose-plugin" }
  1. 配置共享模塊
    在你的共享模塊(通常是 shared)的 build.gradle.kts 文件中添加 Compose Multiplatform 的依賴。
plugins {
    ...
    // compose multiplatform support
    alias(libs.plugins.composeLibrary)
}

kotlin {
    ...
    
    sourceSets {
        val commonMain by getting {
            dependencies {
                // put your multiplatform dependencies here
                // compose multiplatform support
                api(compose.material3) // md 設(shè)計(jì)的組件
                api(compose.foundation) // 基礎(chǔ)布局組件
                api(compose.ui) // 測(cè)量、布局、繪制、事件、Modifier
                api(compose.runtime) // 樹(shù)管理能力
                api(compose.animation) // 動(dòng)畫(huà)
            }
        }
        commonTest.dependencies {
            implementation(libs.kotlin.test)
        }
        ...
    }
}
  1. 創(chuàng)建跨平臺(tái)的 Compose UI
// shared/src/commonMain/kotlin/com/example/shared/Greeting.kt  
import androidx.compose.runtime.Composable  
import androidx.compose.ui.text.TextStyle  
import androidx.compose.ui.unit.sp  
import androidx.compose.material.Text  

@Composable  
fun Greeting(name: String) {  
    Text(  
        text = "Hello, $name!",  
        style = TextStyle(fontSize = 24.sp)  
    )  
}

Android 項(xiàng)目中使用 Compose Multiplatform

在 androidApp 模塊中,引入并使用共享模塊的 Compose 組件:

// androidApp/src/main/java/com/example/mykmmapp/MainActivity.kt  
package com.example.mykmmapp  

import android.os.Bundle  
import androidx.activity.compose.setContent  
import androidx.appcompat.app.AppCompatActivity  
import com.example.shared.Greeting  

class MainActivity : AppCompatActivity() {  
    override fun onCreate(savedInstanceState: Bundle?) {  
        super.onCreate(savedInstanceState)  
        setContent {  
            Greeting("Android")  
        }  
    }  
}  

Configuration 選擇 AndroidApp 運(yùn)行。

iOS 項(xiàng)目中使用 Compose Multiplatform

iOS 無(wú)法直接使用 Compose UI 組件,但是通過(guò)包裝成 UIViewController,可以確保這些組件能夠在 iOS 的 UIKit 和 SwiftUI 中使用,并充分利用 iOS 平臺(tái)的視圖管理機(jī)制。

  1. 在 iosMain 目錄下包裝 Compose UI 組件
// shared/src/iosMain/kotlin/com/example/mykmmapp/ui/App.kt   
package com.example.mykmmapp

import androidx.compose.ui.window.ComposeUIViewController
import com.example.mykmmapp.ui.App

fun AppViewController() = ComposeUIViewController { App() }
  1. 在 iosApp 模塊中,引入并使用 UIViewController 組件
// iosApp/iosApp/ContentView.swift
import SwiftUI
import shared

// 創(chuàng)建一個(gè) Swift 結(jié)構(gòu)體來(lái)包裝 UIViewController
struct ComposeUIViewControllerRepresentable: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> UIViewController {
        ComposeViewControllerKt.AppViewController()  // 調(diào)用 Kotlin 中創(chuàng)建的 Compose UIViewController
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}

struct ContentView: View {
    let greet = Greeting().greet()

    var body: some View {
        ComposeUIViewControllerRepresentable()
                 .edgesIgnoringSafeArea(.all) // 如果只想忽略水平方向的 Safe Area
//                 .edgesIgnoringSafeArea(.horizontal) // 如果只想忽略水平方向的 Safe Area
//                 .edgesIgnoringSafeArea([]) // 默認(rèn)不忽略 Safe Area
        // .all 狀態(tài)欄頂部高度、橫向 safe area 都會(huì)忽略
    }
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Configuration 選擇 iosApp 運(yùn)行,也可以 Xcode 開(kāi)啟 iosApp 目錄運(yùn)行。

5. 實(shí)現(xiàn)實(shí)例應(yīng)用

基礎(chǔ)環(huán)境已經(jīng)配置完畢,并可以運(yùn)行在不同平臺(tái)的設(shè)備上。接下來(lái),我將開(kāi)發(fā)一個(gè)簡(jiǎn)單的實(shí)例應(yīng)用,包含網(wǎng)絡(luò)請(qǐng)求、圖片展示、本地資源管理、日志、路由、序列化等應(yīng)用開(kāi)發(fā)非常基礎(chǔ)的能力,為大家演示 KMM + Compose Multiplatform 項(xiàng)目的實(shí)戰(zhàn)能力。

開(kāi)始前,推薦簡(jiǎn)單了解 kmp-awesome 倉(cāng)庫(kù),它整理和推薦了一些 Kotlin Multiplatform 相關(guān)的優(yōu)秀庫(kù)、工具和資源,是一個(gè)非常有價(jià)值的資源集合,適合那些想要深入了解和使用 Kotlin Multiplatform 的開(kāi)發(fā)者。

I. 導(dǎo)航

介紹

Navigator 用于在多平臺(tái)項(xiàng)目中進(jìn)行高效導(dǎo)航,如果不使用導(dǎo)航,你可能需要在不同的平臺(tái)下創(chuàng)建頁(yè)面(如 Android 平臺(tái)頁(yè)面由 Activity 承載),并橋接跳轉(zhuǎn)邏輯。而 Navigator 使你可以編寫(xiě)一次導(dǎo)航邏輯,并在 iOS 和 Android 上重用,且提供了一種簡(jiǎn)潔的方法來(lái)管理應(yīng)用的所有路由。

這里我選用 PreCompose 庫(kù)提供的跨平臺(tái)導(dǎo)航能力。

依賴管理和 Gradle 配置

  1. 配置所需依賴
[versions]
...
precompose = "1.6.1"

[libraries]
...
precompose = { module = "moe.tlaster:precompose", version.ref = "precompose" }
precompose-viewmodel = { module = "moe.tlaster:precompose-viewmodel", version.ref = "precompose" }
  1. 配置共享模塊
val commonMain by getting {
    // 跨平臺(tái)導(dǎo)航庫(kù)
    api(libs.precompose)
    // ViewModel
    implementation(libs.precompose.viewmodel)
}

使用導(dǎo)航

在 Compose Root UI 組件使用導(dǎo)航:

package com.dixon.app.kmmsample.ui

import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.staticCompositionLocalOf
import moe.tlaster.precompose.PreComposeApp
import moe.tlaster.precompose.navigation.NavHost
import moe.tlaster.precompose.navigation.Navigator
import moe.tlaster.precompose.navigation.rememberNavigator
import moe.tlaster.precompose.navigation.transition.NavTransition

/*
    navigator 使用:
    Navigator.navigate(route: String, options: NavOptions? = null)
    Navigator.goBack()
    Navigator.canGoBack: Boolean
 */


val LocalNavigator = staticCompositionLocalOf<Navigator> {
    error("Navigator not provided")
}

@Composable
fun App() {
    PreComposeApp {
        // your app's content goes here
        val navigator = rememberNavigator()
        CompositionLocalProvider(LocalNavigator provides navigator) {
            NavHost(
                // Assign the navigator to the NavHost
                navigator = navigator,
                // Navigation transition for the scenes in this NavHost, this is optional
                navTransition = NavTransition(),
                // The start destination
                initialRoute = PAGE_HOME_MAIN_ROUTE,
            ) {
                pages.forEach {
                    scene(
                        // Scene's route path
                        route = it.route,
                        // Navigation transition for this scene, this is optional
                        navTransition = NavTransition(),
                        content = it.content
                    )
                }
            }
        }
    }
}

后續(xù)任意 Composable 使用 LocalNavigator.current.navigate(route, options) 進(jìn)行頁(yè)面跳轉(zhuǎn)。
關(guān)于路由的詳細(xì)用法和封裝可以跳轉(zhuǎn)此 示例倉(cāng)庫(kù) 查看。

在導(dǎo)航依賴?yán)?,我同時(shí)導(dǎo)入了 ViewModel 依賴。這是因?yàn)樵趯?dǎo)航返回時(shí),Composable 函數(shù)會(huì)重新執(zhí)行,部分?jǐn)?shù)據(jù)需要通過(guò) ViewModel 緩存起來(lái)。可以說(shuō),導(dǎo)航和 ViewModel 是密不可分的。在下邊的網(wǎng)絡(luò)庫(kù)中你會(huì)看到相關(guān)應(yīng)用。

II. 網(wǎng)絡(luò)庫(kù)

介紹

Ktor Client 是 Ktor 框架的一部分,由 JetBrains 開(kāi)發(fā),用于構(gòu)建高效、非阻塞的 HTTP 客戶端。Ktor Client 支持多個(gè)平臺(tái),如 JVM、Android、JavaScript、Native 等,使其成為 Kotlin Multiplatform 項(xiàng)目中理想的選擇。

依賴管理和 Gradle 配置

  1. 配置所需依賴
[versions]
...
kotlinxSerializationJson = "1.6.1"
kotlinxSerializationPlugin = "2.0.0"
# ktorClientCore = "3.0.0-beta-2" # 新版本有問(wèn)題棄用
ktorClientCore = "2.3.12" # 選用穩(wěn)定版本

[libraries]
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktorClientCore" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktorClientCore" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktorClientCore" }
ktor-client-ios = { module = "io.ktor:ktor-client-ios", version.ref = "ktorClientCore" }
ktor-client-json = { module = "io.ktor:ktor-client-json", version.ref = "ktorClientCore" }
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktorClientCore" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktorClientCore" }
ktor-client-serialization = { module = "io.ktor:ktor-client-serialization", version.ref = "ktorClientCore" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktorClientCore" }

[plugins]
kotlinSerializationPlugin = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinxSerializationPlugin" }
  1. 配置共享模塊
plugins {
    ...
    // 添加 Kotlin Serialization 插件,注意低版本無(wú)效
    // 配套 libs.kotlinx.serialization.json 庫(kù)使用
    alias(libs.plugins.kotlinSerializationPlugin)
}

kotlin {
   ...
    
    sourceSets {
        val commonMain by getting {
            dependencies {
                // put your multiplatform dependencies here
                ...
                // 跨平臺(tái)網(wǎng)絡(luò)庫(kù)
                implementation(libs.ktor.client.core)
                implementation(libs.ktor.client.cio)
                implementation(libs.ktor.client.logging)
                implementation(libs.ktor.client.content.negotiation)
                implementation(libs.ktor.serialization.kotlinx.json)
            }
        }
        val androidMain by getting {
            dependencies {
                // 平臺(tái)網(wǎng)絡(luò)庫(kù)引擎
                implementation(libs.ktor.client.okhttp)
            }
        }

        ...
        val iosMain by creating {
            ...
            dependencies {
                // 平臺(tái)網(wǎng)絡(luò)庫(kù)引擎
                implementation(libs.ktor.client.ios)
            }
        }
    }
}

使用網(wǎng)絡(luò)庫(kù)

這里我使用 狗狗免費(fèi) API 來(lái)測(cè)試我的網(wǎng)絡(luò)功能。

  1. 初始化 KtorClient 實(shí)例
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.network.sockets.ConnectTimeoutException
import io.ktor.client.plugins.ClientRequestException
import io.ktor.client.plugins.HttpRequestTimeoutException
import io.ktor.client.plugins.ServerResponseException
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.cookies.AcceptAllCookiesStorage
import io.ktor.client.plugins.cookies.HttpCookies
import io.ktor.client.plugins.logging.DEFAULT
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging
import io.ktor.client.request.get
import io.ktor.serialization.kotlinx.json.json

val ktorClient = HttpClient {
    install(ContentNegotiation) {
        json()
    }
    install(HttpCookies) {
        storage = AcceptAllCookiesStorage()
    }
    install(Logging) {
        logger = Logger.DEFAULT
        level = LogLevel.INFO
    }
    expectSuccess = true
}

// ---- 簡(jiǎn)單封裝 ----

private const val KTOR_TAG = "KtorSafeCall"

// append try-catch
suspend fun <T> safeCall(
    client: HttpClient = ktorClient,
    call: suspend HttpClient.() -> T
): Result<T> {
    return try {
        val result = call(client)
        com.dixon.app.kmmsample.core.base.Logger.i(KTOR_TAG) { "Request succeed: $result" }
        Result(data = result)
    } catch (e: ConnectTimeoutException) {
        com.dixon.app.kmmsample.core.base.Logger.i(KTOR_TAG) { "Connection timed out: ${e.message}" }
        Result(exception = e)
    } catch (e: HttpRequestTimeoutException) {
        com.dixon.app.kmmsample.core.base.Logger.i(KTOR_TAG) { "Request timed out: ${e.message}" }
        Result(exception = e)
    } catch (e: ClientRequestException) {
        // 4xx responses
        com.dixon.app.kmmsample.core.base.Logger.i(KTOR_TAG) { "Client request error: ${e.response.status.description}" }
        Result(exception = e)
    } catch (e: ServerResponseException) {
        // 5xx responses
        com.dixon.app.kmmsample.core.base.Logger.i(KTOR_TAG) { "Server response error: ${e.response.status.description}" }
        Result(exception = e)
    } catch (e: Exception) {
        com.dixon.app.kmmsample.core.base.Logger.i(KTOR_TAG) { "An unexpected error occurred: ${e.message}" }
        Result(exception = e)
    }
}

suspend inline fun <reified T> apiGet(
    source: String
): Result<T> =
    safeCall {
        get(source).body<T>()
    }

data class Result<T>(
    val data: T? = null,
    val exception: Throwable? = null
)

fun <T> Result<T>.isSucceed() = data != null

fun <T> Result<T>.isFailed() = exception != null

inline fun <T> Result<T>.dispose(
    onSucceed: (T) -> Unit,
    onFailed: (Throwable) -> Unit
) {
    if (data != null) {
        onSucceed.invoke(data)
    } else {
        onFailed.invoke(exception ?: RuntimeException("Unknown Exception"))
    }
}

inline fun <T> Result<T>.disposeSucceed(
    onSucceed: (T) -> Unit,
) = this.apply {
    if (data != null) {
        onSucceed.invoke(data)
    }
}

inline fun <T> Result<T>.disposeFailed(
    onFailed: (Throwable) -> Unit,
) = this.apply {
    if (exception != null) {
        onFailed.invoke(exception)
    }
}
  1. 創(chuàng)建 ViewModel 發(fā)起并保存請(qǐng)求
package com.dixon.app.kmmsample.logic.home

import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import com.dixon.app.kmmsample.bean.DogData
import com.dixon.app.kmmsample.core.base.Logger
import com.dixon.app.kmmsample.core.base.apiGet
import com.dixon.app.kmmsample.core.base.disposeFailed
import com.dixon.app.kmmsample.core.base.disposeSucceed
import kotlinx.coroutines.launch
import moe.tlaster.precompose.viewmodel.ViewModel
import moe.tlaster.precompose.viewmodel.viewModelScope

class HomeViewModel : ViewModel() {

    private var _data: MutableState<DogData?> = mutableStateOf(null)
    val data: State<DogData?> = _data

    // 請(qǐng)求數(shù)據(jù),并將返回的 Json 轉(zhuǎn)為 DogData 保存起來(lái)
    fun fetchData() {
        viewModelScope.launch {
            apiGet<List<DogData>>("https://api.thecatapi.com/v1/images/search?api_key=請(qǐng)自行申請(qǐng) api_key")
                .disposeSucceed {
                    _data.value = it.first()
                }.disposeFailed {
                    Logger.e("HomeViewModel#fetchData") { it.message.toString() }
                }
        }
    }
}

Bean 類:

package com.dixon.app.kmmsample.bean

import kotlinx.serialization.Serializable

/**
 * 測(cè)試接口返回的 json 數(shù)據(jù)
 */
@Serializable
data class DogData(
    val id: String,
    val url: String,
    val width: Int,
    val height: Int
)
  1. 頁(yè)面使用 ViewModel
package com.dixon.app.kmmsample.ui.home

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.dixon.app.kmmsample.MR
import com.dixon.app.kmmsample.core.base.Logger
import com.dixon.app.kmmsample.logic.home.HomeViewModel
import com.dixon.app.kmmsample.ui.Builder
import com.dixon.app.kmmsample.ui.LocalNavigator
import com.dixon.app.kmmsample.ui.PAGE_IMAGE_DETAIL_ROUTE
import com.dixon.app.kmmsample.ui.build
import com.dixon.app.kmmsample.ui.put
import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
import moe.tlaster.precompose.viewmodel.viewModel

/**
 * Navigator與傳統(tǒng)Activity不同,跨頁(yè)面要使用ViewModel緩存數(shù)據(jù),否則返回頁(yè)面會(huì)重新請(qǐng)求
 */
@Composable
fun HomeMain() {
    Logger.i("HomeMain") { "Composable Root : HomeMain" }
    val navigator = LocalNavigator.current
    val homeVM = viewModel(
        modelClass = HomeViewModel::class,
        creator = {
            HomeViewModel()
        })
    val data by homeVM.data
    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Button(onClick = {
            homeVM.fetchData()
        }) {
            Text("點(diǎn)擊獲取隨機(jī)圖片數(shù)據(jù)")
        }
        Spacer(Modifier.height(20.dp))
        Text(
            text = data?.toString() ?: "請(qǐng)先獲取數(shù)據(jù)!",
            fontSize = 14.sp,
            color = Color.Gray
        )
        Spacer(Modifier.height(20.dp))
        Button(onClick = {
            // 導(dǎo)航到圖片展示頁(yè),詳見(jiàn)下文圖片庫(kù)
            navigator.Builder(PAGE_IMAGE_DETAIL_ROUTE)
                .put("resource", data?.url)
                .build()
        }) {
            Text("展示網(wǎng)圖")
        }
    }
}

關(guān)于網(wǎng)絡(luò)庫(kù)的詳細(xì)用法和封裝可以跳轉(zhuǎn)此 示例倉(cāng)庫(kù) 查看。

III. 日志

介紹

演示項(xiàng)目相對(duì)簡(jiǎn)單,日志功能我選擇了自行實(shí)現(xiàn),并借此演示如何使用平臺(tái)能力實(shí)現(xiàn)跨平臺(tái)代碼。

代碼實(shí)現(xiàn)

  1. CommonMain 添加跨平臺(tái)代碼
// shared/src/commonMain/kotlin/com/dixon/app/kmmsample/core/base/Logger.kt
package com.dixon.app.kmmsample.core.base

object Logger {

    enum class LogLevel {
        DEBUG, INFO, WARNING, ERROR, NONE
    }

    var currentLogLevel: LogLevel = LogLevel.NONE
        private set
    var isLoggingEnabled: Boolean = false
        private set

    // 首次調(diào)用 Ln 時(shí)執(zhí)行
    init {
        enableLogging(true)
        setLogLevel(LogLevel.DEBUG)
    }

    fun setLogLevel(level: LogLevel) {
        currentLogLevel = level
    }

    fun enableLogging(enable: Boolean) {
        isLoggingEnabled = enable
    }

    fun d(tag: String, message: () -> String) {
        if (isLoggingEnabled && currentLogLevel <= LogLevel.DEBUG) {
            print(LogLevel.DEBUG, tag, message.invoke())
        }
    }

    fun i(tag: String, message: () -> String) {
        if (isLoggingEnabled && currentLogLevel <= LogLevel.INFO) {
            print(LogLevel.INFO, tag, message.invoke())
        }
    }

    fun w(tag: String, message: () -> String) {
        if (isLoggingEnabled && currentLogLevel <= LogLevel.WARNING) {
            print(LogLevel.WARNING, tag, message.invoke())
        }
    }

    fun e(tag: String, message: () -> String) {
        if (isLoggingEnabled && currentLogLevel <= LogLevel.ERROR) {
            print(LogLevel.ERROR, tag, message.invoke())
        }
    }
}

internal expect fun print(level: Logger.LogLevel, tag: String, message: String)

在不同平臺(tái)上,輸出日志的方式是不一致的(如 Android 使用 Log,iOS 使用 NSLog)。為了支持跨平臺(tái)的開(kāi)發(fā),Kotlin 提供了 expect 和 actual 關(guān)鍵字。expect 聲明用于在共享模塊中聲明一個(gè)平臺(tái)無(wú)關(guān)的接口或方法,而 actual 聲明用于在平臺(tái)特定的模塊中實(shí)現(xiàn)這些接口或方法。實(shí)際上項(xiàng)目依賴的基礎(chǔ)能力庫(kù)都是通過(guò)這樣的方式實(shí)現(xiàn)了跨平臺(tái)能力。

  1. 實(shí)現(xiàn) Android 平臺(tái)代碼
// shared/src/androidMain/kotlin/com/dixon/app/kmmsample/core/base/Logger.android.kt
package com.dixon.app.kmmsample.core.base

import android.util.Log

internal actual fun print(level: Logger.LogLevel, tag: String, message: String) {
    when (level) {
        Logger.LogLevel.INFO -> Log.i(tag, message)
        Logger.LogLevel.DEBUG -> Log.d(tag, message)
        Logger.LogLevel.WARNING -> Log.w(tag, message)
        Logger.LogLevel.ERROR -> Log.e(tag, message)
        Logger.LogLevel.NONE -> Log.i(tag, message)
    }
}
  1. 實(shí)現(xiàn) iOS 平臺(tái)代碼
// shared/src/iosMain/kotlin/com/dixon/app/kmmsample/core/base/Logger.ios.kt
package com.dixon.app.kmmsample.core.base

import platform.Foundation.NSLog

/*
    關(guān)于在 iosMain 中使用 ios 原生/第三方 api:
    1. 在 Kotlin Multiplatform 項(xiàng)目中,NSLog 以及其他 NS 前綴的函數(shù)和類來(lái)自于 Kotlin/Native 提供的對(duì) Apple 平臺(tái)(iOS 和 macOS)的基礎(chǔ)庫(kù)的綁定。
    這些綁定是 Kotlin/Native 標(biāo)準(zhǔn)庫(kù)的一部分,允許你在 Kotlin 代碼中直接使用 Apple 平臺(tái)的 API。
    2. 通過(guò)使用 Kotlin/Native 提供的 CocoaPods 集成,你可以在 Kotlin Multiplatform 項(xiàng)目中使用 iOS 的第三方 SDK。
 */

internal actual fun print(level: Logger.LogLevel, tag: String, message: String) {
    val logMessage = when (level) {
        Logger.LogLevel.DEBUG -> "DEBUG[$tag]: $message"
        Logger.LogLevel.INFO -> "INFO[$tag]: $message"
        Logger.LogLevel.WARNING -> "WARN[$tag]: $message"
        Logger.LogLevel.ERROR -> "ERROR[$tag]: $message"
        Logger.LogLevel.NONE -> "NONE[$tag]: $message"
    }
    NSLog(logMessage)
}

使用日志

Logger.i(TAG) { "Request succeed: $xx" }

IV. 圖片庫(kù)

介紹

一般應(yīng)用都有展示本地圖片、網(wǎng)絡(luò)圖片的能力,本地圖片涉及本地資源,我們先跳過(guò),這里我們演示如何展示網(wǎng)絡(luò)圖片。
這里我選用 Coil3 庫(kù)提供的跨平臺(tái)圖片庫(kù)能力。

Coil3 目前只有 Alpha 版本,有精力也可以通過(guò)橋接各端圖片庫(kù)自行實(shí)現(xiàn)。

依賴管理和 Gradle 配置

  1. 配置所需依賴
[versions]
coil3 = "3.0.0-alpha08" # 09 沒(méi)有對(duì)應(yīng)版本的 ktor

[libraries]
coil3-core = { module = "io.coil-kt.coil3:coil", version.ref = "coil3" }
coil3-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil3" }
coil3-ktor = { module = "io.coil-kt.coil3:coil-network-ktor", version.ref = "coil3" }
  1. 配置共享模塊
val commonMain by getting {
            dependencies {
                // put your multiplatform dependencies here
                ...
                // 圖片庫(kù) 目前還是Alpha版本
                implementation(libs.coil3.core)
                implementation(libs.coil3.ktor)
                implementation(libs.coil3.compose)
            }
        }

使用圖片庫(kù)

AsyncImage(
    modifier = Modifier.fillMaxWidth().align(Alignment.Center)
        .clip(RoundedCornerShape(10.dp)),
    model = resource,
    contentDescription = null,
    contentScale = ContentScale.FillWidth
)

在 Demo 項(xiàng)目里,我創(chuàng)建了一個(gè)新的頁(yè)面用于展示網(wǎng)絡(luò)庫(kù)請(qǐng)求到的狗狗圖片,使用導(dǎo)航可以攜帶圖片 url 跳轉(zhuǎn)到該頁(yè)面,并且通過(guò) navigator.goBack() 可以重新返回至上一級(jí)頁(yè)面。

package com.dixon.app.kmmsample.ui.image

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil3.compose.AsyncImage
import com.dixon.app.kmmsample.core.base.Logger
import com.dixon.app.kmmsample.ui.LocalNavigator

@Composable
fun ImageDetail(resource: String?) {
    Logger.i("ImageDetail") { "Composable Root : ImageDetail $resource" }
    val navigator = LocalNavigator.current
    if (resource.isNullOrEmpty()) {
        Logger.i("ImageDetail") { "Composable Root : ImageDetail ErrorTipPage" }
        Column(
            horizontalAlignment = Alignment.CenterHorizontally,
            modifier = Modifier.fillMaxSize()
        ) {
            Text(
                text = "Error",
                fontSize = 20.sp,
                fontWeight = FontWeight.ExtraBold
            )
            Spacer(Modifier.height(20.dp))
            Text(text = "Please request image first!")
            Spacer(Modifier.height(20.dp))
            Button(onClick = {
                navigator.goBack()
            }) {
                Text("返回")
            }
        }
    } else {
        Logger.i("ImageDetail") { "Composable Root : ImageDetail ContentPage" }
        Box(
            modifier = Modifier
                .fillMaxSize()
                .background(Color.Black)
        ) {
            /*
             or ImageRequest、Painter
             */
            AsyncImage(
                modifier = Modifier.fillMaxWidth().align(Alignment.Center)
                    .clip(RoundedCornerShape(10.dp)),
                model = resource,
                contentDescription = null,
                contentScale = ContentScale.FillWidth
            )
            Button(onClick = {
                navigator.goBack()
            }) {
                Text("Back")
            }
        }
    }
}

建議結(jié)合 示例倉(cāng)庫(kù) 查看相關(guān)代碼。

V. 本地資源

介紹

Compose UI 支持跨平臺(tái),相應(yīng)的,string、image、fonts、colors 等本地資源也需要跨平臺(tái)支持。
moko-resources 是一個(gè) Kotlin Multiplatform 庫(kù)和 Gradle 插件,旨在提供在 macOS、iOS、Android、JVM 和 JS/Browser 上訪問(wèn)本地資源的功能。

依賴管理和 Gradle 配置

  1. 配置根目錄 build.gradle
buildscript {
    dependencies {
        // 本地資源配置1
        classpath("dev.icerock.moko:resources-generator:0.24.1")
    }
}
  1. 配置共享模塊
plugins {
    ...
    // 本地資源配置2
    id("dev.icerock.mobile.multiplatform-resources")
}

kotlin {
    sourceSets {
        val commonMain by getting {
            dependencies {
                //put your multiplatform dependencies here
                ...
                // 本地資源配置3
                api("dev.icerock.moko:resources:0.24.1")
                api("dev.icerock.moko:resources-compose:0.24.1")
            }
        }
        ...
    }
}

// 本地資源配置 4
multiplatformResources {
    resourcesPackage.set("com.dixon.app.kmmsample") // required
//    resourcesClassName.set("SharedRes") // optional, default MR
//    resourcesVisibility.set(MRVisibility.Internal) // optional, default Public
    iosBaseLocalizationRegion.set("en") // optional, default "en"
    iosMinimalDeploymentTarget.set("16.0") // optional, default "9.0"
}
  1. iOS 配置
    由于 iOS 是靜態(tài)庫(kù),不支持直接打包資源,因此需要通過(guò)腳本將資源拷貝到 iOS 應(yīng)用中。

配置步驟

3.1. gradle.properties 中添加以下配置

moko.resources.disableStaticFrameworkWarning=true  

3.2. 在 Xcode 中配置腳本

* 打開(kāi) `iosApp`  
* 點(diǎn)擊 `iosApp` 目錄  
* 選擇 `Build Phases`  
* 點(diǎn)擊 "+"  
* 選擇 `New Run Script Phase`  
* 將以下腳本粘貼進(jìn)去:  
# Type a script or drag a script file from your workspace to insert its path.  
"$SRCROOT/../gradlew" -p "$SRCROOT/../" :shared:copyFrameworkResourcesToApp \
    -Pmoko.resources.BUILT_PRODUCTS_DIR="$BUILT_PRODUCTS_DIR" \
    -Pmoko.resources.CONTENTS_FOLDER_PATH="$CONTENTS_FOLDER_PATH" \
    -Pkotlin.native.cocoapods.platform="$PLATFORM_NAME" \
    -Pkotlin.native.cocoapods.archs="$ARCHS" \
    -Pkotlin.native.cocoapods.configuration="$CONFIGURATION"   

更多資源配置與腳本配置細(xì)節(jié)詳見(jiàn) GitHub 項(xiàng)目文檔

使用本地資源

  1. 添加本地資源
    新增資源目錄:
MyKMMApp/  
├── shared/                        # 共享模塊  
│   ├── src/  
│   │   ├── commonMain/            # 共享的主要代碼(跨平臺(tái))  
│   │   │   ├── kotlin/            # 共享的 Kotlin 源代碼文件夾  
│   │   │   ├── moko-resources/            # 共享的本地資源文件夾  
│   │   │   │   ├── images/            # 共享的本地圖片資源文件夾
│   │   │   │   │   ├── demo@2x.png            # 共享的本地圖片資源
│   │   │   │   ├── base/            # 共享的本地字符串資源文件夾
│   │   │   │   │   ├── strings.xml            # 共享的本地字符串資源
│   │   │   │   ├── zh-CN/            # 共享的本地字符串資源文件夾 中文
│   │   │   │   │   ├── strings.xml             # 共享的本地字符串資源
│   │   │   ├── ...  
│   ├── ...  
├── ...
  1. 使用資源
// 圖片
painterResource(MR.images.demo)
// 字符串
stringResource(MR.strings.welcome_message)

詳見(jiàn) 示例倉(cāng)庫(kù)

VI. 效果演示

示例倉(cāng)庫(kù) 演示:

首頁(yè).png

圖片演示頁(yè).png

6. KMM & Compose Multiplatform 進(jìn)階 & 暢想

使用 KMM(Kotlin Multiplatform Mobile)和 Compose Multiplatform 結(jié)合開(kāi)發(fā)具有如下優(yōu)點(diǎn):

優(yōu)點(diǎn)

  1. 共享邏輯:利用 KMM 共享業(yè)務(wù)邏輯和數(shù)據(jù)處理代碼。
  2. 共享 UI:通過(guò) Compose Multiplatform 共享 UI 組件和布局。

這種結(jié)合使得開(kāi)發(fā)人員可以在業(yè)務(wù)邏輯和界面上均實(shí)現(xiàn)代碼共享,從而提高開(kāi)發(fā)效率和一致性,同時(shí)保留對(duì)各個(gè)平臺(tái)進(jìn)行特定優(yōu)化和實(shí)現(xiàn)的靈活性。

跨平臺(tái)特性

由于 KMM 和 Compose Multiplatform 出色的跨平臺(tái)特性,即便是新的平臺(tái)也可以低成本支持。據(jù)筆者所知,某廠支持了包括鴻蒙平臺(tái)在內(nèi)的跨移動(dòng)三端的 KMM & Compose Multiplatform,并基于此開(kāi)發(fā)跨三端的業(yè)務(wù)代碼。相信這些代碼不久的將來(lái)就會(huì)開(kāi)源出來(lái)。

支持鴻蒙平臺(tái)
Compose Multiplatform 仍然可以基于 Skiko 渲染,KMM 可以使用 Kotlin/JS 橋接 ArkTs,但是 Kotlin 的很多功能特性會(huì)喪失。因此,建議使用 Kotlin/Native + N-API 橋接層,這樣可以用 Kotlin 語(yǔ)言編寫(xiě)高性能的本地代碼,并將其作為原生擴(kuò)展在 Node.js 應(yīng)用程序中使用。

基本原理

  1. 編寫(xiě) Kotlin/Native 代碼:使用 Kotlin/Native 編寫(xiě)需要實(shí)現(xiàn)的功能代碼,并將其編譯為共享庫(kù)(例如 .so 文件)。
  2. 使用 N-API:在 Node.js 端使用 N-API 管理和調(diào)用這一共享庫(kù)。

7. 進(jìn)階學(xué)習(xí)和資源

Kotlin Multiplatform Mobile 官方文檔
KMM 官方博客,最新消息
Compose-multiplatform 官方 Github
KMM 開(kāi)源三方庫(kù)整合

8. 總結(jié)

Kotlin Multiplatform Mobile (KMM) 和 Compose Multiplatform 未來(lái)充滿前景。以下是一些核心展望:

  1. 更多生產(chǎn)級(jí)應(yīng)用:更多企業(yè)和開(kāi)發(fā)者將采用這些技術(shù)構(gòu)建跨平臺(tái)應(yīng)用。
  2. 改進(jìn)的工具鏈:IntelliJ IDEA 和 Android Studio 將提供更強(qiáng)大的支持,提升開(kāi)發(fā)效率。
  3. 更多平臺(tái)支持:支持范圍將擴(kuò)展到桌面和 Web 平臺(tái),實(shí)現(xiàn)更廣泛的跨平臺(tái)覆蓋。
  4. 性能優(yōu)化:進(jìn)一步提升生成代碼的性能,接近原生體驗(yàn)。
  5. 壯大的庫(kù)生態(tài)系統(tǒng):更多第三方庫(kù)和插件將出現(xiàn),簡(jiǎn)化開(kāi)發(fā)過(guò)程。
  6. 社區(qū)貢獻(xiàn):活躍的社區(qū)將提供豐富的開(kāi)源項(xiàng)目和知識(shí)分享,推動(dòng)技術(shù)發(fā)展。
  7. 改進(jìn)的文檔和教育資源:官方和社區(qū)將提供更全面的教程和學(xué)習(xí)材料,助力開(kāi)發(fā)者快速上手。
  8. 廣泛的企業(yè)級(jí)應(yīng)用采納:隨著技術(shù)的成熟和優(yōu)化,更多企業(yè)將采用這些技術(shù)開(kāi)發(fā)關(guān)鍵應(yīng)用。

總之,KMM 和 Compose Multiplatform 將大幅簡(jiǎn)化跨平臺(tái)開(kāi)發(fā),提高效率和應(yīng)用質(zhì)量,成為跨平臺(tái)開(kāi)發(fā)的重要選擇。

[TOC]

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容