Kotlin Multiplatform 多平台开发

当前跨平台技术领域主要是React NativeFlutter,目前来说还是和预想的一次编写到处运行有很大差距的,并且各自也存在不少的问题还没有解决。

Kotlin相信大家都有所了解,特别是在Android开发领域已经在谷歌的推动下成为了主流的开发语言,本文就Kotlin在多平台开发方向的发展进行分享。

一、概述

支持多平台编程是Kotlin的主要优势之一,它减少了为不同平台编写和维护相同代码所花费的时间,同时保留了Native的灵活性和优势。

和其他跨平台技术一样,Kotlin Multiplatform并不能完全取缔原生开发,各个平台都有自己的特性,这些特性不是单一的语言技术可以统一完成的。Kotlin Multiplatform能让你共享尽可能多的代码,但是也提供调用一些平台特有的API。

1.1 Kotlin Multiplatform的工作方式

kotlin-multiplatform

  • Common Kotlin包括语言、核心库和基本工具。用通用Common Kotlin编写的代码可在所有平台上的任何地方使用。
  • 借助Kotlin Multiplatform库,可以在通用代码和特定于平台的代码中重用多平台逻辑。通用代码可以依赖一组涵盖常用操作的库,例如网络请求、数据库操作、序列化等。
  • 要与平台进行交互操作,需要使用特定于平台的Kotlin版本(Kotlin/JVMKotlin/JSKotlin/Native),包括对Kotlin语言的扩展以及特定于平台的库和工具,并将你编写的代码编译成对应平台的可执行文件,如使用 Kotlin/JVM 编译成 JVM 字节码。
  • 可以访问平台原生代码JVMJSNative)并利用所有原生功能。

1.2 平台共享代码

借助Kotlin Multiplatform,在不同平台上编写和维护相同的代码所需要的时间更少了,使用Kotlin提供的机制可以共享通用的逻辑:

  1. 在项目中使用的所有平台之间共享代码

flat-structure

  1. 在项目中包含的部分平台之间共享代码

hierarchical-structure

  1. 在移动平台共享代码示例

    通过这种机制,commonMain定义了期望声明,并且commonMain必须提供与期望的声明相对应的实际声明。这适用于大多数Kotlin声明,例如函数,类,接口,枚举,属性和注释。

    expect-actual

    //Common 期望声明
    expect fun randomUUID(): String
    
    //Android 实际声明
    import java.util.*
    actual fun randomUUID() = UUID.randomUUID().toString()
    
    //iOS 实际声明
    import platform.Foundation.NSUUID
    actual fun randomUUID(): String = NSUUID().UUIDString()
    

二、支持的平台

Target platform Target preset 说明
Kotlin/JVM jvm
Kotlin/JS js 选择执行环境:
browser {} 用于在浏览器中运行的应用程序。
nodejs{} 用于在 Node.js 上运行的应用程序。
Android 应用程序与库 android 手动应用 Android Gradle 插件——com.android.applicationcom.android.library
每个 Gradle 子项目只能创建一个 Android 目标。
Android NDK androidNativeArm32androidNativeArm64 64 位目标需要 Linux 或 macOS 主机。
可以在任何所支持的主机上构建 32 位目标。
iOS iosArm32iosArm64iosX64 需要 macOS 主机。
watchOS watchosArm32watchosArm64watchosX86
tvOS tvosArm64tvosX64
macOS macosX64 需要 macOS 主机。
Linux linuxArm64linuxArm32HfplinuxMips32linuxMipsel32linuxX64 Linux MIPS 目标(linuxMips32linuxMipsel32)需要 Linux 主机。
可以在任何所支持的主机上构建其他 Linux 目标。
Windows mingwX64, mingwX86 需要 Windows 主机。
WebAssembly wasm32

当前主机不支持的目标在构建期间会被忽略。

三、多平台项目

3.1 创建多平台项目

创建一个多平台项目时,项目向导会自动在build.gradle.kts)文件中应用kotlin-multiplatformGradle插件。

plugins {
    id 'org.jetbrains.kotlin.multiplatform' version '1.4.31'
}

一个多平台项目针对的是由不同Target preset的多个平台。目标是构建的一部分,负责为特定平台(例如macOS,iOS或Android)构建,测试和打包应用程序。

创建多平台项目时,目标平台将添加到build.gradlebuild.gradle.kts)文件中的kotlin块中,编写参照支持的平台表格中Target preset列,例如jvm平台与浏览器平台编写方式:

kotlin {
    jvm()
    js {
        browser {}
    }
 }

