KMM初探

什么是KMM?

在开始使用 KMM 之前,您需要了解 Kotlin。

KMM 全称:Kotlin Multiplatform Mobile)是一个用于跨平台移动开发的 SDK,相比于其他跨平台框架,KMM是原生UI+逻辑共享的理念,由KMM封装成Android(Kotlin/JVM)的aar和iOS(Kotlin/Native)的framework,再提供给View层进行调用。

a45a47064c5aab78124a5e9c53a807bf.jpeg

Kotlin Multiplatform Mobile

KMM使用Kotlin 的多平台功能,您可以使用 Kotlin 语言和技术栈,开发一套可以在多平台之间共享的代码库,用来构建统一的代码逻辑,而不用针对各个平台都去实现自己的一套。

0cd32194d2a9b8be6457fbe39b37ea20.png

Kotlin多平台功能

环境工具准备

为了能够开始使用 KMM,您应该安装以下内容:

  1. Java JDK 版本 11

  2. Ruby and Bundler

  3. Android Studio — 版本 4.2 或更高版本

  4. Xcode — 版本 11.3 或更高版本

  5. Xcode 命令行开发者工具

  6. 适用于 Android Studio 的Kotlin Multiplatform Mobile plugin。在 Android Studio 中,选择 Preferences > Plugins > 在 Marketplace 中搜索插件Kotlin Multiplatform Mobile并安装:

b5cfc9c2bb5da796751fd5890c6e627e.png

kmm plugin

准备就绪,是时候创建您的第一个 KMM 项目了

创建第一个 KMM 项目

使用以下步骤创建您的第一个 KMM 项目:

1、打开Android Studio并点击新建 项目:

95751660f568e7d89d5a1085c4c16a92.png

2、在项目的模板中,向下滚动,您将看到我们现在有了KMM 应用程序。选择它并单击下一步按钮:

1874c9a4cf5af139a558b2db7f3280de.png

3、输入您的项目名称、要使用的最低 SDK,然后单击“下一步”按钮:

5fd471d7cd762217bedcf1dc97927bd5.png

4、更改 Android 和 iOS 的应用程序名称。您还可以更改共享模块名称,这里使用默认值并选择为shared模块添加示例测试:

3eb40819153ddbbc1329a336d68e04cc.png

对于iOS应用程序,可以在regular framework或CocoPods依赖管理器之间切换依赖关系,在环境工具准备中我提到需要为CocoPods安装Ruby 。

  1. 单击“下一步”按钮并等待 Gradle 完成项目设置:

项目加载成功后,现在可以找到项目中所有的app模块:

  • androidApp

  • iosApp

  • shared

3d6dfbc00c1b805e8afe6d2ae447df95.png

image
  1. 配置中选择androidApp 和 iOSApp运行到相应模拟器


434d9c0704e2ede140fc141893745e71.png

image

通过AndroidStudio就可运行iOS,通常使用 Xcode 来开发 iOS 原生应用

KMM 会利用 Gradle 插件,自动调用 Xcode 进行构建,并调起 iOS 模拟器运行

9b965f63f35c13c48a671d3ffc79b35b.png

image

注意:使用 KMM Plugin 建立的工程,会默认使用 Kotlin(.kts 文件)的形式来进行 Gradle 配置,另外,其新建的 iOS 工程,也默认使用 Swift UI 进行开发,且这两项不可以配置,如果需要使用传统的 iOS UI 开发形式,需要以集成的形式来新建 KMM Module

KMM工程结构

KMM 插件建立的 KMM 工程的文件目录结构说明

├── androidApp        # 实际 Android APP Module
├── build.gradle.kts  # 工程根 Gradle 配置
├── gradle
├── gradle.properties
├── gradlew
├── gradlew.bat
├── iosApp            # 实际的 iOS 工程根目录
├── local.properties
├── settings.gradle.kts
└── shared            # KMM 模块代码目录
    ├── build.gradle.kts  # KMM 模块 Gradle 配置(依赖、插件、构建 Task、cinterop 等配置)
    └── src # 内部模块形式都为 Gradle 工程 Module
        ├── androidMain    # Android 差异化代码,最终生成 AAR
        ├── commonMain     # 共享模块 API 代码
        ├── iosMain        # iOS 差异化代码,
        └── nativeInterop  # 默认不会创建,用来存放 *.def 文件,描述与 C/C++ 代码,或 Apple Framework 交互时,构建 klib 的配置

b3c05c2a2eb68fd4787636a5ac1c39b2.jpeg

image

androidApp和iosApp为Android和iOS这两个平台的工程模块,shared为共享逻辑模块,供androidApp和iosApp调用。

打开根目录的settings.gradle.kts:

