Kotlin Multiplatform 原理深入分析
liuian 2025-10-02 03:18 46 浏览
什么是 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 中,可以预见的将来会非常有潜力。
相关推荐
-
- 如何下载ps软件免费版(ps如何下载免费版本)
-
1.在搜索引擎中输入adobe并点击搜索。2.点击adobe官网。3.点击支持并点击下载与安装。4.点击开始免费试用并点击下载。5.打开文件夹并点击打开进行安装即可。6.根据以上步骤即可下载安装ps。AdobePhotoshopCS6号...
-
2026-01-14 05:37 liuian
- 怎么判断该换硅脂了(cpu硅脂干了影响真的很大吗)
-
方法步骤如下第一,从系统软件的运行上来看,如果在运行某些大型软件,容易导致显卡发热的程序时,出现画面掉帧,或卡顿,甚至是画面卡死等情况,这大多是因为显卡散热出现问题,导致显卡自动降频,以降低功耗来减少...
- 壁纸图片2025最新款(电脑桌面壁纸图片2025最新款)
-
要更换2023最新款壁纸图片,可以按照以下步骤操作:首先,找到您想要更换的壁纸图片并下载到您的设备上。其次,进入您的设备设置,找到“壁纸”或“桌面壁纸”选项,并点击进入。然后,选择“更换壁纸”并在相册...
- 清理垃圾的神器(清理垃圾的神器是什么)
-
1、《腾讯手机管家》这款可以帮助用户进行强力的清理,加速告别空间卡顿,缓慢延迟的问题的软件当中,用户可以随时随地登录软件进行自动清理和自动清理,自动清理包括图片,视频,语音文件在内的各种换成文件,为手...
- 苹果笔记本怎样重装系统(苹果笔记本怎样重装系统还原)
-
苹果笔记本电脑系统可以通过以下步骤进行重装:1.备份数据:在开始重装前,需要备份你的重要数据。你可以将数据存储到外部硬盘、云存储或其他可靠的设备中。2.下载安装器:从AppStore中下载macOS...
- 手机wifi打不开怎么办
-
手机wifi打不开的原因,可能集中在该手机出现了手机文件丢失、手机版本不稳定、手机文件出错以及手机wifi模块摔坏等故障造成的。手机wifi打不开修复教程1.wcnss_qcom_cfg文件丢失导...
- bios恢复出厂设置后无法开机
-
可通过进入BIOS界面设置bios恢复出厂设置的方法解决,步骤如下:1、通过按Delete或数字键盘中的Del键进入BIOS。2、按箭头键输入并将光标移动到“加载设置默认值”项,然后按enter确认。...
- 电脑硬盘打不开怎么办(电脑硬盘打不开怎么办)
-
电脑硬盘坏了是不能开机的。硬盘坏道的修复方法:1、逻辑坏道的修复对于逻辑坏道,Windows自带的“磁盘扫描程序(Scandisk)”就是最简便常用的解决手段。如果硬盘出现了坏道,我们可在Window...
- linux系统备份与还原工具(linux系统备份与还原工具在哪)
-
用GHOST对LINUX系统做备份1:要求将安装了LINUX系统的硬盘(原盘)整盘刻至另一硬盘(目标盘)。2:所需工具:DOS系统引导盘,GHOST2003(版本低的对文件格式不能很好的支持),原盘(...
- pdf怎么转换成xml格式(如何将pdf格式转换成xml格式)
-
将PDF转换为XML需要使用专业的PDF转换工具。以下是一些常用的PDF转XML工具:1.AdobeAcrobatDC:AdobeAcrobatDC是一款功能强大的PDF编辑软件,其中包括P...
- windows7iso文件(iso文件 win7)
-
利用winrar可以直接打开iso文件,如果双击不能直接打开需要设置winrar,步骤如下:1、启动winrar,点击选项菜单设置命令;2、点击综合选项卡,点击全部选择,点击确定即可。具体操作方法步骤...
- 路由器ip地址是什么意思(路由器的ip地址是)
-
路由器IP地址是指连接到互联网的路由器在局域网内的唯一标识符,一般为192.168.1.1或192.168.0.1等地址。通过路由器IP地址,用户可以通过浏览器等工具登录到路由器管理界面,进行网络设置...
-
- mediaplayer播放记录在哪里(mediaplayer历史记录)
-
《WindowsMediaPlayer》无法播放该文件,表示《WindowsMediaPlayer》目前的版本不支持该视频的格式编码。解决方法: 1.如果安装的是正版操作系统,点帮助→检查更新,稍待片刻,WindowsMed...
-
2026-01-14 02:37 liuian
- 电脑xp怎么换系统win7(电脑xp系统换win7教程)
-
第一种方法:自助安装win7系统 我们在进行自助安装win7系统之前我们要保证我们的电脑是联网的。为了能更加顺利的完成对xp系统的升级,我们的电脑最好是能高速上网的,只有能联网我们才可以下载最新的系...
- appstore官方网站(appstore.apple.com)
-
Appstore即applicationstore,通常理解为应用商店。Appstore是苹果公司基于iPhone的软件应用商店,向iPhone的用户提供第三方的应用软件服务,这是苹果开创的一...
- 一周热门
-
-
飞牛OS入门安装遇到问题,如何解决?
-
如何在 iPhone 和 Android 上恢复已删除的抖音消息
-
Boost高性能并发无锁队列指南:boost::lockfree::queue
-
大模型手册: 保姆级用CherryStudio知识库
-
用什么工具在Win中查看8G大的log文件?
-
如何在 Windows 10 或 11 上通过命令行安装 Node.js 和 NPM
-
威联通NAS安装阿里云盘WebDAV服务并添加到Infuse
-
Trae IDE 如何与 GitHub 无缝对接?
-
idea插件之maven search(工欲善其事,必先利其器)
-
如何修改图片拍摄日期?快速修改图片拍摄日期的6种方法
-
- 最近发表
- 标签列表
-
- 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)
