多平臺(tái)開(kāi)發(fā)痛點(diǎn)
Kotlin Multiplatform最重要的目標(biāo)是在多平臺(tái)上共享代碼,現(xiàn)在支持的平臺(tái)有JVM,Android,Javascript,iOS、Linux、Windows、Mac等,幾乎覆蓋所有的平臺(tái)。設(shè)想下現(xiàn)在移動(dòng)優(yōu)先的策略,一個(gè)公司至少要做Android、iOS、WAP、小程序平臺(tái)。其中Data Model,接口調(diào)用,業(yè)務(wù)邏輯等這些代碼各個(gè)平臺(tái)都需要用不同的語(yǔ)言實(shí)現(xiàn)。這樣做了很多重復(fù)的工作,而且你需要招更多人,公司需要為更多人支付更多的薪水。

Kotlin Multiplatform簡(jiǎn)介

Kotlin Multiplatform并不是把你寫(xiě)的代碼在各個(gè)平臺(tái)上翻譯一遍,這樣做會(huì)有很多限制。各個(gè)平臺(tái)會(huì)有自己的一些平臺(tái)特性的功能。Kotlin Multiplatform能讓你共享盡可能多的代碼,但是也提供調(diào)用一些平臺(tái)特有的API(expect/actual語(yǔ)法)。這里我們可以看到我們使用Kotlin/JVM來(lái)生成安卓和后端的Java代碼,使用Kotlin/Native來(lái)生成Objective-C代碼給到iOS,使用Kotin/JS生成javascript代碼。
Common Module

你可以把多個(gè)平臺(tái)通用的代碼提取到Common Module,比如DTO、API調(diào)用,一些工具類(lèi)、還有業(yè)務(wù)邏輯。當(dāng)然你也可以直接使用一些已經(jīng)支持Multiplatform的第三方類(lèi)庫(kù):
- Kotlin Standard Library
- Ktor client 網(wǎng)絡(luò)接口調(diào)用(基于協(xié)程)
- Kotlin serialization 序列化
- Mockk Mock類(lèi)庫(kù)
- Kotlinx-io io類(lèi)庫(kù)
- Kotlinx-json json庫(kù)
支持多平臺(tái)的第三方類(lèi)庫(kù)現(xiàn)在也是迅猛發(fā)展,會(huì)有越來(lái)越多Java、Kotlin類(lèi)庫(kù)會(huì)轉(zhuǎn)成支持Multiplatform。
Demo
官方的demo可以看這里:http://kotlinlang.org/docs/tutorials/native/mpp-ios-android.html。怎么搭建環(huán)境創(chuàng)建項(xiàng)目這里就不詳細(xì)說(shuō)了:

