精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

攜程機票 App KMM 跨端 KV 存儲庫 MMKV-Kotlin

存儲
對于有 MMKV 使用經驗的原移動端開發人員來說,學習遷移成本很低。在經過了大半年的線上實驗證明了其穩定性與功能的完整性后,攜程機票研發團隊決定將其開源,為 Kotlin Multiplatform 開源生態添磚加瓦。

作者 | 禹昂,攜程移動端資深工程師,專注于 Kotlin 移動端跨平臺領域,Kotlin 中文社區核心成員,圖書《Kotlin 編程實踐》譯者。

一、背景

攜程機票移動端研發團隊自 2021 年始就一直在移動端實踐 Kotlin Multiplatform 技術(請見參考鏈接 1)。由于目前 Kotlin Multiplatform 生態尚處于起步階段,大部分 Kotlin 開源庫都是 JVM only 的,因此在我們團隊的日常開發過程中迫切需要一些能夠支持 KMM(Kotlin Multiplatform Mobile)的基礎庫或框架。

在原生移動端開發中,Android SDK 提供了 SharedPreferences,iOS 提供了 NSUserDefaults 用于 KV 存儲功能,但這二者在性能要求較高的情況下不能滿足需求。后來雖然 Google 推出了 Jetpack Datastore 用于替換 SharedPreferences,但它僅僅支持 Android 平臺。

攜程的基礎框架團隊經過一系列評估后決定使用騰訊的開源庫 MMKV (參考鏈接 2)用于滿足攜程 App 的 KV 存儲需求。相較于 SharedPreferences 與 NSUserDefaults,MMKV 擁有更強大的性能;相較于 Jetpack Datastore,MMKV 同時支持多個平臺,雙端業務邏輯一致性會更好;此外,MMKV 的優勢還包括:支持多進程訪問、進程被突然殺死時存儲依然可以生效等。因此,攜程機票移動端研發團隊決定基于 MMKV 二次開發,使 MMKV 支持 Kotlin Multiplatform 技術棧。

MMKV-Kotlin 因此應運而生,它擁有極為便捷的集成方式,與 MMKV 高度相似的 API 等諸多特點。對于有 MMKV 使用經驗的原移動端開發人員來說,學習遷移成本很低。在經過了大半年的線上實驗證明了其穩定性與功能的完整性后,攜程機票研發團隊決定將其開源,為 Kotlin Multiplatform 開源生態添磚加瓦。

我們先來簡單介紹一下 MMKV-Kotlin 的用法,便于讀者對其有個較為直觀的認識,也便于后文討論其內部設計。

2.1 安裝與導入

對于 KMM 開發者,在 common source set 中導入 MMKV-Kotlin,在 Gradle 腳本(kts)中添加:

dependencies {     
implementation("com.ctrip.flight.mmkv:mmkv-kotlin:1.0.0")
}

如果您是使用 Kotlin 編寫純 Android 程序的用戶,則導入方式為在 Gradle 腳本(kts)中添加:

dependencies {     
implementation("com.ctrip.flight.mmkv:mmkv-kotlin-android:1.0.0")
}

對于純 Android 開發者來說,雖然沒有跨平臺的需求,但 MMKV-Kotlin 的 API 有針對 Kotlin 語法作出的優化。

注意,截至文章發布前,MMKV-Kotlin 的最新版本是 1.2.0,基于 Kotlin 1.7.0,MMKV 1.2.13。

2.2 初始化

MMKV 在使用前需要進行初始化,由于 MMKV-Android 強依賴于 Context 類型,因此 MMKV-Kotlin 的初始化 API 在兩端有所區別,需要在 Android 與 iOS 的主工程或 KMM 的平臺相關 source set 中分別初始化:

Android:

import com.ctrip.flight.mmkv.initialize
// In Android source set
fun initializeMMKV(context: Context) {
val rootDir = initialize(context)
Log.d("MMKV Path", rootDir)
}

iOS:

import com.ctrip.flight.mmkv.initialize

// In iOS source set
fun initializeMMKV(rootDir: String) {
initialize(rootDir)
Log.d("MMKV Path", rootDir)
}

2.3 簡單的讀寫操作

import com.ctrip.flight.mmkv.defaultMMKV
fun demo() {
val kv = defaultMMKV()
kv.set("Boolean", true)
kv.set("Int", Int.MIN_VALUE)
kv.set("String", "Hello from mmkv")
println("Boolean: ${kv.takeBoolean("Boolean")}")
println("Int: ${kv.takeInt("Int")}")
println("String: ${kv.takeString("String")}")
}

使用方式與 MMKV 的 Java 及 Objective-C API 高度相似。

三、架構設計

MMKV core 采用 C++ 編寫,其絕大部分功能都在 core 實現。例如 mmap 提供的內存-文件映射、數據根據 protobuf 協議序列化與反序列化、多進程實現等等。core 直接對外暴露 C++ API,在 Win32、POSIX 等系統上可由開發者直接使用。在 core 的外層 MMKV 提供了多種語言的包裝,用于支持多種技術棧。例如:Java(Android)、Objective-C(iOS/macOS)、Dart(Flutter)、 JavaScript(React-Native,非騰訊開發與維護)。

