Compose 是如何实现跨平台的?
霍丙乾 bennyhuo
讲者简介
分享经历
2017.11 | Android 技术大会 | |
2018.11 | JetBrains 北京开发者大会 | |
2020.5 | GDG Android 11 Meetup | |
2020.10 / 11 | GDG DevFest / 全球移动开发者峰会 | |
2021.7 | GDG 社区说 | |
2021.11 / 12 | GDG DevFest / Kotlin 中文开发者大会 | |
2022.9 | GDG 社区说 | |
2022.10 | GDG DevFest | |
2023.4 | GDG 社区说 | |
2023.5 | KUG 北京 KotlinConf Global | |
2023.6 | Java 核心技术大会 | |
2023.10 | JetBrains 码上道 | |
2023.11 | GDG DevFest | |
2023.12 | 机械工业出版社 | |
2024.2 | GDG 社区说 |
Compose 的发展历程
2019.5 Google IO:首次公开预览
2020.8 Alpha
2021.2 Beta
2021.7 正式发布
2021.8 1.0 alpha
Jetpack Compose
Compose Multiplatform
2023.4 1.4.0 iOS alpha | Web Experimental
2024.5 1.6.10 iOS beta | Web alpha
2020.11 M1
Compose 的整体架构
Compose 的整体架构
Compiler
Runtime
Compose 的整体架构
Compiler
Runtime
语法检查
函数变换
符号导出
组件逻辑
组件渲染
原生混合排版
Compose 的整体架构
Compiler
Runtime
语法检查
函数变换
符号导出
组件逻辑
组件渲染
原生混合排版
Kotlin 跨平台的基本思路
Kotlin 的平台支持
Windows
Linux
macOS
Kotlin Native
WASM
iOS
...
Android
Kotlin JVM
Kotlin JS
Browser
Node.js
Java vm
Android
Browser
Kotlin Compiler
Kotlin 的编译产物
Kotlin Native
WASM
Kotlin JVM
Kotlin JS
JavaScript
WebAssembly
JVM Bytecode
Native Binary
C/C++/...
Java
JavaScript/Rust/...
TypeScript
Kotlin 的源码结构
Kotlin Common
Kotlin-Jvm
Kotlin-Native
Java
C/Objective-C
expect
expect
actual
actual
Kotlin 的源码结构 示例
expect fun getCurrentTimeMilliseconds(): Long
actual fun getCurrentTimeMilliseconds(): Long {
return System.currentTimeMillis()
}
actual fun getCurrentTimeMilliseconds(): Long {
return (NSDate().timeIntervalSince1970 * 1000).toLong()
}
Compose 的跨平台实现
Compose 编译时的跨平台处理
Composable 函数
@JvmInline�value class User(val name: String)��@Composable�fun Greeting(user: User) {� Text(user.name)�}
Composable 函数
参数可变
class KlibAssignableParamTransformer(...)
: AbstractComposeLowering(...), ModuleLoweringPass {� ...�� override fun visitFunction(declaration: IrFunction): IrStatement {� val assignableParams = declaration.valueParameters.filter { it.isAssignable }� val variables = assignableParams.map {� val variable = IrVariableImpl(
...� name = it.name,� type = it.type,� isVar = true,
...� )� variable.parent = declaration� variable.initializer = IrGetValueImpl(..., it.symbol)� variable� }� ...� return super.visitFunction(declaration)� }�}�
参数可变
内联类型
if (type.isInlineClassType()) {� if (context.platform.isJvm()) {� return coerceInlineClasses(this, type, type.unboxInlineClass()).unboxValueIfInline()� } else {� val primaryValueParameter = klass.primaryConstructor?.valueParameters?.get(0)� ...� val fieldGetter = klass.getPropertyGetter(primaryValueParameter!!.name.identifier)� ?: error("Expected a getter")� return irCall(symbol = fieldGetter, dispatchReceiver = this).unboxValueIfInline()� }�}
内联类型
$dirty 变量
changedParam.irCopyToTemporary(� // LLVM validation doesn't allow us to have val here.� isVar = !context.platform.isJvm() && !context.platform.isJs(),� nameHint = "\$dirty",� exactName = true�)
val $dirty = $changed�if ($changed and 0b1110 == 0) {� $dirty = $dirty or if ($composer.changed(name)) 0b0100 else 0b0010�}
$dirty 变量
stability 字段
class Greeting {� private val platform = getPlatform()�� fun greet(): String {� return "Hello, ${platform.name}!"� }�}
stability 字段
stability 字段
private fun IrClass.makeStabilityFieldNonJvm(): IrField {� val stabilityFieldName = this.uniqueStabilityFieldName()� val fieldParent = this.getPackageFragment()�� return context.irFactory.buildField {� ...� }.also { stabilityField ->� stabilityField.parent = fieldParent� makeStabilityProp(stabilityField, fieldParent)� }�}
private fun IrClass.makeStabilityFieldJvm(): IrField {� return context.irFactory.buildField {� ...� }.also { stabilityField ->� stabilityField.parent = this@makeStabilityFieldJvm� }�}
klib 写入稳定性推导注解
class ClassStabilityFieldSerializationPlugin(...) : DescriptorSerializerPlugin {� ...� override fun afterClass(...) {� ...�� if (proto.flags and hasAnnotationFlag == 0) {� proto.flags = proto.flags or hasAnnotationFlag� }�� val parametersValue = classStabilityInferredCollection?.getParametersValue(descriptor)� if (parametersValue != null) {� proto.addExtension(� KlibMetadataSerializerProtocol.classAnnotation,� createAnnotationProto(extension, parametersValue)� )� }� }�}
Compose 运行时的跨平台处理
组件渲染
Hardware
Canvas
OwnedLayer
Compose Node
@Composable fun
组件渲染 - Android
Canvas
OwnedLayer
AndroidCanvas
GraphicsLayerOwnerLayer
RenderNodeLayer
ViewLayer
AndroidComposeView
组件渲染 - iOS
Canvas
OwnedLayer
SkiaBackedCanvas
SkiaLayer
Skia
CAMetalLayer / Metal
混合排版
Native Canvas
Compose Canvas
混合排版 - Android
Native Canvas
Canvas
混合排版 – Android – 示例
Native 控件
Compose 控件
混合排版 – Android – 示例
Native 控件
Compose 控件
混合排版 – Android – 示例
Native Canvas
Native 控件
Compose 控件
混合排版 – Android
val layoutNode: LayoutNode = run {� // Prepare layout node that proxies measure and layout passes to the View.� val layoutNode = LayoutNode()�� val coreModifier = Modifier� .nestedScroll(NoOpScrollConnection, dispatcher)� .semantics(true) {}� .pointerInteropFilter(this)� .drawBehind {� drawIntoCanvas { canvas ->� (layoutNode.owner as? AndroidComposeView)?.drawAndroidView(...)� }� }� .onGloballyPositioned {� layoutAccordingTo(layoutNode)� }
混合排版 – Android
val layoutNode: LayoutNode = run {� // Prepare layout node that proxies measure and layout passes to the View.� val layoutNode = LayoutNode()�� val coreModifier = Modifier� .nestedScroll(NoOpScrollConnection, dispatcher)� .semantics(true) {}� .pointerInteropFilter(this)� .drawBehind {� drawIntoCanvas { canvas ->� (layoutNode.owner as? AndroidComposeView)?.drawAndroidView(...)� }� }� .onGloballyPositioned {� layoutAccordingTo(layoutNode)� }
事件
绘制
布局
混合排版 - iOS
Native Canvas
(UIView)
Compose Canvas
(UIView/CAMetalLayer)
混合排版 – iOS – 示例
Native 控件
Compose 控件
混合排版 – iOS – 示例
Compose Canvas
(UIView/CAMetalLayer)
Native Canvas
(UIView)
Native 控件
Compose 控件
混合排版 – iOS – 示例
Native 控件
Compose 控件
Compose Canvas
(UIView/CAMetalLayer)
UIView
混合排版 – iOS – 示例
Native 控件
Compose 控件
混合排版 – iOS – 示例
Native 控件
Compose 控件
混合排版 – iOS – 示例
Native 控件
Compose 控件
混合排版 – iOS – 示例
Native 控件
Compose 控件
混合排版 – iOS – 示例
Compose Canvas
(UIView/CAMetalLayer)
Native 控件
Compose 控件
混合排版 – iOS – 示例
Native 控件
Compose 控件
混合排版 – iOS – 示例
Compose Canvas
(UIView/CAMetalLayer)
Native 控件
Compose 控件
混合排版 – iOS – 示例
Compose Canvas
(UIView/CAMetalLayer)
Native 控件
Compose 控件
混合排版 – iOS – 示例
Native 控件
Compose 控件
混合排版 – iOS – 示例
Native 控件
Compose 控件
混合排版 – iOS
EmptyLayout(� modifier.onGloballyPositioned { coordinates ->� ...� interopContext.deferAction {� embeddedInteropComponent.wrappingView.setFrame(...)� }� ...� interopContext.deferAction { onResize(...) }� }.drawBehind {� drawRect(Color.Transparent, blendMode = BlendMode.Clear)� }.trackUIKitInterop(embeddedInteropComponent.wrappingView).let {� if (interactive) it.then(InteropViewCatchPointerModifier()) else it� }
...�)
事件响应区域
绘制透明区域
布局
混合排版 – iOS – 缺陷
Native 控件
Compose 控件
混合排版 – iOS – 缺陷
Native 控件
Compose 控件
混合排版 – iOS – 缺陷
Native 控件
Compose 控件
不支持 Native 控件有透明区域!
其他问题
参考资料
敬请期待
关注我
😃 🧐
谢谢大家