Demo還是非常簡(jiǎn)單的,就是創(chuàng)建一個(gè)字符串,然后拼接上平臺(tái)返回的一段字符串。這里我們來(lái)做一個(gè)相對(duì)復(fù)雜一點(diǎn)的示例,比如我們要調(diào)用一個(gè)網(wǎng)絡(luò)接口,返回一個(gè)天氣信息的json string,然后我們把json string序列化成對(duì)象,然后在android iPhone界面上顯示。
SharedCode項(xiàng)目gradle配置
apply plugin: 'kotlin-multiplatform'
apply plugin: 'kotlinx-serialization'
kotlin {
targets {
final def iOSTarget = System.getenv('SDK_NAME')?.startsWith("iphoneos") \
? presets.iosArm64 : presets.iosX64
fromPreset(iOSTarget, 'ios') {
compilations.main.outputKinds('FRAMEWORK')
}
fromPreset(presets.jvm, 'android')
}
sourceSets {
commonMain{
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:$serialization_version"
}
}
androidMain {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:$serialization_version"
}
}
iosMain{
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:$serialization_version"
}
}
}
}
我們?cè)趖agets里面定義了兩種平臺(tái),分別是ios和android,當(dāng)然默認(rèn)的還有common模塊用來(lái)放共享的代碼。
在sourceSets里面我們定義了common, android, ios模塊的依賴(lài),這里我們依賴(lài)了kotlin標(biāo)準(zhǔn)庫(kù)和kotlinx-serialization庫(kù)。這里要注意下,同一個(gè)庫(kù)在不同平臺(tái)下依賴(lài)的artifactId可能會(huì)不一樣。
Show me code
common.kt
expect fun httpGet(url:String):String
fun <T> convertJsonToObject(jsonString: String, serializer:DeserializationStrategy<T>):T {
return JSON(strictMode = false).parse(serializer, jsonString)
}
fun getGuangZhouWeather():CityWeather {
val jsonString = httpGet("http://t.weather.sojson.com/api/weather/city/101280101")
println("jsonString: $jsonString")
return convertJsonToObject(jsonString, CityWeather.serializer())
}
@Serializable
data class CityWeather(
var time: String,
var date: String,
var message: String,
var status: String,
var cityInfo:CityInfo,
var data: WeatherData
)
@Serializable
data class WeatherData(
var shidu: String,
var pm25: String,
var pm10: String,
var quality: String,
var wendu: String,
var ganmao: String,
var yesterday:DayInfo,
var forecast:MutableList<DayInfo>
)
這段common.kt的代碼是在android和ios上共享的,注意第一行:
expect fun httpGet(url:String):String
我們用expect關(guān)鍵字定義了一個(gè)調(diào)用http請(qǐng)求的方法,傳入一個(gè)字符串類(lèi)型的url然后返回http response的字符串。這個(gè)expect關(guān)鍵字表示這個(gè)方法是需要每個(gè)平臺(tái)各自實(shí)現(xiàn)的。
convertJsonToObject方法是調(diào)用kotlinx-serialization跨平臺(tái)庫(kù)把json string序列化成對(duì)象。
getGuangZhouWeather方法是先調(diào)用網(wǎng)絡(luò)請(qǐng)求,然后把字符串序列化成CityWeather對(duì)象返回。
最后我們需要做的是在/androidMain/kotlin/actual.kt和/iosMain/kotlin/actual.kt文件實(shí)現(xiàn)在common.kt定義的httpGet方法。
Android實(shí)現(xiàn)
actual fun httpGet(url:String):String{
return URI(url).toURL().readText()
}
iOS實(shí)現(xiàn)
actual fun httpGet(url:String):String{
val urlWithString = NSURL.URLWithString(url)
if (urlWithString != null) {
val requestWithURL = NSMutableURLRequest.requestWithURL(urlWithString)
val response: CPointer<ObjCObjectVar<NSURLResponse?>>? = null
val error : CPointer<ObjCObjectVar<NSError?>>? = null
val nsData = NSURLConnection.sendSynchronousRequest(requestWithURL, response, error)?.copy() as NSData
println("nsData: $nsData, lenght: ${nsData.length}, desc: ${nsData.description}")
println("response: $response")
val string = NSString.stringWithCString(nsData.bytes() as CPointer<ByteVar>, encoding=NSUTF8StringEncoding)
if (string != null) {
return string
}
}
return ""
}
如果你玩過(guò)Objective-c,你一定對(duì)上面的iOS實(shí)現(xiàn)的代碼非常熟悉,這里的每個(gè)類(lèi)都跟Objecttive-c都能對(duì)應(yīng)上。實(shí)現(xiàn)項(xiàng)目可以通過(guò)寫(xiě)Kotlin代碼來(lái)Objective-C代碼。這就是Kotlin/Native的能力。

Build
在項(xiàng)目頂層指定gradlew命令,編譯項(xiàng)目。
./gradlew clean build

編譯完成之后你可以看到

運(yùn)行Android & iPhone App
調(diào)用其實(shí)也很簡(jiǎn)單,android 和 iphone調(diào)用getGuangZhouWeather方法,然后顯示溫度。

運(yùn)行iPhone的時(shí)候要注意,因?yàn)檫@里要調(diào)用http接口,所以你需要設(shè)置一下security settings,如下:

完整代碼請(qǐng)看:https://github.com/dengyin2000/mpp-iOS-Android
Reference: