本文正在参与「金石计划」

一、布景

初探 Kotlin Multiplatform Mobile 跨平台原理

本文会尝试经过 KMM 编译产品了解一套 kt 代码是如安在多个渠道复用的。

KMM 发流程简介

我以开发一个 KMM 日志库为例,简单介绍开发流程是什么:

  1. 在 CommonMain 界说接口,用 expect 关键字修饰,表明此接口在不同渠道的完成不一样。
  2. 在详细渠道完成接口,并用 actual 关键字修饰
// ----- commonMain -----
expect fun log(tag: String, msg: String)
// ----- androidMain -----
actual fun log(tag: String, msg: String) {
  Log.i(tag, msg)
}
// ----- iosMain -----
actual fun log(tag: String, msg: String) {
  NSLog("$tag:: %s", msg)
}
  1. 编译、打包、发布

初探 Kotlin Multiplatform Mobile 跨平台原理

  1. 依靠详细渠道库房
    1. 假如宿主为 Android App,则依靠对应的 kmm-infra-android
    2. 假如宿主为 iOS App,需求现将 kmm-infra-iosarm64 打包成 Framework,然后 iOS 依靠 Framework
    3. 假如宿主为 KMM 库,则依靠 kmm-infra

二、Common 和详细渠道的联络

了解 KMM 基本的开发流程和发布产品后,咱们需求持续深化了解发布产品的结构,再来了解 Common 层代码和详细渠道代码是怎么建立联络的。

Common 层编译产品

├── kmm-infra
  ├── 1.0.0-SNAPSHOT
    ├── kmm-infra-1.0.0-SNAPSHOT-kotlin-tooling-metadata.json
    ├── kmm-infra-1.0.0-SNAPSHOT-sources.jar
    ├── kmm-infra-1.0.0-SNAPSHOT.jar
    ├── kmm-infra-1.0.0-SNAPSHOT.module
    ├── kmm-infra-1.0.0-SNAPSHOT.pom
    └── maven-metadata-local.xml
  • kotlin-tooling-metadata.json,寄存了编译东西的相关信息,比方 gradle 版别、KMM 插件版别以及详细渠道编译东西的信息,比方 jvm 渠道会有 jdk 版别,native 渠道会有 konan 版别信息
  • source.jar,Kotlin 源码
  • .jar,寄存 .knm (knm是什么,后文会详细介绍) ,其间描绘了 expect 的接口
  • .module,见下文

.module 是什么?

用 json 描绘编译产品文件结构的清单文件,以及相关 common 和详细渠道产品的信息。里边描绘的字段较多,我只放一些关键信息,剩下内容感兴趣的读者能够自己研究

{
   "variants": [
      {
        "name": "",
        "attributes": {
           "org.gradle.category": "",
           "org.gradle.usage": "",
           "org.jetbrains.kotlin.platform.type": ""
        }
        "available-at": {
          "url": "",
        },
        "dependencies": [
          {
            "group": "org.jetbrains.kotlin",
            "module": "kotlin-stdlib-common",
            "version": {
              "requires": "1.8.0"
            }
          }
        ]
      }
   ]
}
  • name,当前产品的称号,比方 common 层为 metadataApiElements,详细渠道为 {target}{Api/Metadata}Elements-published

  • available-at,详细渠道特有的字段,其间 url 指的是详细渠道 .module 的文件路径,作为相关 common 和详细渠道的桥梁

  • dependencies,描绘有哪些依靠

详细渠道的 .module

为方便大家更好的了解,这儿还是贴出一份完整的 iOS 渠道的 .module 文件

{
  "formatVersion": "1.1",
  "component": {
    "url": "../../kmm-infra/1.0.0-SNAPSHOT/kmm-infra-1.0.0-SNAPSHOT.module",
    "group": "com.gpt.jarvis.kmm",
    "module": "kmm-infra",
    "version": "1.0.0-SNAPSHOT",
    "attributes": {
      "org.gradle.status": "integration"
    }
  },
  "createdBy": {
    "gradle": {
      "version": "7.4.2"
    }
  },
  "variants": [
    {
      "name": "iosArm64ApiElements-published",
      "attributes": {
        "artifactType": "org.jetbrains.kotlin.klib",
        "org.gradle.category": "library",
        "org.gradle.usage": "kotlin-api",
        "org.jetbrains.kotlin.native.target": "ios_arm64",
        "org.jetbrains.kotlin.platform.type": "native"
      },
      "dependencies": [
        {
          "group": "org.jetbrains.kotlin",
          "module": "kotlin-stdlib-common",
          "version": {
            "requires": "1.8.0"
          }
        }
      ],
      "files": [
        {
          "name": "kmm-infra.klib",
          "url": "kmm-infra-iosarm64-1.0.0-SNAPSHOT.klib",
          "size": 6396,
          "sha512": "2ebdb65f7409b86188648c1c9341115ab714ad5579564ce4ec0ee7fb6e0286351f01d43094bc7810d59ab1c4d4fa7887c21ce53bc087c34d129309396ceb85a5",
          "sha256": "056914503154535806165c132df52819aedcc93a7b1e731667a3776f4e92ff79",
          "sha1": "c43ed6cb8b5bf3f40935230ce3a54b2f27ec1d6a",
          "md5": "d79166eda9f4bf67f5907b368f9e9477"
        }
      ]
    },
    {
      "name": "iosArm64MetadataElements-published",
      "attributes": {
        "artifactType": "org.jetbrains.kotlin.klib",
        "org.gradle.category": "library",
        "org.gradle.usage": "kotlin-metadata",
        "org.jetbrains.kotlin.native.target": "ios_arm64",
        "org.jetbrains.kotlin.platform.type": "native"
      },
      "dependencies": [
        {
          "group": "org.jetbrains.kotlin",
          "module": "kotlin-stdlib-common",
          "version": {
            "requires": "1.8.0"
          }
        }
      ],
      "files": [
        {
          "name": "kmm-infra-iosarm64-1.0.0-SNAPSHOT-metadata.jar",
          "url": "kmm-infra-iosarm64-1.0.0-SNAPSHOT-metadata.jar",
          "size": 5176,
          "sha512": "fa828f456c3214d556942105952cb901900a7495f6ce6030e4e65375926a6989cd1e7b456f772e862d3675742ce2678925a0a12a1aa37f4795e660172d31bbff",
          "sha256": "c4de0db2b60846e3b0dbbd25893f3bd35973ae790696e8d39bd3d97d443a7d4c",
          "sha1": "e59036a081663f5c5c9f96c72c9c87788233c8bc",
          "md5": "9293e982f84b623a5f0daf67c6e7bb33"
        }
      ]
    }
  ]
}

iOS 渠道编译产品

咱们其实能够经过上面 iOS 渠道 .module 文件看到一些描绘,有 metadata.jar.klib

├── kmm-infra-iosarm64
│ ├── 1.0.0-SNAPSHOT
│ │ ├── kmm-infra-iosarm64-1.0.0-SNAPSHOT-metadata.jar
│ │ ├── kmm-infra-iosarm64-1.0.0-SNAPSHOT-sources.jar
│ │ ├── kmm-infra-iosarm64-1.0.0-SNAPSHOT.klib
│ │ ├── kmm-infra-iosarm64-1.0.0-SNAPSHOT.module
│ │ ├── kmm-infra-iosarm64-1.0.0-SNAPSHOT.pom
│ │ └── maven-metadata-local.xml
│ └── maven-metadata-local.xml
└── kmm-infra-iosx64
    ├── 1.0.0-SNAPSHOT
    │ ├── kmm-infra-iosx64-1.0.0-SNAPSHOT-metadata.jar
    │ ├── kmm-infra-iosx64-1.0.0-SNAPSHOT-sources.jar
    │ ├── kmm-infra-iosx64-1.0.0-SNAPSHOT.klib
    │ ├── kmm-infra-iosx64-1.0.0-SNAPSHOT.module
    │ ├── kmm-infra-iosx64-1.0.0-SNAPSHOT.pom
    │ └── maven-metadata-local.xml
    └── maven-metadata-local.xml
  • metadata.jar,主要寄存了 .knm
  • .klib,也寄存了 metadata.jar 中相同的内容,除此以外还有 ir,方便编译器后端持续编程机器码
  • 假如不了解 ir 是什么,能够参阅我之前写的 Kotlin Compiler】IR 介绍

.knm 和 .klib 是什么?后文会详细介绍

三、.klib 和 .knm 文件

  1. klib 的文件结构是怎样的?
  2. .knm 是什么文件?为什么只能用 IDEA 阅读?

klib 文件结构

klib 指 Kotlin Library

klib
├── ir
│ ├── bodies.knb
│ ├── debugInfo.knd
│ ├── files.knf
│ ├── irDeclarations.knd
│ ├── signatures.knt
│ ├── strings.knt
│ └── types.knt
├── linkdata
│ ├── module
│ ├── package_com
│ │ └── 0_com.knm
│ ├── package_com.jarvis
│ │ └── 0_jarvis.knm
│ ├── package_com.jarvis.kmm
│ │ └── 0_kmm.knm
│ ├── package_com.jarvis.kmm.infra
│ │ └── 0_infra.knm
│ └── root_package
│     └── 0_.knm
├── manifest
├── resources
└── targets
    └── ios_arm64
        ├── included
        ├── kotlin
        └── native

.knm 的生成进程

knm 指 kotlin native metadata

初探 Kotlin Multiplatform Mobile 跨平台原理

  1. .kt 经过编译器 frontend, 生成 kotlinIr
  2. 经过 protobuf 序列化后,生成 .knm 文件,这也解说了 vim 打开是乱码的原因
  3. .knm 经过反序列化能够得到 KotlinIr
  4. KotlinIr 经过反编译能够得到代码的细节,这正是在 IDEA 里能看到 .knm 是什么的原因

运用装置 Kotlin Plugin 的 IDEA 检查 knm 文件

初探 Kotlin Multiplatform Mobile 跨平台原理

运用 vim 检查 knm 文件

初探 Kotlin Multiplatform Mobile 跨平台原理

四、iOS 和 KMM 库的关系

iOS 中的依靠库是一组 .h 和二进制文件,所以 KMM 库终究一定要转成 .h 和二进制文件。 KMM 中,iOS 渠道的编译产品是 klib

问题:

  1. Kotlin 是怎样依靠并调用 iOS Objective-C 库的?
  2. iOS 是怎么运用 KMM 库的?

为了解说上面的两个问题,需求了解 KMM 和 OC 互操作的机制(相互调用),以及 klib 是怎么打包

OC 互操作流程

初探 Kotlin Multiplatform Mobile 跨平台原理

  1. Copy iOS 工程中需求用到的 .h 文件(此处也能够直接在 KMM 工程中经过 Cocoapods 插件直接依靠 pod 库)
  2. .h 文件经过 cinterop 东西生成 klib,由于 kotlin 不认识 oc 的 .h,所以需求经过 klib 将 .h 转成 kotlin 认识的形式后才能调用
  3. 将开发完成的 kotlin 代码编译打包,经过 fatFramework 东西输出终究 .h 和二进制文件
  4. iOS 依靠 Umbrella.h 和二进制文件,此流程已经走到 iOS 原生端,和 KMM 无关了

FatFrameWork 流程

初探 Kotlin Multiplatform Mobile 跨平台原理

  1. KMM 工程打包 klib 并上传
  2. KMM_Umbrella (依靠了许多 KMM 库的全家桶工程) 工程拉取 klib 依靠
  3. 履行 iosFatFramework 使命,输出终究 framework.h 和二进制文件
    • klib 中的 ir 经过 kotlin 编译器后端,编译成对应渠道的二进制文件
    • 链接
    • 兼并不同架构的二进制文件,比方 iosArm64 iosX64,详细可参阅【mac】lipo指令详解
    • 兼并头文件
    • 创立 .modulemap 文件,详细细节能够参阅 了解 iOS 中的 Modules
    • 生成 info.plist ,此文件是对 framework 的描绘清单文件
    • 合成 DSYM( Debugger Symbols) 文件

终究输出结构如下

fat-framework
└── debug
    └── KMMUmbrellaFramework.framework
        ├── Headers
        │ └── KMMUmbrellaFramework.h
        ├── Info.plist
        ├── KMMUmbrellaFramework
        └── Modules
            └── module.modulemap

总结

初探 Kotlin Multiplatform Mobile 跨平台原理

  1. 经过在 Common 层界说 expect 接口,生成 .knm,以及相关详细渠道信息的 .module
  2. 在详细渠道经过 actual 完成接口,生成 .klib/.aar/.jar
  3. Android 渠道比较特别,因为 Kotlin 以前只能编译成 JVM 字节码,不存在 ir 概念,K2 Compiler 出现后,统一抽象了编译流程,使得 JVM 也有了自己的编译器后端,也能够经过 IR 编译为 JVM 字节码
  4. iOS 渠道经过 .klib 寄存 ir,然后经过编译器后端打成 iOS 能够运用的 .framework
  5. 将对应产品接入到对应渠道工程

经过对 KMM 编译产品的探究,能让咱们更好地了解 KMM 是怎么完成跨渠道的。

参阅

  • Share code on platforms
  • 了解 iOS 中的 Modules
  • DSYM文件
  • 【mac】lipo指令详解
  • Kotlin 源码