Kotlin Native | Kotlin Documentation
Kotlin/Native is a technology for compiling Kotlin code to native binaries which can run without a virtual machine. Kotlin/Native includes an LLVM-based backend for the Kotlin compiler and a native implementation of the Kotlin standard library.
Kotlin/Native is primarily designed to allow compilation for platforms on which virtual machines are not desirable or possible, such as embedded devices or iOS. It is ideal for situations when a developer needs to produce a self-contained program that does not require an additional runtime or virtual machine.
随着跨平台技术的成熟,Kotlin Native 现在已经稳定支持 Windows、Linux、MacOS、Andorid、IOS 平台,以及正在测试的 JavaScript 和 WebAssembly。
简介
Kotlin Native是一种将Kotlin代码编译为原生二进制文件的技术,无需虚拟机即可运行。它是基于LLVM的Kotlin编译器后端,并且包含Kotlin标准库的原生实现。
同时 Kotlin Native 与 C、C++、Objective-C 以及 Swift 有很强的互操作性,在这些平台上,也可以很轻松的调用 Kotlin 代码。
Kotlin Native 也是 Kotlin Multi Platform 的重要组成部分,它为 App 跨平台开发奠定了坚实的基础。
支持的平台
- macOS
- iOS, tvOS, watchOS
- Linux
- Windows (MinGW)
- Android NDK
原理
现在来看看为什么 Kotlin Native 可以跨平台吧:
观察上面这张图发现,Kotlin Native 会将 commonMain
模块的 greeting.kt
编译成 JVM 平台上的 classfiles 以及 Native 平台上各自的可执行文件。
也就是说,commonMain
内包含了平台无关的代码,在里面你可以用 Kotlin 实现跨平台的代码,也可以定义 expect
方法由具体平台实现。
总而言之,commonMain
只关心跨平台的代码部分,如果某些功能需要分别在不同平台实现,那么 commonMain
也只负责定义一个类似接口的类或函数(expect),然后交给不同平台的模块实现(actual)
创建项目
你可以直接 Clone 一个官方给出的示例:Kotlin/kmp-native-wizard: A mostly-empty template to get started creating a Kotlin/Native project.
也可以跟我一样从头开始创建一个 Kotlin Native 项目。
新建 Gradle 项目
新建一个普通的 Kotlin 项目,用 Gradle Kts,这里我用的版本是 8.4,下面给出 gradle-wrapper.properties
:
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
引入 Kotlin Native
在 ./gradle
目录下新建一个文件名为 libs.versions.toml
,作为依赖管理文件,以便我们管理所有依赖以及版本。
[versions]
kotlin = "2.0.0"
kotlinxSerialization = "1.7.1"
[libraries]
kotlinxSerializationJson = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
[plugins]
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotlinxSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
然后来到 build.gradle.kts
中,稍作修改:
plugins {
}
group = "com.lovelycatv.ktnative"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
现在是一个空的 build.gradle
,下面我们引入必要的插件:
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.kotlinxSerialization)
}
group = "com.lovelycatv.ktnative"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
像这样就好了,然后点击 Gradle Sync,这里会报错:
Please initialize at least one Kotlin target in 'kotlin-native-study (:)'.
Read more https://kotl.in/set-up-targets
先不用管,下面设置好 sourceSets
之后就好了。
设定源集
关于 SourceSet 的相关内容,这里给出官方文档:The basics of Kotlin Multiplatform project structure | Kotlin Documentation
Target 目标平台
首先看看来自官方文档对 Target 的描述:
Targets define the platforms to which Kotlin compiles the common code. These could be, for example, the JVM, JS, Android, iOS, or Linux. The previous example compiled the common code to the JVM and native targets.
简单来说,Target 就是编译的目标平台,例如你指定了 mingwX64 和 linuxX64,那么 gradle 的 build 任务就会将你的代码打包到这两个平台。
为了演示,下面添加所有的 Targets:
kotlin {
val targets = listOf(
mingwX64("windows"),
linuxArm64("linuxArm64"),
linuxX64("linuxX64"),
macosArm64("macosArm64"),
macosX64("macosX64")
)
}
有了 Targets,下面还要给它们设置一些参数:
kotlin {
val targets = listOf(
mingwX64("windows"),
linuxArm64("linuxArm64"),
linuxX64("linuxX64"),
// macosArm64("macosArm64"),
// macosX64("macosX64")
)
targets.forEach {
it.apply {
binaries {
sharedLib {
baseName = "KotlinNativeLib"
}
executable {
entryPoint = "main"
}
}
}
}
}
如果你不需要打包成可执行文件,你可以不设定 executable
。
最后打包出来的文件,在 build/bin/[平台名称]/[*Shared/*Executable]/
中可以找到。
SourceSet 源集
对于源集,官方文档给出解释:A Kotlin source set is a set of source files with its own targets, dependencies, and compiler options. It’s the main way to share code in multiplatform projects.
简单来说就是一个源集可以包括多个 Targets,例如 linuxMain
包括了 linuxArm64Main
与 linuxX64Main
,而 nativeMain
包括了 Windows、Linux、MacOS 等平台,可以简单理解为全平台通用。
下面用一张官方的图解释一下:
也就是说,如果 commonMain
中有某个 expect 函数需要分别在多平台实现,而在 Linux 平台的实现,不管是 x64 还是 arm64 都是一样的代码,那么就没必要在 linuxArm64Main
和 linuxX64Main
中分别写相同的代码,只要在 linuxMain
写好就行了。
上面介绍了什么是源集,下面看看有哪些源集可以用:
- androidMain
- androidNativeMain
- androidNativeTest
- appleMain
- appleTest
- commonMain
- commonTest
- iosMain
- iosTest
- jsMain
- jsTest
- jvmMain
- jvmTest
- linuxMain
- linuxTest
- macosMain
- macosTest
- mingwMain
- mingwTest
- nativeMain
- nativeTest
- tvosMain
- tvosTest
上面的源集是我从 KotlinMultiplatformSourceSetConventions
中复制出来的,Kotlin Native 版本不同可能会有一点点小变化,根据实际情况来就好了。
下面来定义源集,在 kotlin {}
中追加即可:
sourceSets {
commonMain {
dependencies {
implementation(libs.kotlinxSerializationJson)
}
}
nativeMain {
dependencies {
}
}
}
我在 commonMain
中引入了一个依赖,由于各平台都继承 nativeMain
的依赖,而 nativeMain
又继承 commonMain
的依赖,因此这个依赖是全平台通用的。
同样地,如果在 mingwMain
中引入依赖,只有在 Windows 平台可以用。
各平台代码目录
上面介绍了 Target 和 SourceSet,下面来看看如何创建各平台的代码目录吧。
先打开 Project Structure,进入 Modules 中就可以看到所有模块了。
选择对应的模块就可以直接看到目录的路径了,我这里是项目根目录的 src/[模块名称]/[kotlin/resources]
。
下面是我的目录结构:
kotlin-native-study/
│
├── build.gradle.kts
├── settings.gradle.kts
│
├── src/
│ ├── commonMain/
│ │ ├── kotlin/
│ │ │ └── Greeting.kt
│ │ └── resources/
│ │
│ ├── nativeMain/
│ │ ├── kotlin/
│ │ └── resources/
│ │
│ ├── macosMain/
│ │ ├── kotlin/
│ │ └── resources/
│ │
│ ├── linuxArm64Main/
│ │ ├── kotlin/
│ │ └── resources/
│ │
│ ├── linuxX64Main/
│ │ ├── kotlin/
│ │ └── resources/
│ │
│ └── windowsMain/
│ ├── kotlin/
│ └── resources/
│
└── gradle/
├── wrapper/
└── libs.versions.toml
编写测试代码
commonMain
上面我在 commonMain
中写了一个 Greeting.kt
,内容如下:
expect fun getPlatformName(): String
@Serializable
data class Message(
val topic: String,
val content: String,
)
private val PrettyPrintJson = Json {
prettyPrint = true
}
class Greeting {
fun greet(): String {
return "Hello, ${getPlatformName()}!"
}
}
fun main() {
val message = Message(
topic = "Kotlin/Native",
content = Greeting().greet()
)
println(PrettyPrintJson.encodeToString(message))
}
这里的 getPlatformName()
函数是一个 expect 方法,用于获取各平台的名称,这个函数需要在各平台分别实现。
入口函数
如果你不需要编译成可执行文件,可以跳过这部分。
main()
函数就是可执行文件的入口了,如果有多个 main() 函数,需要手动给不同的 Target 指定,例如下面指定 Windows 平台使用它自己模块内的入口函数:
mingwX64("windows").apply {
binaries {
executable {
entryPoint = "com.lovelycatv.windowsMain"
}
}
}
其他平台还是使用 commonMain
中的 main() 函数。
windowsMain
在 src/windowsMain/kotlin
新建文件 Greeting.windows.kt
,内容如下:
actual fun getPlatformName(): String {
return "Windows"
}
其他平台也是一样的操作,这里就不再重复了。
不过需要注意的是,文件名不一定要命名为 Greeting.windows
,因为 Kotlin Native 编译时会自动从目标模块中查找对应的 actual 实现。不过从可读性和可维护性来说,建议命名成相关的名字。
测试
下面你可以直接在 Idea 中运行上面的 main() 函数,这里我在 Windows 平台,得到的运行结果是:
{
"topic": "Kotlin/Native",
"content": "Hello, Windows!"
}
下面测试一下 Linux 上是否可以执行,直接执行 build 任务,打包可执行文件和对应平台的库文件。
找到 Linux 下的可执行文件 *.kexe
,在 build/bin/linuxX64/[*Executable]/
或 build/bin/linuxArm64/
中,将 [*Executable]/
kexe
文件复制到 Linux 中。
这里我的系统是 CentOS(x64),如果你不知道你的系统架构,执行 uname -m
即可。
然后在终端直接执行,得到下面的结果:
{
"topic": "Kotlin/Native",
"content": "Hello, LinuxX64!"
}
结束语
Kotlin Native 项目的创建并不难,只要添加对应的目标平台即可。同时 Kotlin Native 提供的 Build Tasks 可以快速生成多个平台的库文件以及可执行文件,极大简化了跨平台开发的难度。
至于 KMP 我还没有深入了解,感兴趣的可以自行前往:Introduction to Kotlin Multiplatform | Kotlin Documentation。
参考文献: