携程机票 App Kotlin Multiplatform 初探 [复制链接]

2019-6-13 10:00
kengsirLi 阅读:838 评论:0 赞:1
Tag:  
作者: 陈琦 携程技术


从2017年9月到2019年5月,经过一年半的努力,携程机票App团队完成 90% 从 Native 到 Ctrip React Native (CRN) 的技术栈转型。


2019年初,我们开始思考下一步规划。


除?#24605;?#32493;做深 React Native 技术,更快更稳定的迭代交付机票业务需求,优化用户体验,我们需要从具体业务逻辑实现层次抽离出来,从一个完整的应用程序架构设计和实现的角度,寻找跨?#25945;?#25216;术的未来方向。


React Native 和 Flutter 这类大前端技术方案已经可以很好的支撑用户界面和组件,业务逻辑需求功能的实现,但是单线程动态脚本语言在以下领域仍显不足。


  • 灵活调用强大的?#25945;?厂商 API (AI, AR, mult-core GPU, ...)

  • 高?#38405;?#35745;算

  • 多线程处理

  • 后台任务

  • 低功耗


我们希望能够找到一种可靠的跨?#25945;ǎ?#21407;生,或能够与原生 API 进行灵活自由双向互操作的技术方案。经过一?#38382;?#38388;的针对 Kotlin 及相关开源社区的调研,观察,?#23548;琄otlin Multiplatform 技术在这方面展现出了良好的发展潜力。


一、Native multiplatform



传统主流的跨?#25945;?#21407;生方案是 C/C++,目前依然是最被广泛使用的。 React Native 和 Flutter 的底层实现也是如此。



Kotlin Multiplatform 的跨?#25945;?#36801;移如下图。



二、Kotlin Native



了解 Kotlin Multiplatform 需要先从 Kotlin Native 入手。相比 Kotlin/JVM,Kotlin Native 使用 Kotlin 语言编译器,配合 LLVM backend,将 Kotlin 代码编译为?#25945;?#21407;生二进制文件,不依赖虚拟机或运行时环境。当前 LLVM 版本 6.0.1 。官方正在将编译方案从 LLVM 的backend 转移到 frontend (clang) 。


目前已支持的?#25945;ǎ?/p>


  • iOS 9.0+ (arm32, arm64, x86_64 模拟器)

  • macOS (x86_64)

  • Android (arm32, arm64) ,编译生成 Linux SO 文件

  • Windows (mingw x86_64, x86)

  • Linux (x86_64, arm32, MIPS, MIPS little endian, Raspberry Pi)

  • WebAssembly (wasm32)


三、Kotlin Native 与 C 双向互操作



3.1 cinterop


Kotlin Native 官方附带工具,用于快速生成Kotlin与?#25945;–库互相调用操作所需的内容。


首先创建一个.def文件,描述需要包含在语言绑定的内容。


然后使用 cinterop 分析C头文件,?#25104;?#29983;成 Kotlin 语言的类型,函数和常量,完成Kotlin绑定。


最后通过LLVM编译器链接生成最终的可执行文件 *.kexe 或库文件 *.klib。


kexe 是?#25945;?#30456;关的可执行程序文件格式。


klib 是?#25945;?#30456;关的库文件格式,类似 JAR 的ZIP格式,?#38468;?#35814;见官网文档:https://kotlinlang.org/docs/reference/native/libraries.html#the-library-format


解压后的文件夹结构如下:


- foo/- targets/    - $platform/    - kotlin/        - Kotlin compiled to LLVM bitcode.    - native/        - Bitcode files of additional native objects.    - $another_platform/    - There can be several platform specific kotlin and native pairs.- linkdata/    - A set of ProtoBuf files with serialized linkage metadata.- resources/    - General resources such as images. (Not used yet).- manifest - A file in *java property* format describing the library.


3.2 ?#25945;?#24211;


大多数情况下,我们并不需要使用 cinterop 手动生成所有所需的C库绑定。


Kotlin Native SDK已经提供了大部?#21046;教?#30340;原生库绑定。例如:


  • Linux POSIX

  • Windows Win32

  • macOS/iOS Apple Framework, POSIX

  • 以?#26696;髕教?#30340;常用热门库,OpenGL, zlib 等


