原文作者:Aman Bansal 原文地址:Create Hello World App with KMM 📱- Android & IOS 译者:秉心说
在移动开发领域,Android 和 iOS 版本的应用程序通常会有很多共同点,背后的业务逻辑基本也是一致的。文件下载,读写数据库,从远程服务器获取数据,解析远程数据等等。所以我们为什么不只写一次业务逻辑代码,在不同的平台上共享呢?
有了这个想法之后,Jetbrains 带来了 Kotlin Multiplatform Project 。
Kotlin Multiplatform Mobile (KMM) 是由 Jetbrains 提供的跨平台移动开发 SDK 。借助 Kotlin 的 跨平台能力,你可以使用一个工程为多个平台编译。
使用 KMM,具备灵活性的同时也保留了原生编程的优势。为 Android/iOS 应用程序的业务逻辑代码使用单一的代码库,仅在需要的时候编写平台特定代码,例如实现原生的 UI,使用平台特定 API 等等。
KMM 可以和你的工程无缝集成。共享代码,使用 Kotlin 编写,使用 Kotlin/JVM 编译成 JVM 字节码,使用 Kotlin/Native 编译成二进制,所以你可以和使用其他一般类库一样使用 KMM 业务逻辑模块。
在写这篇博客的同时,KMM 仍然处于 Alpha,你可以开始尝试在你的应用中共享业务逻辑代码。
在移动开发领域,KMM 目前没有为大众所熟知。Jetbrains 开发了 Android Studio 的 KMM 插件 来帮助你快速设置 KMM 工程。插件还可以帮助你编写,运行,测试共享代码。
现在,你需要等待工程的第一次构建,需要花费一些时间去下载和设置必要的组件。
译者注:KMM 插件要求你的 Kotlin 插件版本至少为 4.0 版本以上
在菜单栏选择你要运行的平台,选择设备,点击 Run
要运行 iOS 应用,你需要安装 Xcode 和模拟器。
Android 开发者? 看起来很熟悉?😎
IOS 开发者? 看起来就像外星人?👽
Project 的 build.gradle.kts 文件:
buildscript {
repositories {
gradlePluginPortal()
jcenter()
google()
mavenCentral()
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10")
classpath("com.android.tools.build:gradle:4.0.1")
}
}
group = "com.aman.helloworldkmm"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
shared 模块包含了Android 和 iOS 的公用代码。但是,为了在 Android/iOS 上实现同样的逻辑,有时候你不得不写两份版本特定代码,例如蓝牙,Wifi 等等。为了处理这种情况,Kotlin 提供了 expect/actual 机制。shared 模块的源代码按三个源集进行分类:
commonMain
下存储为所有平台工作的代码,包括 expect
声明androidMain
下存储 Android 的特定代码,包括 actual
实现iosMain
下存储 iOS 的特定代码,包括 actual
实现每一个源集都有自己的依赖,Kotlin 标准库依赖会自动添加到所有源集,你不需要在编译脚本中声明。
build.gradle.kts
这份 build.gradle.kts 文件包含了 shared 模块对于 Android/iOS 的配置。
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
plugins {
kotlin("multiplatform")
id("com.android.library")
id("kotlin-android-extensions")
}
group = "com.aman.helloworldkmm"
version = "1.0-SNAPSHOT"
repositories {
gradlePluginPortal()
google()
jcenter()
mavenCentral()
}
kotlin {
android()
ios {
binaries {
framework {
baseName = "shared"
}
}
}
sourceSets {
val commonMain by getting
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
val androidMain by getting {
dependencies {
implementation("com.google.android.material:material:1.2.0")
}
}
val androidTest by getting {
dependencies {
implementation(kotlin("test-junit"))
implementation("junit:junit:4.12")
}
}
val iosMain by getting
val iosTest by getting
}
}
android {
compileSdkVersion(29)
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdkVersion(24)
targetSdkVersion(29)
versionCode = 1
versionName = "1.0"
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
}
}
}
val packForXcode by tasks.creating(Sync::class) {
group = "build"
val mode = System.getenv("CONFIGURATION") ?: "DEBUG"
val sdkName = System.getenv("SDK_NAME") ?: "iphonesimulator"
val targetName = "ios" + if (sdkName.startsWith("iphoneos")) "Arm64" else "X64"
val framework = kotlin.targets.getByName<KotlinNativeTarget>(targetName).binaries.getFramework(mode)
inputs.property("mode", mode)
dependsOn(framework.linkTask)
val targetDir = File(buildDir, "xcode-frameworks")
from({ framework.outputDirectory })
into(targetDir)
}
tasks.getByName("build").dependsOn(packForXcode)
androidApp 模块的 build.gradle.kts 文件
plugins {
id("com.android.application")
kotlin("android")
id("kotlin-android-extensions")
}
group = "com.aman.helloworldkmm"
version = "1.0-SNAPSHOT"
repositories {
gradlePluginPortal()
google()
jcenter()
mavenCentral()
}
dependencies {
implementation(project(":shared"))
implementation("com.google.android.material:material:1.2.0")
implementation("androidx.appcompat:appcompat:1.2.0")
implementation("androidx.constraintlayout:constraintlayout:1.1.3")
}
android {
compileSdkVersion(29)
defaultConfig {
applicationId = "com.aman.helloworldkmm.androidApp"
minSdkVersion(24)
targetSdkVersion(29)
versionCode = 1
versionName = "1.0"
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
}
}
}
对于跨平台应用来说,版本特定代码是很常见的。例如你可能想知道你的应用是运行在 Android 还是 iOS 设备,并且得到设备的具体型号。为了完成这个功能,你需要使用 expect/actual 关键字。
首先,在 common 模块中使用 expect
关键字声明一个空的类或函数,就像创建接口或者抽象类一样。然后,在所有的其他模块中编写平台特定代码来实现对应的类或函数,并用 actual
修饰。
注意,如果你使用了
expect
,你必须提供对应名称的actual
实现。
否则,你会得到如下错误:
commonMain
expect class Platform() {
val platform: String
}
androidMain
actual class Platform actual constructor() {
actual val platform: String = "Android ${android.os.Build.VERSION.SDK_INT}"
}
iosMain
import platform.UIKit.UIDevice
actual class Platform {
actual val platform: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
}
MainActivity.kt (Android)
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val tv: TextView = findViewById(R.id.text_view)
tv.text = "Hello World, ${Platform().platform}!"
}
}
ContentView.swift (iOS)
struct ContentView: View {
var body: some View {
Text("Hello World, "+ Platform().platform)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
恭喜!! 你已经完成了你的第一个 KMM app 。
AAkira/Kotlin-Multiplatform-Libraries
在已经一片红海的移动端跨平台开发领域,Kotlin 另辟蹊径,让你可以继续使用平台原生方式开发 UI,在业务逻辑上做到 “Write once,run everywhere”。甚至放飞一下自我,未来的某一天是不是可以用 Flutter 做 UI 上的通用,用 Kotlin 做业务逻辑上的通用?
不管怎样,最终还是得开发者买账才行。不知道你怎么看 KMM,在评论区留下的你的看法吧!
最后打个广告,推荐一波我的小专栏,面向面试的 Android 复习笔记 ,目前已经输出六篇文章,感兴趣的可以给个订阅,点击文末 阅读原文 可直达。
Android 复习笔记目录