关于android:Android开发ComposeUI如何解决布局嵌套原理解析

1次阅读

共计 5226 个字符,预计需要花费 14 分钟才能阅读完成。

概述

  • 背景:布局层级过多、布局适度的嵌套会导致测量工夫呈指数级增长,最终影响布局性能。
  • 现状: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 源码

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

正文完
 0