Kotlin Native 在本机开发时默认下载到 ~/.konan/ 文件夹,例如 ~/.konan/kotlin-native-macos-1.2.1/, ?#25945;?#24211;文件位于~/.konan/kotlin-native-macos-1.2.1/klib/platform/,?#23547;?#21547;以下内容,可见大部?#21046;教⊿DK都已预处理完成。


Android Native Arm32


?#25193;ぉ?android?#25193;ぉ?android_arm32.tree.txt?#25193;ぉ?builtin?#25193;ぉ?egl?#25193;ぉ?gles?#25193;ぉ?gles2?#25193;ぉ?gles3?#25193;ぉ?glesCommon?#25193;ぉ?linux?#25193;ぉ?media?#25193;ぉ?omxal?#25193;ぉ?posix?#25193;ぉ?sles└── zlib

13 directories, 1 file


iOS Arm64


?#25193;ぉ?ARKit?#25193;ぉ?AVFoundation?#25193;ぉ?AVKit?#25193;ぉ?Accelerate?#25193;ぉ?Accounts?#25193;ぉ?AdSupport?#25193;ぉ?AddressBook?#25193;ぉ?AddressBookUI?#25193;ぉ?AssetsLibrary?#25193;ぉ?AudioToolbox?#25193;ぉ?AuthenticationServices?#25193;ぉ?BusinessChat?#25193;ぉ?CFNetwork?#25193;ぉ?CallKit?#25193;ぉ?CarPlay?#25193;ぉ?ClassKit?#25193;ぉ?CloudKit?#25193;ぉ?CommonCrypto?#25193;ぉ?Contacts?#25193;ぉ?ContactsUI?#25193;ぉ?CoreAudio?#25193;ぉ?CoreAudioKit?#25193;ぉ?CoreBluetooth?#25193;ぉ?CoreData?#25193;ぉ?CoreFoundation?#25193;ぉ?CoreGraphics?#25193;ぉ?CoreImage?#25193;ぉ?CoreLocation?#25193;ぉ?CoreMIDI?#25193;ぉ?CoreML?#25193;ぉ?CoreMedia?#25193;ぉ?CoreMotion?#25193;ぉ?CoreNFC?#25193;ぉ?CoreServices?#25193;ぉ?CoreSpotlight?#25193;ぉ?CoreTelephony?#25193;ぉ?CoreText?#25193;ぉ?CoreVideo?#25193;ぉ?DeviceCheck?#25193;ぉ?EAGL?#25193;ぉ?EventKit?#25193;ぉ?EventKitUI?#25193;ぉ?ExternalAccessory?#25193;ぉ?FileProvider?#25193;ぉ?FileProviderUI?#25193;ぉ?Foundation?#25193;ぉ?GLKit?#25193;ぉ?GSS?#25193;ぉ?GameController?#25193;ぉ?GameKit?#25193;ぉ?GameplayKit?#25193;ぉ?HealthKit?#25193;ぉ?HealthKitUI?#25193;ぉ?HomeKit?#25193;ぉ?IOSurface?#25193;ぉ?IdentityLookup?#25193;ぉ?IdentityLookupUI?#25193;ぉ?ImageIO?#25193;ぉ?Intents?#25193;ぉ?IntentsUI?#25193;ぉ?LocalAuthentication?#25193;ぉ?MapKit?#25193;ぉ?MediaAccessibility?#25193;ぉ?MediaPlayer?#25193;ぉ?MediaToolbox?#25193;ぉ?MessageUI?#25193;ぉ?Messages?#25193;ぉ?Metal?#25193;ぉ?MetalKit?#25193;ぉ?MetalPerformanceShaders?#25193;ぉ?MobileCoreServices?#25193;ぉ?ModelIO?#25193;ぉ?MultipeerConnectivity?#25193;ぉ?NaturalLanguage?#25193;ぉ?Network?#25193;ぉ?NetworkExtension?#25193;ぉ?NewsstandKit?#25193;ぉ?NotificationCenter?#25193;ぉ?OpenAL?#25193;ぉ?OpenGLES?#25193;ぉ?OpenGLES2?#25193;ぉ?OpenGLES3?#25193;ぉ?OpenGLESCommon?#25193;ぉ?PDFKit?#25193;ぉ?PassKit?#25193;ぉ?Photos?#25193;ぉ?PhotosUI?#25193;ぉ?PushKit?#25193;ぉ?QuartzCore?#25193;ぉ?QuickLook?#25193;ぉ?ReplayKit?#25193;ぉ?SafariServices?#25193;ぉ?SceneKit?#25193;ぉ?Security?#25193;ぉ?Social?#25193;ぉ?Speech?#25193;ぉ?SpriteKit?#25193;ぉ?StoreKit?#25193;ぉ?SystemConfiguration?#25193;ぉ?Twitter?#25193;ぉ?UIKit?#25193;ぉ?UserNotifications?#25193;ぉ?UserNotificationsUI?#25193;ぉ?VideoSubscriberAccount?#25193;ぉ?VideoToolbox?#25193;ぉ?Vision?#25193;ぉ?WatchConnectivity?#25193;ぉ?WatchKit?#25193;ぉ?WebKit?#25193;ぉ?builtin?#25193;ぉ?darwin?#25193;ぉ?iAd?#25193;ぉ?iconv?#25193;ぉ?ios_arm64.tree.txt?#25193;ぉ?objc?#25193;ぉ?posix└── zlib

