本文正在参与「金石计划」
一、布景
本文会尝试经过 KMM 编译产品了解一套 kt 代码是如安在多个渠道复用的。
KMM 发流程简介
我以开发一个 KMM 日志库为例,简单介绍开发流程是什么:
- 在 CommonMain 界说接口,用
expect
关键字修饰,表明此接口在不同渠道的完成不一样。 - 在详细渠道完成接口,并用
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)
}
- 编译、打包、发布
- 依靠详细渠道库房
- 假如宿主为 Android App,则依靠对应的
kmm-infra-android
- 假如宿主为 iOS App,需求现将
kmm-infra-iosarm64
打包成 Framework,然后 iOS 依靠 Framework - 假如宿主为 KMM 库,则依靠
kmm-infra
- 假如宿主为 Android App,则依靠对应的
二、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 文件
- klib 的文件结构是怎样的?
- .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
- .kt 经过编译器 frontend, 生成 kotlinIr
- 经过 protobuf 序列化后,生成 .knm 文件,这也解说了 vim 打开是乱码的原因
- .knm 经过反序列化能够得到 KotlinIr
- KotlinIr 经过反编译能够得到代码的细节,这正是在 IDEA 里能看到 .knm 是什么的原因
运用装置 Kotlin Plugin 的 IDEA 检查 knm 文件
运用 vim 检查 knm 文件
四、iOS 和 KMM 库的关系
iOS 中的依靠库是一组 .h 和二进制文件,所以 KMM 库终究一定要转成 .h 和二进制文件。 KMM 中,iOS 渠道的编译产品是 klib
问题:
- Kotlin 是怎样依靠并调用 iOS Objective-C 库的?
- iOS 是怎么运用 KMM 库的?
为了解说上面的两个问题,需求了解 KMM 和 OC 互操作的机制(相互调用),以及 klib 是怎么打包
OC 互操作流程
- Copy iOS 工程中需求用到的 .h 文件(此处也能够直接在 KMM 工程中经过 Cocoapods 插件直接依靠 pod 库)
- .h 文件经过
cinterop
东西生成 klib,由于 kotlin 不认识 oc 的 .h,所以需求经过 klib 将 .h 转成 kotlin 认识的形式后才能调用 - 将开发完成的 kotlin 代码编译打包,经过
fatFramework
东西输出终究 .h 和二进制文件 - iOS 依靠 Umbrella.h 和二进制文件,此流程已经走到 iOS 原生端,和 KMM 无关了
FatFrameWork 流程
- KMM 工程打包 klib 并上传
- KMM_Umbrella (依靠了许多 KMM 库的全家桶工程) 工程拉取 klib 依靠
- 履行 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
总结
- 经过在 Common 层界说
expect
接口,生成 .knm,以及相关详细渠道信息的 .module - 在详细渠道经过
actual
完成接口,生成 .klib/.aar/.jar - Android 渠道比较特别,因为 Kotlin 以前只能编译成 JVM 字节码,不存在 ir 概念,K2 Compiler 出现后,统一抽象了编译流程,使得 JVM 也有了自己的编译器后端,也能够经过 IR 编译为 JVM 字节码
- iOS 渠道经过 .klib 寄存 ir,然后经过编译器后端打成 iOS 能够运用的 .framework
- 将对应产品接入到对应渠道工程
经过对 KMM 编译产品的探究,能让咱们更好地了解 KMM 是怎么完成跨渠道的。
参阅
- Share code on platforms
- 了解 iOS 中的 Modules
- DSYM文件
- 【mac】lipo指令详解
- Kotlin 源码