项目包括src中带有Kotlin源集的目录,这些源集是Kotlin代码文件的集合,以及它们的资源,依赖关系和语言设置。可以在Kotlin编译中为一个或多个目标平台使用源集。每个源集目录都包含Kotlin代码文件(kotlin目录)和资源文件(resources目录)。项目向导会为通用代码以及所有添加的目标的maintest编译创建默认源集。src目录层次如下图所示:

source-sets

3.2 多平台依赖关系

默认情况下,所有特定于平台的源集都依赖公共源集commonMain,并不需要手动指定dependsOn关系,例如jvmMainjsMain可使用位于commonMain中声明的函数。

如果需要手动调整特定平台的依赖源集,首先在gradle.properties中增加一行:

kotlin.mpp.enableGranularSourceSetsMetadata=true

随后引入一个中间源集,该源集包含多个目标的共享代码,并创建包含中间集的源集的结构。例如为桌面环境引入一个中间源集desktopMain,在 linuxX64MainmingwX64MainmacosX64Main这三个平台中共享:

kotlin {
    sourceSets {
        desktopMain {
            dependsOn(commonMain)
        }
        linuxX64Main {
            dependsOn(desktopMain)
        }
        mingwX64Main {
            dependsOn(desktopMain)
        }
        macosX64Main {
            dependsOn(desktopMain)
        }
    }
}

目前中间源集并不能支持所有组合模式,仅提供如下组合方式:

  • JVM + JS + Native
  • JVM + Native
  • JS + Native
  • JVM + JS
  • 多个Native源集

值得一提的是Native源集的组合,非常灵活,比较典型的一个例子是kotlinx.coroutines库,其源集的层次结构如下:

lib-hierarchical-structure-1

3.3 外部依赖关系

如果想要依赖一个外部的库,可以在Gradle文件指定源集的dependencies块中增加一个依赖关系,例如:

kotlin {
    sourceSets {
        commonMain {
            dependencies {
                implementation 'com.example:my-library:1.0'
            }
        }
    }
}

如果需要依赖一个kotlinx库并且要区分平台,可以使用指定平台的后缀获得特定平台的变种库。例如依赖kotlinx-coroutines库,commonMainkotlinx-coroutines-corejvmMainkotlinx-coroutines-core-jvm

kotlin {
    sourceSets {
        commonMain {
            dependencies {
                implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2'
            }
        }
        jvmMain {
            dependencies {
                implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.4.2'
            }
        }
    }
}

四、移动开发实践

在移动开发方向,有更进一步的支持,JetBrains专门为Android Studio编写了一个名为Kotlin Multiplatform Mobile的插件,简称KMM,使得Kotlin Multiplatform在移动端的跨平台开发变得更为方便。截至目前,KMM插件仅支持在MacOS上运行,需要Android Studio 4.2或更高版本、Xcode 11.3或更高版本、最新的Kotlin插件。

4.1 创建一个移动跨平台项目

  1. Android Studio中,选择文件| 新增| 新项目
  2. 在项目模板列表中选择“ KMM应用程序”,然后单击“下一步”kmm-project-wizard-1
  3. 为第一个应用程序指定一个名称,然后单击“下一步”kmm-project-wizard-2
  4. 保留应用程序和共享文件夹的默认名称,选中复选框以为您的项目生成示例测试,然后单击“完成”kmm-project-wizard-3basic-project-dirs
  5. 在运行配置列表中选择androidApp/iosApp,单击运行即可在选中的模拟器上或真实设备上运行。

run-android

first-kmm-on-android

run-ios

first-kmm-on-ios

4.2 KMM项目结构

  • 一个基本的Kotlin移动多平台(KMM)项目包括三个组件:
  • 共享模块-Kotlin模块,其中包含Android和iOS应用程序的通用逻辑,内置于Android库和iOS框架中,使用Gradle作为构建系统。
  • Android应用程序-内置于Android应用程序中的Kotlin模块,使用Gradle作为构建系统。
  • iOS应用程序-内置到iOS应用程序中的Xcode项目。

basic-project-structure

iOS应用程序是从Xcode项目生成的,它存储在根项目中的单独目录中,Xcode使用自己的构建系统。因此,iOS应用程序项目未通过Gradle与KMM项目的其他部分连接,相反,它使用共享模块作为外部构件 - framework

4.3 从现有项目迁移KMM

对于已有Android项目,这一过程并不繁琐。通常一个Android项目的业务核心module就是app,项目结构应该是这样的:business-logic-to-share