116 directories, 1 file


四、Kotlin Native 与 Swift/Objective-C 双向互操作



基于cinteroop,增加了面向对象的?#25104;洹O附?#35814;见官网文档:https://kotlinlang.org/docs/reference/native/objc_interop.html#mappings



五、Kotlin Multiplatform



Kotlin 1.3 重新设计了多?#25945;?#24037;程项?#32771;?#26500;,以提高工程结构的灵活性和扩展性,更容易共享?#20174;肒otlin代码。


Kotlin Native 变成 Kotlin Multiplatfrom 的目标?#25945;?#20043;一, 相关库和插件转为内部实现。


例如对于应用程序开发人员使用的Gradle插件,从org.jetbrains.kotlin.konan 变更为 org.jetbrains.kotlin.multiplatform。


konan 变成 multiplatform 内部引用的依赖库,创建Kotlin Gradle工程时后台自动下载并保持在本机 ~/.konan/ 文件夹。


为?#24605;?#23569;开发人员的误解,对外提供的SDK版本号都统一跟随Kotlin语言版本号。


例如,Kotlin语言最新版本是1.3.31,各?#25945;?#24211;SDK版本号也一样,而 kotlin native macos 1.2.1 仅用于Kotlin内部开发人员的版本Tag,对使用者透明,我们无需关心。


因此网上搜索得到的大部分基于konan的文?#38470;?#31243;和GitHub源码均已过时,需留意gradle配置中是否基于multiplatform plugin。包括官网部分文档。


IntelliJ IDEA 提供了 Kotlin Mulitplatform的工程模版。?#23548;?#19978;IDEA + Android SDK 可以替代Android Studio 99%的开发工作。针对Native?#25945;?#26377;4种模版,大同小异,区别仅是Gradle Modue结构略有不同。



1)Kotlin/Native 模版是针对单一?#25945;?#30340;最小化工程模版。


2)Kotlin (Mobile Android/iOS) 模版沿用Android工程的默认结构,将Android主工程,Kotlin Common代码集放在root/app/src,根目录额外增加了一个iOS主工程文件夹。


3)Kotlin (Multiplatform Library) 和 Kotlin (Mobile Shared Library)  非常相似,可以简单的认为后者是前者的子集。


前者包含Kotlin/JS和3个Native (macOS,Windows,Linux) ?#25945;ā?/p>


后者仅包含Android,iOS 2个?#25945;ā?/p>


其中仅有一处细微差异。前者jvmMain模块依赖stdlib-jdk8,后者jvmMain模块依赖stdlib。即前者JVM运行环境是Java服务端,后者JVM运行环境是Android设备。


4)Kotlin(Mobile Shared Library) 是最简结构,文件夹如下。


