「KMP」Kotlin多平台技术简化了跨平台项目的开发。Kotlin应用程序将在不同的操作系统上运行,如iOS、Android、macOS、Windows、Linux、watchOS等。
通过KMP,我们可以轻松的在不同平台间最大程度的重用代码,共享业务逻辑,在需要实现本地UI或使用平台api时编写特定于平台的代码,这大大提升开发效率和业务逻辑统一的统一性。
如果你还不清楚什么是KMP
,敬请移步: 「KMM」、「KMP」?他们到底是什么?
本文尝试从0开始,创建我们的第一个KMP项目。
万事开头难,依照惯例,我们的第一个项目,就先从使用KMP
在iOS
设备和Android
设备上显示「Hello KMP」文本开始。
KMP允许我们在不同平台间共享逻辑层代码,UI层代码保持平台独立性。我们的第一个KMP项目也采用该方式。
step 1:设置环境
创建我们的第一个KMP项目之前,我们需要设置一下开发环境。这里的环境包括硬件和软件两部分:
硬件环境
由于苹果公司的限制,开发基于iOS系统的应用需要使用带有macOS系统的电脑,在Windows系统上是无法正常开发的,所以,首先,你需要一台Mac电脑。
这里推荐MacBookPro 16英寸 M3 Max 128G+8T
深空黑版本[/滑稽]。
软件环境
软件方面,KMP要求以下几个:
开发工具 | 作用 |
---|---|
Android Studio | KMP 项目的主力IDE,大多数情况下我们使用它来编写我们的KMP代码。它也是Android项目的官方IDE,基于IDEA定制 |
Xcode | 开发iOS应用程序的IDE,安装完成后接受其许可协议并打开,它会执行一些特定的任务来完成iOS开发环境的搭建,除了编写iOS平台特的代码外,大多数情况下我们不会使用Xcode |
JDK | 用于开发Java应用程序,包含JRE等必要的组件 |
Kotlin Multiplatform Mobile plugin | KMM的插件,可以在Android Studio的插件市场中下载安装,具体路径为:Android Studio|Settings|Preferences|Plugins,在Marketplace中搜索并安装 |
Kotlin plugin | 一般情况下,Kotlin插件会与Android Studio绑定。Kotlin plugin需要与Kotlin Multiplatform Mobile plugin保持兼容,具体的兼容性表参考 Kotlin Multiplatform Mobile plugin releases | Kotlin 。如果有兼容性问题需要更新插件,可在Android Studio中进行更新。 |
以上就是KMP
所需的全部工具,建议将这些工具都安装至最新的稳定版本,以避免不必要的兼容性问题。
ps:Kotlin/Native 有时会不支持最新的Xcode,如果遇到这种问题,可以尝试安装旧版本的Xcode。Kotlin/Native 与Xcode的兼容性可以参考 compatibility guide
确认安装环境
一切准备完毕后,我们可以通过KDoctor
来校验我们的环境是否有问题。与Xocde类似,KDoctor同样只能运行在MacOS系统上。
建议通过Homebrew安装KDoctor:
brew install kdoctor
你也可以选择去往 KDoctor 的 Github 主页手动下载。
安装完成后,在命令行运行以下命令校验我们的安装环境:
kdoctor
运行完成后会输出类似以下的内容:
可以看到KDoctor主要校验的就是我们上面提到几点:操作系统、Java、Android Studio以及对应的插件、Xcode以及对应的CommandLineTools
和cocoapods
。
每项检查的结果分为三种状态:
- [✓] - Success
- [✖] - Failure
- [!] - Warning
如果你和我一样,之前从没有使用过Xcode,大概率会和我遇到一样的错误和警告: Current command line tools: /Library/Developer/CommandLineTools
和 cocoapods not found
,别急我们一条一条来看:
- CommandLineTools
在 macOS 上,Command Line Tools
是一组提供在终端中使用的工具,它们对开发者来说非常有用。也是许多类型开发工作的先决条件,包括但不限于 C/C++、Python 扩展和一些 gem 包的安装。 我们按照报错的指引,前往Xcode|Settings|Locations|Locations设置一下即可。
- cocoapods not found
CocoaPods 是一个应用于 macOS 和 iOS 应用程序开发的依赖管理工具,专门用于 Swift 和 Objective-C 编程语言的项目。它通过简化第三方库(称为 "pods")的添加和更新过程,帮助开发者管理项目依赖。
我们的第一个KMP项目并不会使用到CocoaPods,所以这个警告我们暂且忽略。
问题解决后我们再次运行kdoctor
命令:
可以看到关于cocoapods仍然有警告,但已经不影响我们接下来的KMP项目了~
step 2:创建我们的第一个KMP项目
使用官方模版创建KMP项目
磨刀不误砍柴工,环境搭建完成后,我们就可以着手创建我们的第一个KMP项目了,Kotlin官方为我们提供了一套创建KMP项目的模版: KMP向导
我们只需要:
- 打开KMP向导 链接
- 选中
New Project
tab,按需更改项目名称和项目id - 按需选择我们需要支持的平台,此处我们选择Android和iOS
- 对于iOS,我们选择不共享UI,因此此处选择
Do not share UI(use only SwiftUI)
- 点击下载按钮得到我们的第一个KMP项目工程文件
使用Android Studio打开KMP项目
将我们的「HelloKMP」项目使用Android Studio打开,左上角选择以"Project"形式查看工程目录:
工程主要包含3大模块:
- composeApp
该文件夹包含了使用Compose Multiplatform
在不同平台间共用的代码,该文件夹包含了两类子文件夹:
commonMain
所有平台共享的代码androidMain
oriosMain
or...
平台特定代码
由于我们的项目仅支持iOS和Android两个平台,且选择了保持UI部分的平台独立性(即iOS平台使用SwiftUI构建UI),所以此处的composeApp仅针对Android平台。
- iosApp
包含iOS应用程序,即使使用Compose Multiplatform
共享UI,页需要该文件夹下的代码提供入口。iOS平台的特定代码也应存放于此。
- shared
shared模块包含了iOS和Android的通用逻辑代码,其包含3个子文件夹:
- androidMain
- iosMain
- commonMain
开发时需按照代码的通用程度将代码正确存放在不同的子文件夹下。
shared
文件夹下的代码会根据平台的不同生成不同的产物:在Android平台生成的就是Kotlin/JVM
的代码,在iOS平台生成的就是Kotlin/Native
代码。
奔跑吧,骚年
KMP项目默认包含了两个构建配置:composeApp
和iosApp
,分别对应构建Android App和iOS App,我们分别选择两个配置,run起来看看:
ps:构建ios项目时,不要忘了启动Xcode并保持运行
step3:编写通用代码
我们来调整下代码,毕竟是我们的第一个KMP项目,我们就将当前时间展示出来以做纪念(这还不是手到擒来): 找到项目中展示文案的代码,在Greeting.kt
中,我们对文案加以改造:
IDE大大的红色仿佛给了我们一个大大的逼兜:竟然找不到System类。很奇怪么?其实一点也不奇怪。
Greeting.kt
位于shared|commonMain
目录下,意味着此处的代码,是iOS和Android平台共用逻辑,而System是JVM特有的类,iOS平台上并不存在,所以报错也就不足为奇了。
那怎么来解决这个问题呢?这就要用到expect
和actual
关键字了:
expect 关键字
当你在共享代码模块中需要使用某些平台特定的实现(例如,API 调用、库或者某些行为)时,你可以使用 expect 关键字来声明一个期待的声明(expected declaration)。这表明你期望每个目标平台都提供这个声明的具体实现。expect 声明不能包含实际的实现代码。
actual 关键字
对于每个平台特定模块,你需要使用 actual 关键字来提供 expect 声明的具体实现。这意味着对于共享代码中的每个 expect 声明,每个平台都必须有一个对应的 actual 实现。actual 实现包含平台特定的代码,这些代码在该平台上执行预期的功能。
例如获取系统时间戳,我们在commonMain模块下声明一个接口,然后在不同的平台上实现该接口,提供真正的、基于平台的实现方法。
kotlin
#shared|commonMain|Time.kt
interface Time {
val currentTime: Long
}
expect fun getTime(): Time
#shared|androidMain|Time.android.kt
class AndroidTime: Time {
override val currentTime: Long
get() = System.currentTimeMillis()
}
actual fun getTime(): Time = AndroidTime()
#shared|iosMain|Time.ios.kt
class iOSTime: Time {
override val currentTime: Long
get() = Clock.System.now().toEpochMilliseconds()
}
actual fun getTime(): Time = iOSTime()
然后就可以在Greeting.kt
中获取系统的时间戳:
kotlin
class Greeting {
private val platform = getPlatform()
private val time = getTime()
fun greet(): String {
return "Hello, ${platform.name}!${time.currentTime}"
}
}
ps:在iOS平台获取系统时间戳时,我们使用
Clock.System.now().toEpochMilliseconds()
来获取。 事实上,这是Kotlin 中的一种获取当前时间戳的方式,来自 Kotlinx DateTime 库,在Android平台上也可以正常调用。上面的例子主要是为了体现expect/actual关键字的作用。
再次运行一下看看效果:
可以看到,时间戳都被正常打印出来了。
总结
通过我们的第一个KMP项目,相信你对KMP大概已经有了初步的认知。接下来,我们将继续学习KMP,打怪升级,成为一代大侠。