pluginManagement {
    repositories {
        google()
        gradlePluginPortal()
        mavenCentral()
    }
}

dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
    }
}

rootProject.name = "wb-kmm-demo"
include(":androidApp")
include(":shared")

会发现主项目只include了androidApp和shared这两个子项目,因为这两个项目是Gradle项目

iOS的项目如何引用呢?

The iOS application is produced from an Xcode project. It's stored in a separate directory within the root project. 
Xcode uses its own build system; thus, the iOS application project isn't connected with other parts of the Multiplatform Mobile project via Gradle. 
Instead, it uses the shared module as an external artifact – framework.

iOS作为Xcode项目,储存在根项目的另一个文件夹。Xcode有自己的编译系统,因此iOS项目并不依靠Gradle去和shared工程建立联系,而是依靠将shared工程打包成framework供iOS项目使用。我们可以看一下shared模块编译后的产物,如下图所示:

f358182a5964ebd699184c88264df427.png

image

特定于平台的API和实现

因为在我们的核心目标即双端共享代码。

什么是共享代码呢?让同一份代码能在 Android & iOS 上运行。

怎么实现这个目标呢?简单来说把全部代码分为两个部分,其中一部分就是与平台无关的代码。

啥是平台无关的代码呢?比如我们要编写一个检验手机号的算法,我们认为这个算法的代码是平台无关的,因为输入就是一个字符串,里面的实现就是根据指定的规则来判断这个输入字符串的合法性,期间不涉及任何平台相关特性的访问,比如系统 API 的访问。

但是光有与平台无关的代码是不够的,一旦涉及到与平台相关的访问,例如获取设备版本号或硬件信息等,我们就需要独立于平台去完成它们,这些独立于平台的代码在哪实现?这就有了 AndroidApp 和 iOSApp 这两个模块。在“shared”模块中,我们包含了核心应用程序逻辑,例如类、函数,并使用 Gradle 作为构建系统。

expect/actual 机制 :

ad6a7e7cf10fafbfaa569e84d68454b9.jpeg

image

KMM 里 expect/actual 机制是非常重要的,因为它提供了一种语法技术来解决平台相关的问题。

拿工程例子来说,工程逻辑中使用设备的 版本 号时,这里需要编写特定平台的代码才能实现。这时,expect 就相当于声明一个协议,它定义了我们期望得到的接口或数据,之后各个平台都需要独立地实现这个协议来满足业务需要。

shared模块的实现形式

进入shared模块,看commonMain文件夹下Greeting的实现:

class Greeting {
    private val platform: Platform = getPlatform()


    fun greet(): String {
        return "Hello, ${platform.name}!"
    }
}

greeting()方法调用platform.name,Platform的实现如下:

interface Platform {
    val name: String
}


expect fun getPlatform(): Platform

Platform是个接口,使用expect关键字来声明getPlatform(),再由Android和iOS通过使用actual关键字分别实现:

Android:

class AndroidPlatform : Platform {
    override val name: String = "Android ${android.os.Build.VERSION.SDK_INT}"
}
 
actual fun getPlatform(): Platform = AndroidPlatform()

iOS:

import platform.UIKit.UIDevice


class IOSPlatform: Platform {
    override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
}


actual fun getPlatform(): Platform = IOSPlatform()

最后是两端调用实现:

Android:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApplicationTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colors.background
                ) {
                    GreetingView(Greeting().greet())
                }
            }
        }
    }
}


@Composable
fun GreetingView(text: String) {
    Text(text = text)
}

iOS:

import SwiftUI
import shared


struct ContentView: View {
  let greet = Greeting().greet()
  var body: some View {
    Text(greet)
  }
}


struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}
KMM vs Flutter vs RN

与 Flutter 这种框架的思想相反,KMM 是用一套语言生成多个平台特定的字节码,所有的翻译工作由 kotlinc 或 kotlin-nativec 编译器来执行,从某种角度来讲,是『从上到下』;

而 Flutter 的思想是『从下到上』,这也决定了两种框架适用的场景,Flutter 就适合绘制 UI,而 KMM 则是更适合与 UI 无关的逻辑代码,比如:Model 层,网络请求、数据解析、建模等

  • 体积:

  1. 使用 Flutter 需要在 App 包内部增加两个引擎:

  • 一个是 Flutter 的渲染引擎,该引擎使用 C/C++ 开发,直接调用 OpenGL/Skia 的 API 进行绘制,从而摆脱 iOS 的 UIKit 以及 Android 的 View 组件直接渲染成需要的样式,保证样式高度统一

  • 另一个是 Dart 语言的 Runtime,用于解析并运行 Dart 语言编译的 Bundle