?#25193;ぉ?build.gradle?#25193;ぉ?gradle│   └── wrapper│       └── gradle-wrapper.properties?#25193;ぉ?gradle.properties?#25193;ぉ?settings.gradle└── src    ?#25193;ぉ?commonMain    │   ?#25193;ぉ?kotlin    │   │   └── sample    │   │       └── Sample.kt    │   └── resources    ?#25193;ぉ?commonTest    │   ?#25193;ぉ?kotlin    │   │   └── sample    │   │       └── SampleTests.kt    │   └── resources    ?#25193;ぉ?iosMain    │   ?#25193;ぉ?kotlin    │   │   └── sample    │   │       └── SampleIos.kt    │   └── resources    ?#25193;ぉ?iosTest    │   ?#25193;ぉ?kotlin    │   │   └── sample    │   │       └── SampleTestsNative.kt    │   └── resources    ?#25193;ぉ?jvmMain    │   ?#25193;ぉ?kotlin    │   │   └── sample    │   │       └── SampleJvm.kt    │   └── resources    └── jvmTest        ?#25193;ぉ?kotlin        │   └── sample        │       └── SampleTestsJVM.kt        └── resources

27 directories, 10 files


接下来我们需要首先解决一些?#25945;?#30456;关的上手问题,结合一些简单且?#23548;?#23384;在的小场景,探索Kotlin Multiplatform的?#23548;?#29616;状。


六、场景1:Logger



打印输出日志是任何?#25945;?#21644;技术栈的开发人员每天面对的简单且必需的功能。我们首先看看各?#25945;?#30340;基础方案。大致都是定义好日志级别,提供出全局单例或静态方法API。


6.1 Java Logger


Since Java 1.4


import java.util.logging.Logger

private val logger = Logger.getLogger("Module")

fun javaLog() { logger.fine("Fine Msg") logger.config("Config Msg") logger.info("Info Msg") logger.warning("Warning Msg") logger.severe("Error Msg")}


6.2 Android Log


Since API Level 1


import android.util.Log

private val tag = "Module"

fun androidLog() { Log.v(tag, "Verbose Msg") Log.d(tag, "Debug Msg") Log.i(tag, "Info Msg") Log.w(tag, "Warning Msg") Log.e(tag, "Error Msg")}


?#23548;?#22312;Android日常开发中,我个人更倾向于使用Java Logger,相比Android.util.Log,Java Logger可以通过注册 ConsoleHandler, FileHandler, SockeHandler, StreamHandler,配合 SimpleFormatter, XMLFormatter,将日志转存到手机本地文件或后端服务器,供后续分析,以及每一行日志代码可以少写一个Tag?#38382;?br style="margin: 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;">


6.3 iOS os_log


忽略 C print 和 Objective-C NSLog,仅看 iOS 10提供的 unified logging system。


void iOSLog(){    os_log(OS_LOG_DEFAULT, "Msg");    os_log_fault(OS_LOG_DEFAULT, "Fault Msg");    os_log_error(OS_LOG_DEFAULT, "Error Msg");    os_log_info(OS_LOG_DEFAULT, "Info Msg");    os_log_debug(OS_LOG_DEFAULT, "Debug Msg");    os_log_with_type(OS_LOG_DEFAULT, OS_LOG_TYPE_DEFAULT, "Msg with type");}


6.4 Kotlin Log - Common Expect


参考 android.util.Log,复制一份 Kotlin Common 模块的声明。


跨?#25945;?#20844;共模块的实现,除了使用常规的 Interface/Implementation 方案,Kotlin 提供了 expect/actual 声明语法。


这里使用 expect 关键字声明一个单例Log对象的预期。其类成员方法等同于Java的纯虚方法。


/** * Keep consistent with android.utl.log constant level. * */enum class Level(val value: Int) {    VERBOSE(2),    DEBUG(3),    INFO(4),    WARN(5),    ERROR(6),    ASSERT(7)}

expect object Log { fun isLoggable(tag: String, level: Level): Boolean fun v(tag: String, msg: String) fun d(tag: String, msg: String) fun i(tag: String, msg: String) fun w(tag: String, msg: String) fun e(tag: String, msg: String) fun wtf(tag: String, msg: String)}


6.5 Kotlin Log - Android Actual


Android?#25945;?#30340;实现直接绑定android.util.Log。


// Kotlin Android Implementationactual object Log {    actual fun isLoggable(tag: String, level: Level): Boolean = android.util.Log.isLoggable(tag, level.value)    actual fun v(tag: String, msg: String) { android.util.Log.v(tag, msg) }    actual fun d(tag: String, msg: String) { android.util.Log.d(tag, msg) }    actual fun i(tag: String, msg: String) { android.util.Log.i(tag, msg) }    actual fun w(tag: String, msg: String) { android.util.Log.w(tag, msg) }    actual fun e(tag: String, msg: String) { android.util.Log.e(tag, msg) }    actual fun wtf(tag: String, msg: String) { android.util.Log.wtf(tag, msg) }}


6.6 Kotlin Log - iOS Actual


由于cinterop工具仅处理C库的实例和函数的绑定,不能实现 macro宏定义的绑定。而os_log()提供的常用API?#23548;?#26159; macro宏定义,所以我们需要找到其内部?#23548;实?#29992;的函数 _os_log_internal()。这对新手可能是一个小坑,在未详细了解?#25945;ˋPI的情况下,在Kotlin?#25945;?#24211;中费时费力查找绑定方法无果。


// <os/log.h> declaration#define os_log(log, format, ...) \        os_log_with_type(log, OS_LOG_TYPE_DEFAULT, format, ##__VA_ARGS__)

#define os_log_with_type(log, type, format, ...) __extension__({ \ _Pragma("clang diagnostic push") \ _Pragma("clang diagnostic error \"-Wformat\"") \ _Static_assert(__builtin_constant_p(format), "format argument must be a string constant"); \ _os_log_internal(&__dso_handle, log, type, format, ##__VA_ARGS__); \ _Pragma("clang diagnostic pop") \})


_os_log_internal() 的第一个?#38382;?__dso_handle 定义如下。这里有趣了,我们需要调用它的内存地?#20998;?#38024;,kotlinx.cinterop.ptr就是为此而生。


// <os/trace_base.h> declarationextern struct mach_header __dso_handle;


下面这?#38382;?#29616;是纯Kotlin语?#28304;?#30721;,是上文中 Kotlin Common 的Log单例对象 expect 声明对应的 actual实现。调用 Kotlin Native iOS?#25945;?#24211;已提供好的 os_log API绑定。


// Kotlin iOS Implementationimport kotlinx.cinterop.ptrimport platform.darwin.*

actual object Log { actual fun isLoggable(tag: String, level: Level): Boolean = os_log_type_enabled(OS_LOG_DEFAULT, level.toPlatform()) actual fun v(tag: String, msg: String) = _os_log_internal(__dso_handle.ptr, OS_LOG_DEFAULT, OS_LOG_TYPE_DEFAULT, "$tag | $msg") actual fun d(tag: String, msg: String) = _os_log_internal(__dso_handle.ptr, OS_LOG_DEFAULT, OS_LOG_TYPE_DEBUG, "$tag | $msg") actual fun i(tag: String, msg: String) = _os_log_internal(__dso_handle.ptr, OS_LOG_DEFAULT, OS_LOG_TYPE_INFO, "$tag | $msg") actual fun w(tag: String, msg: String) = _os_log_internal(__dso_handle.ptr, OS_LOG_DEFAULT, OS_LOG_TYPE_INFO, "$tag | $msg") actual fun e(tag: String, msg: String) = _os_log_internal(__dso_handle.ptr, OS_LOG_DEFAULT, OS_LOG_TYPE_ERROR, "$tag | $msg") actual fun wtf(tag: String, msg: String) = _os_log_internal(__dso_handle.ptr, OS_LOG_DEFAULT, OS_LOG_TYPE_FAULT, "$tag | $msg")}


6.7 Kotlin Log - AndroidNativeArm Actual


大部分业务场景中,我们使用Kotlin/JVM实现Android?#25945;?#30340;功能,Kotlin Native for AndroidNativeArm32/64 可用但仍不好用。我们简单看看其实现。


与iOS Native类似,只需在build.gradle文件中添加相应Target。这里使用了Gradle Kotlin DSL新版本。使用Kotlin编写Gradle配置文件的体验比Groovy更佳,IDE支持语法高亮,自动补全,代码跳转,编译提示等便捷功能。


androidNativeArm32() {        binaries {            sharedLib()// or staticLib() or executable()        }    }


Android NDK Log API


/** * Writes the constant string `text` to the log, with priority `prio` and tag * `tag`. */int __android_log_write(int prio, const char* tag, const char* text);

/** * Writes a formatted string to the log, with priority `prio` and tag `tag`. * The details of formatting are the same as for * [printf(3)](http://man7.org/linux/man-pages/man3/printf.3.html). */int __android_log_print(int prio, const char* tag, const char* fmt, ...)#if defined(__GNUC__) __attribute__((__format__(printf, 3, 4)))#endif ;


继续纯Kotlin语言实现。


// Kotlin AndroidNativeArm Implementationimport platform.android.*

@kotlin.ExperimentalUnsignedTypesactual object Log { actual fun isLoggable(tag: String, level: Level): Boolean = level >= Level.INFO

actual fun v(tag: String, msg: String) { __android_log_write(ANDROID_LOG_VERBOSE.toInt(), tag, msg) } actual fun d(tag: String, msg: String) { __android_log_write(ANDROID_LOG_DEBUG.toInt(), tag, msg) } actual fun i(tag: String, msg: String) { __android_log_write(ANDROID_LOG_INFO.toInt(), tag, msg) } actual fun w(tag: String, msg: String) { __android_log_write(ANDROID_LOG_WARN.toInt(), tag, msg) } actual fun e(tag: String, msg: String) { __android_log_write(ANDROID_LOG_ERROR.toInt(), tag, msg) } actual fun wtf(tag: String, msg: String) { __android_log_write(ANDROID_LOG_FATAL.toInt(), tag, msg) }}


以上Demo源码工程,详见:https://github.com/9468305/log-kotlin


?#23548;?#29983;产环境使用,推荐 Jake Wharton's Timber:https://github.com/JakeWharton/timber


七、场景2:IO File



本地文件读写相比打印输出日志略复杂但也很常用。Android/JVM 和 Java 服务端的 IO File 技术方案一致但场景选型不同。


  • Java IO (Blocking IO)

    Default IO Streaming.

  • Java NIO (Non-Blocking IO)

    Since 1.4.

  • Java NIO2 (Asynchronous I/O, AIO)

    Since 7, Enhancements in 8.


移动端常用 Blocking IO,一方面是因为该方案适合嵌入式?#25945;ǎ?#21478;一方面是因为Android?#20302;?#29256;本对JDK高版本的支持更?#38470;够?#24930;。长期以来我们需要兼容JDK 6,最低?#20302;?#29256;本升级至Android 4.4(API Level 19)以上才能够兼容JDK 7,Android 8.0(API Level 26)才升级至JDK 8,并且仅支持部分功能API。


Android NIO?#23548;使?#27867;应用于网络组件的实现,例如Google Guava,Square OKHttp。


iOS File 就是C Posix 使用方式,这里不再赘述。


下面这段代码使用纯Kotlin语言调用iOS?#25945;≒OSIX File API。memScoped{}表示该作用域内的申请的内存空间,当离开作用域后,会被自动?#22836;擰?#36825;是Kotlin Native不依赖JVM GC的内存管理方式,即ARC自动引用计数。


fun sample() {    val file = fopen(__filename = "filename", __mode = "r")    if (file != null) {        try {            memScoped { // ARC - Automatic Reference Counting                val bufferLength = 1024                val buffer = allocArray<ByteVar>(bufferLength)                while (true) {                    val line = fgets(buffer, bufferLength, file)?.toKString()                    if (line == null || line.isEmpty())                        break                    println(line)                }            }        } finally {            fclose(file)        }    }}


那么如何Kotlin Multiplatform实现 java.io.file 与 C POSIX File API的统一?我们看看官方现状。


Package kotlin.io for native 仅有3个方法。


// Prints the given message to the standard output stream.fun print()// Prints the given message and the line separator to the standard output stream.fun println()// Reads a line of input from the standard input stream.fun readLine(): String?


Package kotlinx.io 基于NIO方案实现,目前?#28304;?#20110;Experimental阶段,官方建议配合 kotlinx.coroutines, kotlinx.atomicfu 一起使用,尚未支持Native?#25945;ā?br style="margin: 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;">


所以目前我们只能自己实现双?#25945;?#30340;统一封装。这部分实现并不难,可参考 OpenJDK 和 AOSP源码。Java File底层实现原理也是通过 JNI 调用 C POSIX。Android 源码部分改写了OpenJDK的实现。具体?#38468;?#35814;见Android SDK FileInputStream/FileOutputSteam源码。


另外 Okio 2 正在进行迁移至Kotlin和支持多?#25945;ǎ瑂quare团队的最终目标是将Retrofit和OkHttp运行在多?#25945;ā?#35814;见:

https://github.com/square/okio/issues/370


八、场景3: SQLite



嵌入式?#25945;?#20027;流关系型数据存储方案。


1、Android SQLiteOpenHelper


  • Android SDK 默认提供的SQLite方案。

  • SQLite low-level API

  • Raw SQL queries

  • 使用比较繁琐


2、Android Jetpack Room


  • Jetpack 新组件。

  • SQLite 之上的 ORM 抽象层。


3、iOS SQLite library


  • 相比 Android,iOS 更接近原始 SQLite C 库。


SQLDelight


https://github.com/square/sqldelight


目前最成熟稳定的 Kotlin 多?#25945;?SQLite解决方案。作者 Alec Strong, Jake Wharton(又见大神)。不论是Android Java 开发,还是Kotlin多?#25945;?#24320;发,我都建议大家了解一下它。


它的思路非常有趣,与Room为代表的各种ORM方案截然相反。它是从SQL查询语句生成代码,而不是从代码生成SQL查询。这里不展开介绍,直接放上Jake Wharton关于SQLDelight vs Room的评论原文。


In my opinion, Room exists at the wrong level of abstraction.


The reason Retrofit and Gson/Moshi/etc. are successful is because there's nothing from which to generate the interfaces and model objects so you write both by hand duplicating an implicit contract. If you switch to protocol buffers, you stop writing models by hand–the tool can generate those. If you switch to gRPC or Swagger, you stop writing Retrofit interfaces by hand–a tool can generate those. When you have an explicit source of schema you no longer need to duplicate that schema by hand in code.


SQL table definitions and queries are a schema. They can define the types and names of both the model objects and the interface through which you interact with queries. Thus, it doesn't really make sense to force the user to duplicate that schema by writing the model objects and interface by hand. You wouldn't do it with protobuf and gRPC. Why are you doing it with your database?


Room is an okay choice. It's far better than all the ORMs people have been using for years. Alec and I have given talks where the conclusion was that we don't care which you choose, just don't choose an ORM (https://youtu.be/4eUuD7LsqMs).


That being said, it's hard not to see SQLDelight as a step up from Room. It validates more. It has better tooling. It generates Kotlin. And it's multiplatform. Yes, I'm biased, but Alec can tell you how long we spent evaluating what the right level of abstraction is and what the right developer UX is. It's a pleasant experience writing only SQL and having SQLDelight generate the interfaces and model objects that you'd otherwise be forced to write by hand with Room. And when you do that, you also stop (ab)using star selects in your queries and selecting only the minimal amount of columns necessary rather than selecting entire tables so you can reuse entities.


You write the SQL query and you write the method signature. Generating the method signature is a pure function from the SQL query. You have to keep both in sync manually instead of having the method generated automatically based on the SQL bind args and selected columns. SQLDelight generates the interface methods that Room makes you write given the same SQL command.


Similarly, when you write a query that returns results, Room forces you to write a model object which conforms to the selected data. SQLDelight generates the model objects that Room makes you write given the same SQL query.


In summary, you can take all the SQL you're already using with Room, delete all of the interfaces and model objects you had to write manually, and SQLDelight will generate them for you (along with some extra validation that they're correct).


九、The Future



Kotlin解决方案组件的稳定性和进展,详见:

https://kotlinlang.org/docs/reference/evolution/components-stability.html


Kotlin Native 处于 Additions in Incremental Releases (AIR) 阶段。


Multiplatform Projects 处于 Moving fast (MF) 阶段。


?#23433;?#20037;的Google IO 2019大会?#24076;琄otlin语言在Android?#25945;?#30340;地位进一步上升。Android Jetpack 系列组件优先支持Kotlin。Square的Okio 2,OkHttp 4.0 正在迁移Kotlin并支持多?#25945;ā?/p>


所以我相信 Kotlin Multiplatform 的未来充满想象力。


十、One more thing



https://gradle.org/kotlin/


Gradle 5.0 已发布 Kotlin DSL v1.0 稳定版,建议尽早迁移 Gradle工程至 KTS 版本。


我来说两句
您需要登录后才可以评论 登录 | 立即注册
facelist
所有评论(0)
领先的中文移动开发者社区
18620764416
7*24全天服务
意见反馈:[email protected]

扫一扫关注我们

Powered by Discuz! X3.2© 2001-2019 Comsenz Inc.( 粤ICP备15117877号 )

阿拉斯加垂钓APP下载