原文作者:Aman Bansal
原文地址:Create Hello World App with KMM 📱- Android & IOS
译者:秉心说
在移动开发领域,Android 和 iOS 版本的应用程序通常会有很多共同点,背后的业务逻辑基本也是一致的。文件下载,读写数据库,从远程服务器获取数据,解析远程数据等等。所以我们为什么不只写一次业务逻辑代码,在不同的平台上共享呢?
有了这个想法之后,Jetbrains 带来了 Kotlin Multiplatform Project 。
➡️ 什么是 Kotlin Multiplatform Mobile?
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 工程。插件还可以帮助你编写,运行,测试共享代码。
➡️ 一步一步构建 HELLO WORLD KMM 应用
- 在 Android Studio 上安装 Kotlin Multiplatform Mobile 插件。打开 Android Studio -> 点击 Configure -> 选择 Plugins
- 在 plugins 部分选择 Marketplace ,搜索 KMM,安装并重启 Android Studio。
- 在 Android Studio 首页选择 “Start a new Android Studio project” 。
- 在 “Select a project Template” 页面,选择 “KMM Application” 。
- 设置工程名称,最低 SDK,文件目录,包名等。
现在,你需要等待工程的第一次构建,需要花费一些时间去下载和设置必要的组件。
译者注:KMM 插件要求你的 Kotlin 插件版本至少为 4.0 版本以上
➡️ 运行你的程序
在菜单栏选择你要运行的平台,选择设备,点击 Run
要运行 iOS 应用,你需要安装 Xcode 和模拟器。
➡️ 瞅一眼代码
Android 开发者? 看起来很熟悉? 😎
IOS 开发者? 看起来就像外星人? 👽
➡️ 模块
-
shared 模块 —— 存放 Android/iOS 通用业务逻辑代码的 Kotlin 模块,会被编译为 Android library 和 iOS framework。使用 Gradle 进行构建。
-
androidApp 模块 —— Android 应用的 Kotlin 模块。使用 Gradle 构建。
-
iosApp 模块 —— 构建 iOS 应用的 Xcode 工程。
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 module
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
}
}
}
➡️ 使用 Expect/Actual 关键字
对于跨平台应用来说,版本特定代码是很常见的。例如你可能想知道你的应用是运行在 Android 还是 iOS 设备,并且得到设备的具体型号。为了完成这个功能,你需要使用 expect/actual 关键字。
首先,在 common 模块中使用 expect
关键字声明一个空的类或函数,就像创建接口或者抽象类一样。然后,在所有的其他模块中编写平台特定代码来实现对应的类或函数,并用 actual
修饰。
注意,如果你使用了
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 。
➡️开源 KMM 应用
➡️ 可用的 KMM 类库
AAkira/Kotlin-Multiplatform-Libraries
译者说
在已经一片红海的移动端跨平台开发领域,Kotlin 另辟蹊径,让你可以继续使用平台原生方式开发 UI,在业务逻辑上做到 “Write once,run everywhere”。甚至放飞一下自我,未来的某一天是不是可以用 Flutter 做 UI 上的通用,用 Kotlin 做业务逻辑上的通用?
不管怎样,最终还是得开发者买账才行。不知道你怎么看 KMM,在评论区留下的你的看法吧!
最后打个广告,推荐一波我的小专栏,面向面试的 Android 复习笔记 ,目前已经输出六篇文章,感兴趣的可以给个订阅。
Android 复习笔记目录