MMKV-Kotlin 在底層需要依賴并調用 MMKV,對上希望暴露與 MMKV 類似的 API 并做一些符合語言特性的封裝。

MMKV-Kotlin 需要在兩個平臺相關的 source set 分別集成 MMKV。在 Android source set 中,如果直接集成 MMKV core 需要手動編寫 JNI 來做 JVM 層與 C++ 的交互,投入產出比太小, 因此我們選擇直接在 Gradle 腳本中通過 Maven 依賴 MMKV-Android,在 Android source set 中直接調用其 Java API。而在 iOS source set 中,由于 Kotlin 目前只與 C 和 Objective-C 有較為完整的互操作能力,因此直接依賴提供 C++ API 的 MMKV core 也并不合適,我們選擇在 Gradle 腳本中通過 CocoaPods 依賴 MMKV-iOS,在 iOS source set 中通過其 Objective-C API 完成對 MMKV 的調用。

MMKV-Kotlin 的總體設計見下圖:

圖片

四、實現簡介

在《攜程機票 App KMM 跨端生產實踐》一文的 2.2 小節中我們曾以 MMKV 作為 demo 來介紹 KMM 的 expect-actual 技術。但本次開源的版本為了代碼的健壯性與實用性, 調整了具體的實現方式,本節將會進行詳細的探討。

4.1 初始化函數

2.2 小節演示了 MMKV-Kotlin 的初始化,因此其初始化函數是在 Android、iOS 兩個 source set 中分別定義與實現的。

先看看 Android:

import android.content.Context
import com.tencent.mmkv.MMKV
fun initialize(context: Context): String = MMKV.initialize(context)
fun initialize(context: Context, rootDir: String): String = MMKV.initialize(context, rootDir)
fun initialize(context: Context, loader: MMKV.LibLoader): String = MMKV.initialize(context, loader)
fun initialize(context: Context, logLevel: MMKVLogLevel): String = MMKV.initialize(context, logLevel.rawValue)
fun initialize(context: Context, rootDir: String, loader: MMKV.LibLoader): String = MMKV.initialize(context, rootDir, loader)
fun initialize(context: Context, rootDir: String, logLevel: MMKVLogLevel): String = MMKV.initialize(context, rootDir, logLevel.rawValue)
fun initialize(context: Context, loader: MMKV.LibLoader, logLevel: MMKVLogLevel): String = MMKV.initialize(context, loader, logLevel.rawValue)
fun initialize(context: Context, rootDir: String, loader: MMKV.LibLoader, logLevel: MMKVLogLevel): String = MMKV.initialize(context, rootDir, loader, logLevel.rawValue)

初始化函數的實現僅僅是調用 MMKV Java API 中的 initialize 函數。Android 平臺的初始化強依賴 Context 類型,還提供了 LibLoader 類型作為參數,用于在初始化時加載 so 庫。我們希望盡可能滿足 Android 平臺的各種需求,因此將 MMKV-Android 中的初始化 API 全部暴露出來。

再看看 iOS:

import cocoapods.MMKV.MMKV
fun initialize() = MMKV.initialize()
fun initialize(rootDir: String): String = MMKV.initializeMMKV(rootDir)
fun initialize(rootDir: String, logLevel: MMKVLogLevel): String = MMKV.initializeMMKV(rootDir, logLevel.rawValue)
fun initialize(rootDir: String, groupDir: String, logLevel: MMKVLogLevel): String = MMKV.initializeMMKV(rootDir, groupDir, logLevel.rawValue)

相比之下 iOS 平臺少了 Context 類型與 LibLoader 類型,因此初始化函數的重載要少很多。

4.2 MMKV 類型

在 MMKV 的 Java 與 Objective-C 版本中,MMKV 類型是具體 CRUD 功能的實現類。在 Java 版本中,寫函數為一系列 encode 重載函數或統一命名為 putXXX,其中 putXXX 內部調用了 encode 函數,二者只是返回類型不同,讀函數為統一命名為 decodeXXX 或 getXXX 的函數,二者行為一致 。而 Objective-C 版本中,寫函數統一命名為 setXXX 函數,讀函數統一命名為 getXXX 函數。雖然平臺不同,但是具有相同功能的函數的參數數量、類型,以及返回類型都高度統一。因此這給我們定義 common source set 中的 MMKV 類型帶來了便利。

我們需要在 common 層聲明 MMKV 類型(為避免同名帶來的混淆,我們將 common 層的 MMKV 類型命名為 MMKV_KMP),并且具體實現在各平臺的 source set 中,MMKV 類型的實例需要持有 Java 或 Objective-C 的 MMKV 類型的實例,并將 CURD 操作委托給它們。我們的實現方式有兩種可選:

  • MMKV_KMP 聲明為 class,通過 expect-actual 機制在平臺相關層編寫其實現。
  • MMKV_KMP 聲明為 interface,在平臺相關層編寫其實現類。

最終我們選擇了方案二,原因在于:在平臺相關的 source set 中編寫的具體實現 class 需要實例化時需要同時構建 Java/Objective-C 的 MMKV 實例,且最好的方式是在其構造函數作為參數傳入。而 Java 與 Objective-C 的 MMKV 是兩個完全沒有任何關系的獨立類型,因此我們在 common source set 中統一 MMKV_KMP 的構造函數非常不便。其次,在 MMKV 原本的設計中,MMKV 的實例本身也不是通過構造函數創建,而是通過一系列工廠方法創建,因此我們沒有必要在 common 層定義其構造函數。

確定基本設計后,我們看看 MMKV_KMP 的定義:

interface MMKV_KMP {
operator fun set(key: String, value: String): Boolean
operator fun set(key: String, value: Boolean): Boolean
fun takeString(key: String, default: String = ""): String
fun takeBoolean(key: String, default: Boolean = false): Boolean
fun close()
// More other functions and properties
}

雙平臺的實現如下,Android:

import com.tencent.mmkv.MMKV
class MMKVImpl internal constructor(internal val platformMMKV: MMKV) : MMKV_KMP {
override operator fun set(key: String, value: String): Boolean = platformMMKV.encode(key, value)
override operator fun set(key: String, value: Boolean): Boolean = platformMMKV.encode(key, value)

override fun takeString(key: String, default: String): String = platformMMKV.decodeString(key, default) ?: default
override fun takeBoolean(key: String, default: Boolean): Boolean = platformMMKV.decodeBool(key ,default)

override fun close() = platformMMKV.close()
// More other functions and properties

iOS:

import cocoapods.MMKV.MMKV
import platform.Foundation.NSSet

@Suppress("UNCHECKED_CAST")
class MMKVImpl internal constructor(internal val platformMMKV: MMKV) : MMKV_KMP {

override operator fun set(key: String, value: Int): Boolean = platformMMKV.setInt32(value, key)
override operator fun set(key: String, value: Boolean): Boolean = platformMMKV.setBool(value, key)

override fun takeString(key: String, default: String): String = platformMMKV.getStringForKey(key, default) ?: default
override fun takeBoolean(key: String, default: Boolean): Boolean = platformMMKV.getBoolForKey(key, default)

override fun close() = platformMMKV.close()

// More other functions and properties
}

最后是創建 MMKV_KMP 類型的工廠函數,我們只需通過 expect-actual 機制實現即可,這些工廠函數的返回類型都指定為 MMKV_KMP,在平臺 source set 中調用 Java 與 Objective-C 的對應工廠函數,得到 MMKV 實例后通過構造函數構建出 MMKVImpl 實例并返回即可。具體代碼在此省略,可在 Github 中查看。

4.3 平臺專屬 API

在 Kotlin/Native 中,Kotlin 基本類型以及 String 還有部分集合類型都可以映射到 Objective-C 中的對應類型。例如 Kotlin 的 String 可以與 Objective-C 的 NSString 互相映射,在編寫代碼時被認為是同一種類型。因此 common source set 中支持 CURD 的數據類型就是 MMKV-Android 與 MMKV-iOS 支持 CURD 類型的交集,包括:

? Boolean、Int、Long、Float、Double、String、UInt、ULong、ByteArray、Set<String>

其中要注意的點是,Kotlin 的 ByteArray 并不能與 Objective-C 的 NSData 直接映射,但二者可以通過手寫代碼轉換,因此在 iOS 中實現讀寫 ByteArray 也是基于這樣的手動轉換實現, 最終讀寫的還是 NSData。而 Set<String> 類型是 MMKV-Android 原本就支持的,但在 iOS source set 中則是通過讀寫 NSCoding 來實現的,Set<String> 可直接映射為 NSSet,而 NSSet 又是 NSCoding 協議的實現者。

除此之外,MMKV-Android 與 MMKV-iOS 還支持一些平臺特有的類型,例如 Android 額外支持 Parcelable 接口的實現者,而 iOS 額外支持 NSCoding 協議的實現者及 NSDate ,這些額外支持的類型都在平臺 source set 中通過擴展函數的方式提供,以便盡量完整保留 MMKV 原有的功能,并讓開發者可以在平臺 source set 中使用它們。

五、單元測試

單元測試是開源項目必不可少的組成部分,鑒于 MMKV-Kotlin 的 API 與 MMKV 本身大體相同,因此單元測試的設計也參考了 MMKV 的單元測試。

5.1 API 功能測試

Kotlin 提供了一套 kotlin-test 單元測試框架,可以在 common 與 iOS source set 中使用。而在 Android source set 我們仍使用 JUnit。通常情況下我們只需要在 common source set 編寫一套單元測試代碼,而平臺相關 source set 中甚至無需添加任何代碼即可完成單元測試的構建。框架在運行后會針對已添加的平臺分別運行測試。但在 MMKV-Kotlin 中 initialize 函數是分不同平臺實現的,因此我們采取將 API 測試的核心代碼放在 common,在 Android/iOS source set 初始化 MMKV 并構建測試。

Common 層的測試代碼就是針對 MMKV-Kotlin API 的測試,參考了 MMKV 的設計,簡單舉例如下:

class MMKVKotlinTest {

companion object {
const val KEY_NOT_EXIST = "Key_Not_Exist"
}

lateinit var mmkv: MMKV_KMP
private set

fun setUp() {
mmkv = mmkvWithID("unitTest", cryptKey = "UnitTestCryptKey")
}

fun testDown() {
mmkv.clearAll()
}

fun testBoolean() {
val result = mmkv.set("Boolean", true)
assertEquals(result, true)
val value0 = mmkv.takeBoolean("Boolean")
assertEquals(value0, true)
val value1 = mmkv.takeBoolean(KEY_NOT_EXIST)
assertEquals(value1, false)
val value2 = mmkv.takeBoolean(KEY_NOT_EXIST, true)
assertEquals(value2, true)
}

// Other type test......
}

setUp、testDown 分別負責 MMKV_KMP 的對象實例化及測試結束后的清理工作。針對每種具體數據類型的測試都獨立在 testXXX 函數內,針對正常寫讀、讀空值以及讀空值時默認值是否生效三種情況進行了測試。

我們在平臺 source set 中構建具體測試,并通過調用 common 層的測試代碼來完成測試,iOS 平臺的代碼簡單示例如下:

class MMKVKotlinTestIos {
private lateinit var mmkvTest: MMKVKotlinTest
@BeforeTest
fun setUp() {
initialize()
mmkvTest = MMKVKotlinTest().apply {
setUp()
}
}
@AfterTest
fun setDown() {
mmkvTest.testDown()
}
@Test
fun testCommon() = with(mmkvTest) {
testBoolean()
// Call other test functions
}

// Test NSDate and NSCoding......
}

我們通過注解構建測試,并調用 common 層的代碼執行具體測試,最后還需要編寫僅 iOS 平臺支持的 NSDate 與 NSCoding 類型的測試(代碼在上面的示例中省略),單元測試即構建完成。

5.2 Android 插樁測試

MMKV-Kotlin 純粹的單元測試在 Android 平臺是無法正常運行的,原因在于 Android 的單元測試并不支持包含原生二進制代碼的測試。前文提到過,MMKV core 是 C++ 編寫的,在 Android 平臺的構建產物為 so 庫。MMKV-Android 構建出的 aar 以及 MMKV-Kotlin 構建出的 aar 都包含了這個 so 庫。但該 so 庫是針對 Android 平臺的二進制程序,并不能在開發者常用的 Windows 或 Mac 電腦上運行。因此我們需要構建插樁測試(instrumented test)將我們的測試代碼打包成測試 APK 在真機上運行,測試類的代碼如下:

@RunWith(AndroidJUnit4ClassRunner::class)
@SmallTest
class MMKVKotlinTestAndroid {

private lateinit var mmkvTest: MMKVKotlinTest

@Before
fun setUp() {
val context = ApplicationProvider.getApplicationContext<Context>()
initialize(context)
mmkvTest = MMKVKotlinTest().apply {
setUp()
}
}

@After
fun setDown() {
mmkvTest.testDown()
}

@Test
fun testCommon() = with(mmkvTest) {
testBoolean()
// Call other test functions
}

// Test Parcelable......

@Test
fun testIPCUpdateInt() { ... }

@Test
fun testIPCLock() { ... }
}

測試的構建方式與 5.1 小節中 iOS 的構建方式并無二致。我們除了測試了通用類型及 Android 平臺特定的 Parcelable 外,還添加了對 Android 平臺跨進程訪問的測試,即 testIPCUpdateInt 與 testIPCLock 函數。為了完善跨進程測試,我們還需額外定義一個運行在其他進程的 Service(代碼見參考鏈接 4)。跨進程訪問測試的設計也完全參考了 MMKV。

在 Android Studio 中點擊“Make Project”(圖標為一個小錘子)右邊的下拉選項欄,然后點擊“Edit Configurations...”選項,在彈窗中點擊左上角的“+”然后選擇“Android Instrumented Test”,即可開始配置插樁測試。配置的截圖如下:

圖片

連接真機,然后運行即可。

六、Maven Central 發布

Maven Central 可謂是 Android 與 Java 技術領域內分發項目的關鍵一環,開源作者除了要將代碼開源到 Github 以外,通常還要將項目的構建產物發布至 Maven Central,以便于用戶以最便捷的方式集成開源庫。使用 Gradle 進行發布的常見流程如下:

