Kotlin Multiplatform 原理深入分析
liuian 2025-10-02 03:18 51 浏览
什么是 KMP
KMP(Kotlin multiplatform)是 Kotlin 语言的一项重要特性,允许将 kotlin 代码运行在不同平台上,通过『一码多端』的方式来节省成本。
而与诸如 Java / React 这类跨端方案不同,KMP 没有采用所谓的虚拟机的思路,而是选择直接将 kotlin 源码编译成目标平台代码运行的方案。
KMP 的优势和限制
KMP 的优势:
相较传统的跨平台框架而言,由于 Kotlin 会将代码编译成目标平台原生代码执行(可以简单理解为将 Kotlin 源码翻译成 java/c++/js 代码),其最大的优势在于进行 FFI(跨语言调用)时几乎没有性能折损,并且执行性能接近于原生系统。
KMP 的限制:
由于早期的 kotlin 是基于 java / android 平台,这些 kotlin 二/三方库在设计时候也不可能考虑过跨平台。考虑到这些情况,kotlin 在编译时使用了 Target Platform概念,即 kotlin 每个类 / 方法 都是有对应平台的,早期的的 java / android 二三方库只属于 jvm 平台,意味只能在 java / android 平台调用,在其他平台上调用会编译报错。
而对于系统接口,在 KMP 下也是有对应平台限制的,一个简单的判定方法如下:
所有 kotlin.*、kotlinx.*包名的接口,都是跨平台的。
所有 java.*、sun.* 包名的接口,只能在 jvm 平台使用。
所有 android.*包名的接口,只能在 android 平台使用。
androidx.*比较特殊,部分库可以(比如 Room),部分不行,需要自行查看文档判断。
因此,如果想要将 Android 代码通过 KMP 直接编译成其他平台产物,那基本上是不可能直接成功的。如果没有提前设计隔离层的话,工程中二、三方依赖,以及源码中几乎不可避免的含有 Android / jvm 的平台接口,你很可能需要进行大量的抽象改造才可以完成。
KMP 实现原理
跨平台概述
前面提到,KMP 核心思路,是直接将 kotlin 源码编译成目标平台代码运行。而实现这一能力的关键就是 Kotlin 编译器,其核心职责就是将源码翻译成目标平台代码。
在实现上,kotlin 编译器使用了前后端分离的思路。
简单来说,前端负责语法解析&代码分析、后端负责将前端产物翻译成目标平台代码,二者职责清晰。未来如果如果需要支持一个新平台,添加一个新后端即可。
至于 Optimizer,由于不同目标平台的优化方式不同,在 kotlin 编译器中被放在了后端中。
Kotlin Native 编译器
由于 Kotlin Jvm 大家相对比较熟悉,而 Kotlin JS 笔者还没有看,因此本文只着重介绍 Kotlin Native 的相关分析
编译流程
Kotlin Native 编译入口通常为 Gradle Task 或者命令行(konanc),二者最终执行代码是共通的,最终会根据根据产物类型不同执行不同逻辑。
产物分为四类:
Klib:Kotlin Native Library,可以简单理解为 Kotlin Native 版本的 jar / aar,只保存了 kotlin ir 信息。
ObjCFramework:给 iOS 使用的 .framework.
Binary:缓存 / 可执行文件。
CLibrary:动态库 / 静态库。
produce(Klib)
Compile 作用是将 kt 编译成 Klib(可以类比为 aar)。
Klib 解开后结构如下:
所有 KN 模块在编译阶段都会先编译成 Klib,在 link 阶段才会调用 c++ 工具链处理。
produce(Binary/CLibrary/ObjCFramework)
这三个基本流程都差不多,都是将多个 Klib 聚合编译成一个二进制库(类似于 C 的 link、或者 android 的打 apk),区别在于产物不同。核心为编译器后端处理,用于将 kotlin ir 转换为目标平台的二进制库,核心流程如下:
各步骤说明:
1. Add entry points:如果编译可执行文件,就加一个入口文件的 ir file,比较简单。
2. Lowering module && dependencies:将所有依赖库合并,并针对合并后的每个 ir 文件(包括依赖的库的 ir)执行 Lowerings(对 Ir 进行前置优化,比如内联,语法糖处理),每个 lowering 文件需要执行 51 步,每一步都可以在 NativeLoweringPhases.kt 中找到对应的定义。
3. Run after lowering:即真正的 Native 编译流程,主要通过 llvm 将 kotlin IR 翻译为二进制产物,主要步骤:
CodeGen:将 kotlin ir 『翻译』成 llvm IR,这部分主要通过调用 llvm 的 c 函数实现
Generate Export Api + Compile Export Api:生成一个对外 api 的 c++ 接口文件并编译,用于暴露接口给外部调用。
Post Processing :在和底层依赖库(Runtime)的 bit code 链接前,做一些优化工作,比如去除无用代码。
Write BitCode:将所有 bitcode 链接完毕后,生成 out.bc
Compile and link:
调用 clang 将 .bc 编译成 .o,这里会根据 debug / release 添加不同编译参数。
调用 lld 将 .o 文件 link 成目标平台汇编代码
IR 转换
假设有如下源码:
package com.demo.kmpclassHelloWorld{funhelloFun1(a: Int, b: Int): Int {return a + b}}
其编译后的 llvm ir 长这样:
除开一些流转指令、调试指令外、其翻译回 C / C++ 代码大概是这样。
// 没错这个函数名就是这么长int"kfun:com.demo.kmp.HelloWorld#helloFun1(kotlin.Int;kotlin.Int){}kotlin.Int"(*struct.ObjHeader this,int a,int b) {return a + b;}
可以看出和用 C / C++ 写的代码基本上差不多,所以执行效率是非常高的(相当于写 C / C++ 代码去运行)。
其主要的『翻译』逻辑如下:
kotlin 基础类型会『翻译』为对应的 C 的基本数据类型,如: int / float / double / short / long / double。
Kotlin 类会『翻译』成 llvm typeInfo 形式,用来记录类名等信息。
Kotlin 对象会『翻译』成 ObjHeader + 一段内存空间形式,前者用于记录 typeinfo,后者用来存放所有的类字段。
Kotlin 函数会『翻译』成 C 函数,差别在于会多一个 ObjHeader* 参数,用作 $this 指针。
Kotlin 属性会『翻译』成 Get/Set 函数,这个跟 java 是一致的。
Kotlin 运算符会『翻译』成对应的 operator 函数(举例来说,加号(+)会翻译成 add 函数),一些类型(比如基础类型)会进一步通过内联翻译成 C 的运算符。
其余类型则不再赘述,有兴趣可以自行参考源码(位于ir2bitcode.kt)实现。
Kotlin Native 运行时
为了实现内存的自动回收,在 Kotlin Native 平台上,会打包一套 Kotlin Runtime到最终产物中,包含异常处理、线程管理、内存管理等常规能力。
运行时包括如下几个部分,创建线程或者已存在的线程都可以 initRuntime
SetKonanTerminateHandler 为线程设置异常处理Handler,这样可以捕获kotlin excepiton
globalData 初始化全局变量
theaddata 初始化线程内存分配器
workInit 初始化线程消息队列,用于执行协程
和 android 相比,kmp 运行时不支持 synchronized 关键字,可以使用 atomicFu 来解决。
内存管理
Kotlin Native 有 3 种内存分配器:
custom:kotlin 自己开发的内存分配器,也是默认的内存分配器
std:标准库内存分配器,在鸿蒙上是 jemalloc
mimalloc:微软开源的 native 分配器
目前 std/mimalloc 在最新版本已经去掉了,kmp 未来会持续优化 custom 内存分配器
custom 内存分配器是 kotlin 自己实现的内存分配器,包括几个部分
Safealloc mmap 虚拟内存,每次大小256k,分配后检查是否需要触发 alloc gc
CreateObject 分配对象,每个对象额外增加16字节内存,包括 objectData/objectHeader
CreateObject 分配对象时,如果类(typeInfo)加了 TF_HAS_FINALIZER 标记,会通过 extraObject 增加对象弱引用,gc 后调用对象 finialize 方法,objectHeader 指向 extraObject
CreateArray 分配 array,每个 array 额外增加24字节内存,包括 objectData/ArrayAHeader,ArrayHeader 12字节按照8字节对齐到16字节
和 android 相比,有 3 点不同:
Kotlin Native 只支持 Weakreference,不支持 SoftReference
Kotlin Native 对象分配支持逃逸分析,除了在堆上分配,还可以在编译时通过静态代码分析决定哪些变量在栈上分配
Kotlin Native 把 Array 类型单独拿出来了,Android 认为所有类型都是 Object
基础类型
基础类型包括
Byte/Short/Int/Float/String 等,和 android 一致
对象类型
class 包括几部分
instanceSize_:对象大小,如果是 array,instanceSize_ 为每个元素大小
superType: 父类
objOffsets:成员变量 offset 数组,根据 offset 查找成员变量
objOffsetCount_:成员变量数量
interfaceTableSize:interface 数量
interfaceTable:interface 表,指向 interface 实现
和 android 相比,Kotlin Native 将 interface 方法和 abstract 方法都通过 interfacetable 存储,android 是分开存储的
内存回收(GC)
GC 有三种类型,默认 pcms,cms 需要手动配置
cms 是并发标记的,只在遍历 gc root 时暂停线程,性能最好
Stms 需要 stop the world 暂停线程,性能很差
默认 pcms 可以支持多线程 gc,也会 stop the world 暂停线程
由于 cms 性能最好,目前 KMP 项目里面默认使用 cms
cms 类型主要包括几个功能,在在 gc root 收集完成后,会 resume the world 唤醒线程
StopTheWord 所有线程将线程暂停执行
collectRootSet 收集 gc root
resumeTheWorld 唤醒线程
Mark 会根据 gc root 标记存活对象
processWeaks 处理 weakReference
heap.Sweep 释放非存活对象
finalizerProcessor 调用对象 finialize 方法,之前会收集所有线程的 finalize 对象
和 android 相比
heap 默认10M,android 是大对象/小对象各512M,导致比较容易触发 alloc gc,目前已经优化
concurrent gc 通过定时10s触发实现,在空闲时容易造成 cpu 浪费,目前已经优化
cms 目前不会做内存碎片整理,会导致内存占用过高,目前在优化中
cms mark 阶段产生的对象都是存活对象
gc 不支持分代,目前已经优化
小结
Kotlin Multiplatform 在经历了这么多年迭代后,目前现在已经是一个相对成熟的解决方案了。虽然在内存管理方案还有一些瑕疵,但其『IR 翻译成 Native』设计理念使得整个系统的性能上限很高,理论上能达到接近原生的执行性能。而 Jetbrain 的号召力也使得整个研发生态非常有想象力,目前 androidx 已经在开始逐步适配 KMP 中,可以预见的将来会非常有潜力。
相关推荐
-
- 驱动网卡(怎么从新驱动网卡)
-
网卡一般是指为电脑主机提供有线无线网络功能的适配器。而网卡驱动指的就是电脑连接识别这些网卡型号的桥梁。网卡只有打上了网卡驱动才能正常使用。并不是说所有的网卡一插到电脑上面就能进行数据传输了,他都需要里面芯片组的驱动文件才能支持他进行数据传输...
-
2026-01-30 00:37 liuian
- win10更新助手装系统(微软win10更新助手)
-
1、点击首页“系统升级”的按钮,给出弹框,告诉用户需要上传IMEI码才能使用升级服务。同时给出同意和取消按钮。华为手机助手2、点击同意,则进入到“系统升级”功能华为手机助手华为手机助手3、在检测界面,...
- windows11专业版密钥最新(windows11专业版激活码永久)
-
Windows11专业版的正版密钥,我们是对windows的激活所必备的工具。该密钥我们可以通过微软商城或者通过计算机的硬件供应商去购买获得。获得了windows11专业版的正版密钥后,我...
-
- 手机删过的软件恢复(手机删除过的软件怎么恢复)
-
操作步骤:1、首先,我们需要先打开手机。然后在许多图标中找到带有[文件管理]文本的图标,然后单击“文件管理”进入页面。2、进入页面后,我们将在顶部看到一行文本:手机,最新信息,文档,视频,图片,音乐,收藏,最后是我们正在寻找的[更多],单击...
-
2026-01-29 23:55 liuian
- 一键ghost手动备份系统步骤(一键ghost 备份)
-
步骤1、首先把装有一键GHOST装系统的U盘插在电脑上,然后打开电脑马上按F2或DEL键入BIOS界面,然后就选择BOOT打USDHDD模式选择好,然后按F10键保存,电脑就会马上重启。 步骤...
- 怎么创建局域网(怎么创建局域网打游戏)
-
1、购买路由器一台。进入路由器把dhcp功能打开 2、购买一台交换机。从路由器lan端口拉出一条网线查到交换机的任意一个端口上。 3、两台以上电脑。从交换机任意端口拉出网线插到电脑上(电脑设置...
- 精灵驱动器官方下载(精灵驱动手机版下载)
-
是的。驱动精灵是一款集驱动管理和硬件检测于一体的、专业级的驱动管理和维护工具。驱动精灵为用户提供驱动备份、恢复、安装、删除、在线更新等实用功能。1、全新驱动精灵2012引擎,大幅提升硬件和驱动辨识能力...
- 一键还原系统步骤(一键还原系统有哪些)
-
1、首先需要下载安装一下Windows一键还原程序,在安装程序窗口中,点击“下一步”,弹出“用户许可协议”窗口,选择“我同意该许可协议的条款”,并点击“下一步”。 2、在弹出的“准备安装”窗口中,可...
- 电脑加速器哪个好(电脑加速器哪款好)
-
我认为pp加速器最好用,飞速土豆太懒,急速酷六根本不工作。pp加速器什么网页都加速,太任劳任怨了!以上是个人观点,具体性能请自己试。ps:我家电脑性能很好。迅游加速盒子是可以加速电脑的。因为有过之...
- 任何u盘都可以做启动盘吗(u盘必须做成启动盘才能装系统吗)
-
是的,需要注意,U盘的大小要在4G以上,最好是8G以上,因为启动盘里面需要装系统,内存小的话,不能用来安装系统。内存卡或者U盘或者移动硬盘都可以用来做启动盘安装系统。普通的U盘就可以,不过最好U盘...
- u盘怎么恢复文件(u盘文件恢复的方法)
-
开360安全卫士,点击上面的“功能大全”。点击文件恢复然后点击“数据”下的“文件恢复”功能。选择驱动接着选择需要恢复的驱动,选择接入的U盘。点击开始扫描选好就点击中间的“开始扫描”,开始扫描U盘数据。...
- 系统虚拟内存太低怎么办(系统虚拟内存占用过高什么原因)
-
1.检查系统虚拟内存使用情况,如果发现有大量的空闲内存,可以尝试释放一些不必要的进程,以释放内存空间。2.如果系统虚拟内存使用率较高,可以尝试增加系统虚拟内存的大小,以便更多的应用程序可以使用更多...
-
- 剪贴板权限设置方法(剪贴板访问权限)
-
1、首先打开iphone手机,触碰并按住单词或图像直到显示选择选项。2、其次,然后选取“拷贝”或“剪贴板”。3、勾选需要的“权限”,最后选择开启,即可完成苹果剪贴板权限设置。仅参考1.打开苹果手机设置按钮,点击【通用】。2.点击【键盘】,再...
-
2026-01-29 21:37 liuian
- 平板系统重装大师(平板重装win系统)
-
如果你的平板开不了机,但可以连接上电脑,那就能好办,楼主下载安装个平板刷机王到你的个人电脑上,然后连接你的平板,平板刷机王会自动识别你的平板,平板刷机王上有你平板的我刷机包,楼主点击下载一个,下载完成...
- 联想官网售后服务网点(联想官网售后服务热线)
-
联想3c服务中心是联想旗下的官方售后,是基于互联网O2O模式开发的全新服务平台。可以为终端用户提供多品牌手机、电脑以及其他3C类产品的维修、保养和保险服务。根据客户需求层次,联想服务针对个人及家庭客户...
- 一周热门
-
-
用什么工具在Win中查看8G大的log文件?
-
如何在 Windows 10 或 11 上通过命令行安装 Node.js 和 NPM
-
如何修改图片拍摄日期?快速修改图片拍摄日期的6种方法
-
5步搞定动态考勤表!标记节假日、调休日?Excel自动变色!
-
RK3588-HDMIRX(瑞芯微rk3588芯片手册)
-
用纯Python轻松构建Web UI:Remi 动态更新,实时刷新界面内容
-
tplink无线路由器桥接教程(tplink路由器如何进行无线桥接)
-
R语言 | CNS绘图第1款——linkET万物皆可连
-
都说Feign是RPC,没有侵入性,为什么我的代码越来越像 C++
-
windows11专业版密钥最新(windows11专业版激活码永久)
-
- 最近发表
- 标签列表
-
- python判断字典是否为空 (50)
- crontab每周一执行 (48)
- aes和des区别 (43)
- bash脚本和shell脚本的区别 (35)
- canvas库 (33)
- dataframe筛选满足条件的行 (35)
- gitlab日志 (33)
- lua xpcall (36)
- blob转json (33)
- python判断是否在列表中 (34)
- python html转pdf (36)
- 安装指定版本npm (37)
- idea搜索jar包内容 (33)
- css鼠标悬停出现隐藏的文字 (34)
- linux nacos启动命令 (33)
- gitlab 日志 (36)
- adb pull (37)
- python判断元素在不在列表里 (34)
- python 字典删除元素 (34)
- vscode切换git分支 (35)
- python bytes转16进制 (35)
- grep前后几行 (34)
- hashmap转list (35)
- c++ 字符串查找 (35)
- mysql刷新权限 (34)