这两者减小了开发者的适配成本,但增大了 APP 的包体积(其他类似的跨平台框架,如:React Native 等,也会内置 JavaScript Core 或 V8 引擎)

  1. KMM 针对不同平台生成不同的二进制依赖包,根本上还是调用了 Android、iOS 的原生 API,并不会内置引擎这类文件,对 App 的体积影响相对较小

  • 技术栈:

    • Flutter-->Dart

    • KMM-->Kotlin

    • RN-->JavaScript

  • 适用场景:

    • 由于 Flutter 采用类似 3D 游戏的渲染理念,统一了界面渲染引擎,利用 Dart 可以高度保证双端样式和交互逻辑一致,且几乎不存在界面适配问题,完全抹平了 TextView 和 UILabel 这类控件之间的差异,所以 Flutter 适合于界面构建

    • 而 KMM 并不适合 UI,双端的组件,生命周期、API 差异都比较大,KMM 在技术上可以实现功能,但相当于写了两份代码,失去了意义

Flutter和RN这两者虽然在设计及原理上区别很大,但设计思想上都是采用非原生开发语言在 Android 与 iOS 系统框架之上搭建的“阁楼”上运行,每个采用这些框架的 App 在打包时需要集成语言的 Runtime、框架的底层组件等许多重量级的包与库。并且 JavaScript 或 Dart 与原生开发语言(Java/Kotlin、Objective-C/Swift)之间的交互需要通过“桥接通讯”实现

KMM提出了不同于 RN 与 Flutter 的跨端解决方案,即使用不同的编译器编译同一份代码生成各端的不同产物来达到跨平台的目的,这就是 Kotlin Multiplatform

Kotlin Multiplatform

Kotlin Multiplatform 技术可为多种平台创建应用程序并在平台之间高效重用代码,同时保留原生编程的优势。多平台应用程序将在不同操作系统上运行,例如 iOS、Android、macOS、Windows 和 Linux 等。

27c016ec58560938bb22782af63cddff.png

image

Kotlin 官方开发的多平台框架分为以下几种:

  • 标准 Kotlin(Kotlin Stdlib)

    • 即 Kotlin JVM,由于 Kotlin 最初是基于 JVM 运行的,所以可以使用 Java  的全部功能

  • Kotlin Native

    • 简称 KN,内部对各平台(如:Windows、Linux、macOS、iOS、Android)Native API(不使用 Runtime 的)进行封装,底层使用 LLVM 进行编译,可以使用 Kotlin 调用各平台特定的 API 或基于一些 C/C++ 库的能力,如:OpenGL、OpenCV 等,使多平台共享一套 Kotlin 代码

  • Kotlin JS

    • 基本原理是将 Kotlin 代码翻译成 JavaScript,同时可以调用一些 JavaScript 特定的接口,从而进行 Web 开发

f8f6aa80a24457ff4eb23d5b28dae684.png

image

Compose Multiplatform,一个由JetBrains开发的基于kotlin的声明式UI框架,实现了在Android和iOS之间共享UI,实现了完全跨平台的应用程序:

c7a29c3acbe6d6ad62ba9c6752003e64.jpeg

image

Kotlin Multiplatform Wizard

我们可以使用Kotlin Multiplatform Wizard创建跨平台应用程序。

f847df6b83880cc09ab12265817c86f2.png

image

这里可以选择运行它的平台,如果我们选择与compose多平台共享UI为iOS项目,我们可以创建UI与compose多平台,并运行与iOS和android不使用Swift UI。我们也可以用它创建服务器和桌面项目。

10dea5514932dd13f9939e574a36861a.png


相关推荐

  1. <span style='color:red;'>KMM</span><span style='color:red;'>初探</span>

    KMM初探

    2024-03-16 21:36:02      37 阅读
  2. Snakemake:初探

    2024-03-16 21:36:02       55 阅读
  3. Quarkus初探

    2024-03-16 21:36:02       38 阅读
  4. Docker初探

    2024-03-16 21:36:02       35 阅读
  5. KVM迁移

    2024-03-16 21:36:02       40 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-16 21:36:02       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-16 21:36:02       101 阅读
  3. 在Django里面运行非项目文件

    2024-03-16 21:36:02       82 阅读
  4. Python语言-面向对象

    2024-03-16 21:36:02       91 阅读

热门阅读

  1. Linux 服务器环境搭建

    2024-03-16 21:36:02       38 阅读
  2. Winform编程详解八:Label 标签控件

    2024-03-16 21:36:02       39 阅读
  3. ref和reactive的区别?

    2024-03-16 21:36:02       45 阅读
  4. 导出oracle远程数据库到本地

    2024-03-16 21:36:02       47 阅读
  5. Python学习笔记之math库的使用

    2024-03-16 21:36:02       52 阅读