概述

  • 背景:布局层级过多、布局适度的嵌套会导致测量工夫呈指数级增长,最终影响布局性能。
  • 现状:Compose没有上述布局嵌套问题,因为它基本上解决了布局层级对布局性能的影响。
  • 解决原理:Compose界面只容许一次测量,即随着布局层级的加深,测量工夫仅线性增长。

本文将次要阐明:

  1. 布局嵌套过多如何影响布局性能?
  2. ComposeUI如何解决嵌套问题?
  3. 为什么ComposeUI能够只容许一次测量?
  4. ComposeUI测量过程的源码剖析

1. 布局嵌套过多如何影响布局性能?

起因次要是:ViewGroup会对子View进行屡次测量。假如:父布局的布局属性是wrap_content、子布局是match_parent,此时的布局过程是:

  1. 父布局先以0为强制宽度测量子View、而后持续测量剩下的其余子View
  2. 再用其余子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)}

汇总阐明:

  1. IntrinsicSize.Min其实也是个Modifier
  2. MinIntrinsicHeightModifier会在测量之间,先调用calculateContentConstraints计算束缚
  3. calculateContentConstraints中则会递归地调用子项的minIntrinsicHeight,并找出最大值,这样父项的高度就确定了
  4. 固有个性测量实现后,再调用measurable.measure,开始真正的递归测量

至此,对于Compose UI解决布局嵌套层级问题及其原理解说结束。

最初

给大家分享我收集的Android源码解析学习材料,心愿对你有用,期待与大家一起提高

1.深刻解析微信 MMKV 源码

2.深刻解析阿里巴巴路由框架 ARouter
源码

3.深刻解析 AsyncTask 源码(一款
Android 内置的异步工作执行库)

4.深刻解析 Volley 源码(一款 Google
推出的网络申请框架)

5.深刻解析 Retrofit 源码

6.深刻解析 OkHttp 源码

因为文章篇幅无限,内容细节比拟多,须要学习材料的敌人能够点击这里收费获取哦!