  • 注冊 sonatype JIRA 賬號,登錄后提交一個 issue 用于注冊自己發布時會用到的 group id。
  • 本地安裝 GPG suit 后生成密鑰,然后上傳公鑰。
  • 在 Gradle 腳本中引入 maven-publish 與 signing plugin。
  • 編寫發布/簽名腳本,配置發布參數。
  • 執行 publish task。
  • 登錄 Nexus repository manager(后文簡稱 Nexus)處理發布申請。

發布成功后,用戶即可在 Gradle 以及 Maven 等構建工具中通過一行代碼導入你的開源庫。

我相信這個過程對于有 Maven 發布經驗的 Android 及 Java 開發者來說并不陌生。但對于 Kotlin Multiplatform 開發者來說,部分細節有所不同,且網上資料較少,這里會記錄一下踩坑記錄。

Kotlin Multiplatform 工程通常的發布方式是將所有構建產物統一發布,這其中包括 Android 平臺的 aar 文件,JVM 平臺的 jar 文件,Kotlin/Native 的構建產物 klib 文件等。例如一次 publish 后,Nexus 上發布的內容目錄結構如下:

圖片

我們可以看到共有 5 個目錄,其中 mmkv-kotlin 代表 common 層,通常 Multiplatform 工程只需要在 common source set 中對它添加依賴,即可在各平臺 source set 中自動獲取依賴。

而 mmkv-kotlin-android 代表 Android 平臺的產物,其內部的核心是個 aar 文件,與任何純粹的 Android 庫的結構沒有任何區別。由于 Android 在 Gradle 中本身就有完整的構建發布體系, 所以 Android aar 的發布需要手動配置發布的變體,例如(kts):

kotlin {
android {
publishLibraryVariants("release")
}
// ......
}

我們配置了只發布 release 變體,也可以同時傳入 "debug" 參數,將 debug 變體一同發布。

另外三個是 iOS 構建產物,分別對應:iphone 真機(iosarm64)、M1 & M2 芯片的 Mac 上的 iOS 模擬器(iossimulatorarm64)、Intel 芯片的 Mac 上的 iOS 模擬器(iosx64)。它們的核心都是 klib 文件,klib 是純 Kotlin 工程間互相引用的專用格式,例如 target 為 iOS 系統的純 Kotlin/Native 工程可以單獨添加對這幾個 iOS klib 的依賴,從而使用 MMKV-Kotlin。但考慮到 Kotlin/Native 在 iOS 單平臺開發中好像并不存在實際使用場景和需求,因此 MMKV-Kotlin 的文檔中并沒有將這幾個 klib 的依賴代碼列出。

最后看一下 Gradle 發布腳本(kts):

publishing {
publications.withType<MavenPublication> {
artifact(javadocJar)
with(pom) {
// pom setting......
}
}
repositories {
maven {
credentials {
username = NEXUS_USERNAME
password = NEXUS_PASSWORD
}
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2")
}
}
signing {
sign(publishing.publications)
}
}

在腳本中我們依次配置了 javadoc、pom 信息、倉庫信息(用戶名、密碼、上傳的地址)以及簽名。上述 kts 代碼添加到 gradle.build.kts 文件后,sync 項目,然后運行 publish Gradle task,即可完成發布。

最后有一個坑點需要注意,如果你不想將你的工程名稱作為 artifact id,則可以在 publications.withType<MavenPublication> { ... } 內進行配置并覆蓋,只需給 artifactId 屬性重新賦值即可。但目前實測,覆蓋該屬性后只有 multiplatform 與 iOS 的 artifact id 會發生改變,對 Android 無效(Gradle 7.2,Kotlin 1.6.10、1.6.21),Android 會始終使用工程名作為 artifact id。這個坑需要尤為注意,避免 Android 的 artifact id 與其他平臺皆不相同的情況出現。

七、總結與未來計劃

MMKV-Kotlin 利用了 Kotlin 在各原生平臺能夠與“土著語言”(Java、C、Objective-C,與 Swift 的交互正在開發中)直接交互的特性,將原本支持在多個平臺運行的 MMKV 移植到了 Kotlin Multiplatform 技術棧。為了讓原本 MMKV 用戶有較小的遷移學習成本,MMKV-Kotlin 的 API 與 MMKV 保持了高度一致性,但從避免重名等因素考量,部分 API 的命名做了一些改變。例如:MMKV 之于 MMKV_KMP, encode 之于 set 等等。MMKV-Kotlin 也盡量完整保留了 MMKV 平臺特有的特性,可以方便 Kotlin Multiplatform 開發者在平臺相關的 source set 中使用。此外,MMKV-Kotlin 也設計了與 MMKV 類似的單元測試,覆蓋了絕大部分核心 API,并在 Android 平臺上設計了插樁測試用以檢測多進程訪問的正確性。

Kotlin Multiplaform 與 MMKV 都不僅僅支持 Android/iOS 兩個平臺。起初,MMKV-Kotlin 只支持 Android 與 iOS 兩個移動端平臺,但在 1.1.1 版本中已經添加了對 macOS(包括 Intel 與 M1&M2 芯片架構)的支持。導入的方式為在 Kotlin/Native 工程的 Gradle 腳本(kts)中添加:

dependencies { 
// Intel 芯片
implementation("com.ctrip.flight.mmkv:mmkv-kotlin-macosx64:1.1.1")

// M1&M2 芯片
implementation("com.ctrip.flight.mmkv:mmkv-kotlin-macosarm64:1.1.1")
}

如果你的 Kotln/Native 工程是可執行程序,記得在 CocoaPods 中添加對 MMKV 的依賴,并添加對 MMKV 及 MMKVCore 的 link 配置,具體方式可參見 MMKV-Kotlin 的 README。

由于 macOS 版本的 MMKV 也通過 Objective-C 暴露 API,且也可以通過 CocoaPods 集成,因此添加 macOS 的支持只需在 Gradle 構建腳本中添加對應的 source set 即可,實現起來并不困難。其他 Apple 操作系統( watchOS、tvOS)MMKV 暫未直接支持,因此 MMKV-Kotlin 對它們的支持還在論證之中,如果可行,后續會將所有 Apple 平臺列入支持計劃之中。

由于 Win32、Linux 等平臺的 MMKV 通過 C++ 暴露 API,鑒于 Kotlin/Native 與 C++ 的互操作性不完善,以及 JetBrains 官方未來對 C++ 互操作性開發持消極態度(已經移出了 Kotlin roadmap),且目前 Kotlin 開發者對這兩個平臺的開發需求沒有那么迫切,因此暫不考慮列入支持計劃。

由于 MMKV 與 Kotlin 會時常更新版本,因此 MMKV-Kotlin 會緊隨二者進行迭代。若 MMKV 或 Kotlin 進行了升級,MMKV-Kotlin 未來都會進行跟進升級,請使用者確保 MMKV-Kotlin 依賴的 MMKV 或 Kotlin 版本與您使用的版本兼容。

后續攜程機票移動端研發團隊也會繼續深耕 Kotlin Multiplatform 技術領域,為整個技術社區帶來更多的干貨與貢獻。

責任編輯:未麗燕 來源: 攜程技術
相關推薦

2023-05-12 10:14:38

APP開發

2023-01-04 12:17:07

開源攜程

2025-06-24 09:44:41

2020-12-04 14:32:33

AndroidJetpackKotlin

2022-05-20 11:09:15

Flybirds多端測試UI 自動化測試

2022-05-13 09:27:55

Widget機票業務App

2017-04-11 15:11:52

ABtestABT變量法

2022-06-03 09:21:47

Svelte前端攜程

2021-08-20 11:00:04

Redis攜程數據庫

2022-06-10 08:35:06

項目數據庫攜程機票

2022-08-06 08:27:41

Trace系統機票前臺微服務架構

2023-06-06 16:01:00

Web優化

2017-04-11 15:34:41

機票前臺埋點

2022-08-12 08:38:08

攜程小程序Taro跨端解決方案

2017-03-15 17:38:19

互聯網

2023-08-25 09:51:21

前端開發

2023-11-13 11:27:58

攜程可視化

2025-06-24 09:51:47

2022-08-20 07:46:03

Dynamo攜程數據庫

2024-03-08 14:43:03

攜程技術系統
點贊
收藏

51CTO技術棧公眾號

日韩经典中文字幕| 一区二区三区国产| 国产精品一久久香蕉国产线看观看| 中文字幕高清视频| 456成人影院在线观看| 中文字幕精品一区二区精品绿巨人| 91精品视频网站| www.av麻豆| 成人婷婷网色偷偷亚洲男人的天堂| 7777精品伊人久久久大香线蕉完整版| 欧美一级中文字幕| 男女污视频在线观看| 免费日本视频一区| 欧美激情在线狂野欧美精品| 亚洲精品乱码久久久久久不卡 | 欧美日韩福利电影| 在线免费观看成年人视频| 祥仔av免费一区二区三区四区| 亚洲一区在线电影| 日本一区免费在线观看| 国产性生活毛片| 97视频在线观看免费| 中文在线字幕观看| 国产精品久久一区二区三区不卡| 麻豆精品视频在线观看免费| 欧美激情精品久久久久久蜜臀| 免费污网站在线观看| 视频二区欧美毛片免费观看| 日本精品一级二级| 极品粉嫩国产18尤物| 国产精品无码AV| 亚洲人人精品| 精品欧美一区二区在线观看| 黄色片在线免费| 猫咪在线永久网站| 懂色av一区二区三区免费观看| 国产成人精品网站| 日韩精品一卡二卡| 在线观看国产精品入口| 国产午夜一区二区| 国产麻豆xxxvideo实拍| 波多野结衣视频一区二区| 国产精品美女久久久久久| 精品亚洲欧美日韩| 亚洲成人一级片| 久国产精品韩国三级视频| 欧美亚洲视频一区二区| 国产性生活网站| 欧美在线不卡| 久久精品美女视频网站 | wwwwxxxxx欧美| 操人视频欧美| www.黄色av| 激情综合色播激情啊| 国产精品美女主播在线观看纯欲| 日韩不卡在线播放| 国产模特精品视频久久久久| 欧美激情精品久久久久久大尺度 | 国产精品黄色在线观看| 欧美高清性xxxxhd| 亚洲 欧美 激情 另类| 福利91精品一区二区三区| 欧美成人全部免费| 你懂得在线观看| 久久电影院7| 最新中文字幕亚洲| 天天综合网日韩| 日韩网站中文字幕| 欧美日韩一区二区电影| 91插插插插插插插插| 久久久加勒比| 91麻豆精品国产自产在线| caoporm在线视频| 久久伦理中文字幕| 精品国产成人系列| 无码人妻aⅴ一区二区三区 | 日本午夜一区二区| 国产精自产拍久久久久久| 夜夜爽8888| 国内精品久久久久影院薰衣草 | 欧美在线一区视频| 理论不卡电影大全神| 国产欧美中文在线| 亚洲欧美精品| 午夜成年人在线免费视频| 亚洲午夜视频在线观看| 1024av视频| 视频一区在线免费看| 欧美挠脚心视频网站| 国产精品久久久久9999爆乳| 91超碰在线| 91官网在线观看| 午夜免费福利网站| 高潮久久久久久久久久久久久久| 91成人在线免费观看| 欧美伦理片在线观看| 麻豆久久一区| 精品视频www| 亚洲午夜精品在线观看| 国产精品国产| 在线播放国产精品| 麻豆成人在线视频| 久久久久久久波多野高潮日日| 国产裸体写真av一区二区| 午夜精品久久久久久久爽| 95精品视频在线| 致1999电视剧免费观看策驰影院| a级片在线免费| 欧洲精品视频在线观看| 男人添女人荫蒂国产| 国产探花一区在线观看| 亚洲国产中文字幕久久网| 欧美熟妇激情一区二区三区| 杨幂一区二区三区免费看视频| 亚洲天堂久久av| 青青青在线视频| 全部av―极品视觉盛宴亚洲| 99国产精品久久久久老师| 国产在线视频网址| 一区二区高清视频在线观看| av丝袜天堂网| 久久99精品国产自在现线| 日韩最新中文字幕电影免费看| 日本午夜精品理论片a级app发布| 日韩一区精品字幕| 国内精品视频免费| 伊人在我在线看导航| 91成人看片片| 亚洲一区二区三区蜜桃| 一区二区亚洲| 欧美激情视频给我| 亚洲无码久久久久| 久久免费精品国产久精品久久久久| 国产精品久久成人免费观看| 三级成人在线| 精品呦交小u女在线| 久久免费黄色网址| 国产在线国偷精品产拍免费yy| 青青草成人激情在线| heyzo在线欧美播放| 日韩一级免费观看| 毛片视频免费播放| 日韩电影在线观看电影| 久久精品magnetxturnbtih| 在线免费av导航| 91精品国产综合久久福利软件| 国产在线免费av| 秋霞成人午夜伦在线观看| 欧美一区国产一区| 在线看片福利| 亚洲精品suv精品一区二区| 久久久久无码国产精品| 国产成人在线网站| 永久免费网站视频在线观看| 国产精品2区| 久久成人免费视频| 国产强伦人妻毛片| 亚洲三级小视频| 精品国产午夜福利在线观看| 97精品一区| 91久久精品美女高潮| 国产成人午夜| 日韩免费视频一区| 国产美女精品久久| 午夜亚洲视频| 欧美精品一区二区三区四区五区 | av中文字幕网址| 久久精品av| 成人中文字幕在线观看 | 久久久噜噜噜久久| 日本免费不卡视频| 久久久精品免费免费| 免费高清在线观看免费| 日韩黄色碟片| 久久精品亚洲一区| 99久久亚洲精品日本无码| 中文字幕一区二区三区在线不卡| 激情图片中文字幕| 国产精品草草| 久久大片网站| 午夜无码国产理论在线| 色婷婷久久一区二区| 99精品免费观看| 午夜伊人狠狠久久| 9.1成人看片| 免费成人av在线| 久久99国产精品一区| 福利在线一区| 国产成人精品999| 含羞草www国产在线视频| 日韩欧美国产午夜精品| 四虎成人永久免费视频| 国产精品久久777777| 佐佐木明希电影| 久久亚洲不卡| 久久天天东北熟女毛茸茸| 久久99精品国产自在现线| 国产精品女视频| 1769免费视频在线观看| 国产偷国产偷亚洲清高网站| 这里只有精品999| 亚洲国产视频网站| 538精品视频| 粉嫩13p一区二区三区| 北条麻妃在线视频| 好看不卡的中文字幕| 日韩精品国内| 精品午夜电影| 国产精品视频色| 欧美aa在线| 久久视频在线免费观看| 青春草在线观看| 日韩视频永久免费| 中文字幕久久熟女蜜桃| 91丨porny丨国产入口| www.色欧美| 99热免费精品在线观看| 国产高清免费在线| 国产欧美日韩在线观看视频| 97人人澡人人爽| 丁香久久综合| 日本最新高清不卡中文字幕| 羞羞网站在线免费观看| 尤物精品国产第一福利三区 | 亚洲精品wwwww| 国产精品久久久久久免费播放| 欧美日韩日本国产| 久久久久黄色片| 国产精品热久久久久夜色精品三区 | 二区三区在线观看| 亚洲色图第三页| 午夜成人鲁丝片午夜精品| 日韩欧美国产精品一区| 91尤物国产福利在线观看| 色哟哟国产精品| 国产欧美日韩另类| 亚洲国产aⅴ天堂久久| 成人在线观看高清| 一区免费观看视频| 免费一级suv好看的国产网站| 久久亚洲春色中文字幕久久久| 韩国三级视频在线观看| 国产精品一区二区在线观看网站| 亚洲一区二区三区四区中文| 日韩在线麻豆| 国产一区二区三区四区五区在线 | 成人精品999| 91亚洲精品久久久蜜桃| 怡红院一区二区| 成人ar影院免费观看视频| 人妻精油按摩bd高清中文字幕| 国产一区二区伦理片| 在线一区二区不卡| 极品少妇xxxx精品少妇| 亚洲精品久久久久久宅男| 蜜乳av一区二区三区| 男人舔女人下面高潮视频| 日韩电影在线一区二区三区| 国产精品无码一本二本三本色| 久久久蜜桃一区二区人| 99久久激情视频| 日韩av中文在线观看| av在线无限看| 久久成人av少妇免费| 国产无遮挡猛进猛出免费软件 | 午夜一区二区三区视频| 日本熟妇毛茸茸丰满| 精品久久久久久中文字幕一区奶水 | 色就是色欧美| 国产精品九九九九九九| 国产日韩影视精品| 国内自拍第二页| 国产一区二区三区免费观看| 中文字幕55页| 不卡一区中文字幕| 瑟瑟视频在线观看| 国产精品色哟哟| 亚洲国产精品免费在线观看| 一区2区3区在线看| 91video| 欧美日韩精品福利| 中文字幕超碰在线| 在线一区二区观看| 国产乱码精品一区二三区蜜臂 | 免费看成人哺乳视频网站| 日本高清视频一区二区三区 | 国产亚洲成精品久久| 69久久精品| 欧美黑人巨大精品一区二区| 美女视频在线免费| 成人国产在线视频| 超碰成人福利| 午夜精品视频在线观看一区二区| 欧美丝袜激情| 日本阿v视频在线观看| 免费中文字幕日韩欧美| 久久久久xxxx| 91亚洲国产成人精品一区二区三| 国产农村妇女精品一区| 亚洲成av人在线观看| 黄色大全在线观看| 精品久久久久一区二区国产| 男人天堂亚洲二区| 久久国产精品99国产精| 天堂中文在线播放| 亚洲永久免费观看| 国产精品午夜一区二区三区| 黄色影视在线观看| 久久综合九色综合欧美狠狠| 一级 黄 色 片一| 久久婷婷色综合| 久久香蕉精品视频| 欧美日韩国产成人在线免费| 污污视频在线免费看| 麻豆国产精品va在线观看不卡 | 国产剧情日韩欧美| 全国精品免费看| 天堂а√在线中文在线| 日本美女一区二区三区| 精品人妻一区二区免费视频| 综合精品久久久| 337p粉嫩色噜噜噜大肥臀| 欧美xxxx在线观看| 久久久久久久久免费视频| 午夜欧美视频在线观看| www.超碰97| 亚洲欧美偷拍三级| 中文在线资源天堂| 亚洲精品一区二区三区婷婷月 | 亚洲小说图片视频| 久操手机在线视频| 影音先锋日韩在线| 中文字幕有码av| 久久免费午夜影院| 久久99精品波多结衣一区| 日韩欧美有码在线| 男人天堂av网| 欧美第一黄网免费网站| 先锋影音网一区二区| 亚洲欧美日韩综合一区| 久久婷婷一区| 日韩中文字幕有码| 日本精品一区二区三区四区的功能| 天堂在线视频网站| 久久男人av资源网站| av日韩在线播放| 无码粉嫩虎白一线天在线观看| 国产成人综合视频| 精品在线视频观看| 亚洲成人激情在线| 久草免费在线视频| 久久精品日产第一区二区三区| 亚洲久久视频| 中国黄色a级片| 日韩欧美中文字幕在线观看| 青青色在线视频| 日本aⅴ大伊香蕉精品视频| 香蕉久久一区| 中文字幕综合在线观看| 国产又粗又猛又爽又黄91精品| 91久久久久久久久久久久久久| 这里只有精品电影| 肉肉视频在线观看| 精品国产乱码久久久久久蜜柚 | 97在线视频国产| 中日韩免视频上线全都免费| 日日碰狠狠丁香久燥| 国产精品久久久久影院亚瑟 | 欧美日韩精品在线观看视频| 日韩欧美的一区| 免费毛片b在线观看| 日本一区高清不卡| 国产麻豆91精品| 国产在线观看成人| 亚洲人成在线电影| 亚洲综合资源| 国产精品久久..4399| 久久久久99精品一区| 国产精品羞羞答答在线| 欧美高跟鞋交xxxxhd| 亚洲欧美tv| 午夜xxxxx| 午夜精品aaa| 欧美成人综合在线| 国产精品视频地址| 欧美成人日本| 成年人的黄色片| 欧美性受xxxx黑人xyx| 亚洲aⅴ乱码精品成人区| 国产精品美女呻吟| 重囗味另类老妇506070| 久久无码人妻精品一区二区三区 | 亚洲高清视频一区| 国产成人在线观看| 午夜精品三级久久久有码| 亚洲精品国产精品久久清纯直播| 成人在线爆射| 毛片av在线播放| 久久精品一区四区| 亚洲黄色一级大片|