这是一个原有的登录业务,现在决定将data包内的逻辑复用在两个平台,接下来看看如何将项目迁移到KMM,并将data包内的逻辑共享出去:

  1. 在Android项目中,为跨平台代码创建一个KMM共享模块。稍后,将其连接到现有的Android应用程序和将来的iOS应用程序。

    1. 在Android Studio中,点击文件| 新增| 新模块
    2. 在模板列表中,选择“ KMM共享模块”,输入模块名称shared,然后选择“生成packForXcode Gradle”任务复选框。点击完成。kmm-module-wizard
  2. 将对共享模块的依赖项添加到原有的Android应用程序。

    1. 将对共享模块的依赖项添加到Android应用程序的build.gradle文件中。

      dependencies {
          implementation project(':shared')
      }
      
    2. 将业务逻辑代码com.jetbrains.simplelogin.androidapp.dataapp目录移动到目录中的com.jetbrains.simplelogin.sharedshared/src/commonMain

      moving-business-logic

    3. 运行在Android虚拟机或真实设备并解决报错,以确保其正常运行。

  3. 使跨平台应用程序在iOS上运行。

    1. 在原有Android项目根文件夹下,使用Xcode创建一个iOS项目,项目命名为iosApp,并将共享模块编译为.framework

    2. 在Android Studio的终端中运行Gradle任务./gradlew packForXcode,将会生成框架并存储在shared/build/xcode-frameworks/目录中。

    3. 在Xcode的项目设置中的**“框架,库和嵌入式内容”添加新生成的shared/build/xcode-frameworks/shared.framework框架,并指定框架搜索路径搜索路径生成设置**选项卡- $(SRCROOT)/../shared/build/xcode-frameworks

    4. 在Xcode的项目设置的Build Phases选项卡上,单击**+并添加New Run Script Phase**。添加以下脚本并将**“运行脚本”阶段移到“编译源”**阶段之前:

      cd "$SRCROOT/.."
      ./gradlew :shared:packForXCode -PXCODE_CONFIGURATION=${CONFIGURATION}
      
    5. 最后在需要使用共享库的地方import shared即可,在Android Studio中更改gradle.properties,增加xcodeproj=iosApp/SimpleLoginIOS.xcodeproj即可将iOS应用程序连接到Android Studio。

五、最新进展

5.1 Kotlin Multiplatform 的首次用户调查结果

有Android背景的开发人员在使用Kotlin Multiplatform的开发人员中占绝大多数,有后端经验的开发人员的数量也很大,8%的受访者来自iOS背景,61.4%的受访者有5年以上的开发经验。

42.2%的受访者使用Kotlin Multiplatform在iOS和Android之间共享代码。这意味着他们正在使用KMM,后者最近被拆分到一个单独的项目中,Kotlin团队对此投入了大量资源以推进它进入了Alpha状态。此外,移动端+ Web 端、后端+ Web 端的组合也很流行。最出乎意料的是,有16.3%的开发者使用这项技术来覆盖全平台,目的就是使其代码尽可能通用。JetBrains最近发布的Space就是使用这项技术支持全平台案例,它几乎完全基于Kotlin构建。

超过56%的受访者已经或即将发布具有共享模块的应用程序,即使Kotlin Multiplatform仍在Alpha阶段,但它足以支撑生产需求。

超过一半的受访者使用Android Studio来运用Kotlin Multiplatform的功能。紧随其后的是IntelliJ IDEA UltimateIntelliJ IDEA Community Edition,分别为27.3%和16.7%。

5.2 Kotlin Multiplatform Mobile 现已推出 Alpha 版本

有了新的KMM插件,可以在Android Studio中编写、运行、测试和调试共享代码,而无需切换到其他 IDE,也可以直接在Android Studio中整合 iOS 设备和模拟器,提供运行和调试 iOS 应用程序的功能。

借助 New Kotlin Multiplatform Mobile Project 向导,只需点击几下就可以通过共享代码库创建随时可运行的移动应用程序项目。

Alpha 版本意味着一切都已准备就绪,Kotlin 团队将全力改进这项技术并助力技术发展,可以新建Kotlin Multiplatform项目或将KMM模块轻松集成到现有项目。 在这两种情况下,工作都可以按最低成本完成,也可以随时返回完全原生开发。

参考文档

  1. Kotlin Multiplatform
  2. KMM documentation
  3. Kotlin Multiplatform 的首次用户调查结果出炉啦
  4. Kotlin Multiplatform Mobile 现已推出 Alpha 版本
知识共享许可协议
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。
comments powered by Disqus