概述
- 背景:布局层级过多、布局过度的嵌套会导致测量时间呈指数级增长,最终影响布局性能。
- 现状:Compose没有上述布局嵌套问题,因为它根本上解决了布局层级对布局性能的影响。
- 解决原理:Compose界面只允许一次测量,即随着布局层级的加深,测量时间仅线性增长。
本文将主要说明:
- 布局嵌套过多如何影响布局性能?
- ComposeUI如何解决嵌套问题?
- 为什么ComposeUI可以只允许一次测量?
- ComposeUI测量过程的源码分析
1. 布局嵌套过多如何影响布局性能?
原因主要是:「ViewGroup会对子View进行多次测量」。假设:父布局的布局属性是wrap_content、子布局是match_parent,此时的布局过程是:
- 父布局先以0为强制宽度测量子View、然后继续测量剩下的其他子View
- 再用其他子View里最宽的宽度,二次测量这个match_parent的子 View,最终得出它的尺寸,并把这个宽度作为自己最终的宽度。
即 这个过程就对单个子View进行了二次测量。
「而布局嵌套对性能影响则是指数形式的」,即:父布局会对每个子view做两次测量,子view也会对下面的子view进行两次测量,即相当于是 O(2ⁿ)测量。
2. ComposeUI如何解决嵌套问题?
ComposeUI规定:只允许一次测量,不允许重复测量。即每个父布局只对每个子组件测量一次,即测量复杂度变成了:O(n)。
3. 为什么ComposeUI可以只允许一次测量?
ComposeUI引入了:固有特性测量(Intrinsic Measurement)。即 Compose 允许父组件在对子组件测量前先测量子组件的“固有尺寸”,这相当于上面说的两次测量的 第一次 “粗略测量“。
而这种固定特性测量是对整个组件布局树进行一次测量即可,从而避免了随着层级的加深而增加测量次数。
4. ComposeUI测量过程的源码分析
此处主要分析:固定特性测量的测量过程。此处先介绍LayoutNodeWrapper链构建
4.1 LayoutNodeWrapper
先来看两个核心结论:
- 子View都是以LayoutNode的形式,存在于Parent - children中的
- 给Layout的设置的modifier会以LayoutNodeWrapper链的形式存储在LayoutNode中,然后后续做相应变换
下面说明LayoutNodeWrapper的构建:
- 默认的LayoutNodeWrapper链即由LayoutNode , OuterMeasurablePlaceable, InnerPlaceable 组成
- 当添加了modifier时,LayoutNodeWrapper链会更新,modifier会作为一个结点插入到其中
internal val innerLayoutNodeWrapper: LayoutNodeWrapper = InnerPlaceable(this)private val outerMeasurablePlaceable = OuterMeasurablePlaceable(this, innerLayoutNodeWrapper)override fun measure(constraints: Constraints) = outerMeasurablePlaceable.measure(constraints)override var modifier: Modifier = Modifier set(value) { // …… code field = value // …… code // 创建新的 LayoutNodeWrappers 链 // foldOut 相当于遍历 modifier val outerWrapper = modifier.foldOut(innerLayoutNodeWrapper) { mod /*📍 modifier*/ , toWrap -> var wrapper = toWrap if (mod is OnGloballyPositionedModifier) { onPositionedCallbacks += mod } if (mod is RemeasurementModifier) { mod.onRemeasurementAvailable(this) } val delegate = reuseLayoutNodeWrapper(mod, toWrap) if (delegate != null) { wrapper = delegate } else { // …… 省略了一些 Modifier判断 if (mod is KeyInputModifier) { wrapper = ModifiedKeyInputNode(wrapper, mod).assignChained(toWrap) } if (mod is PointerInputModifier) { wrapper = PointerInputDelegatingWrapper(wrapper, mod).assignChained(toWrap) } if (mod is NestedScrollModifier) { wrapper = NestedScrollDelegatingWrapper(wrapper, mod).assignChained(toWrap) } // 布局相关的 Modifier if (mod is LayoutModifier) { wrapper = ModifiedLayoutNode(wrapper, mod).assignChained(toWrap) } if (mod is ParentDataModifier) { wrapper = ModifiedParentDataNode(wrapper, mod).assignChained(toWrap) } } wrapper } outerWrapper.wrappedBy = parent?.innerLayoutNodeWrapper outerMeasurablePlaceable.outerWrapper = outerWrapper …… }// 假设:给Layout设置一些modifierModifier.size(100.dp).padding(10.dp).background(Color.Blue)
对应的LayoutNodeWrapper链如下图所示
图片
<figcaption style="margin: 5px 0px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; text-align: center; font-size: 13px;">image.png</figcaption>
这样一直链式调用下一个的measure,直到最后一个结点InnerPlaceable,最终调用到了自定义Layout时写的measure()
图片
4.2 固有特性测量-实现原理
结论:LayoutNodeWrapper链中插入了一个Modifier
@Stablefun Modifier.height(intrinsicSize: IntrinsicSize) = when (intrinsicSize) { IntrinsicSize.Min -> this.then(MinIntrinsicHeightModifier) IntrinsicSize.Max -> this.then(MaxIntrinsicHeightModifier)}private object MinIntrinsicHeightModifier : IntrinsicSizeModifier { override fun MeasureScope.measure( measurable: Measurable, constraints: Constraints ): MeasureResult { //正式测量前先根据固有特性测量获得一个约束 val contentConstraints = calculateContentConstraints(measurable, constraints) //正式测量 val placeable = measurable.measure( if (enforceIncoming) constraints.constrain(contentConstraints) else contentConstraints ) return layout(placeable.width, placeable.height) { placeable.placeRelative(IntOffset.Zero) } } override fun MeasureScope.calculateContentConstraints( measurable: Measurable, constraints: Constraints ): Constraints { val height = measurable.minIntrinsicHeight(constraints.maxWidth) return Constraints.fixedHeight(height) } override fun IntrinsicMeasureScope.maxIntrinsicHeight( measurable: IntrinsicMeasurable, width: Int ) = measurable.minIntrinsicHeight(width)}
汇总说明:
- IntrinsicSize.Min其实也是个Modifier
- MinIntrinsicHeightModifier会在测量之间,先调用calculateContentConstraints计算约束
- calculateContentConstraints中则会递归地调用子项的minIntrinsicHeight,并找出最大值,这样父项的高度就确定了
- 固有特性测量完成后,再调用measurable.measure,开始真正的递归测量