Android中布局层级过深为什么会对性能有影响?为什么Compose没有布局嵌套...

Android中布局层级过深为什么会对性能有影响?为什么Compose没有布局嵌套...

2023年6月28日发(作者:)

Android中布局层级过深为什么会对性能有影响?为什么Compose没有布局嵌套问题?前⾔做过布局性能优化的同学都知道,为了优化界⾯加载速度,要尽可能的减少布局的层级。这主要是因为布局层级的增加,可能会导致测量时间呈指数级增长。⽽Compose却没有这个问题,它从根本上解决了布局层级对布局性能的影响:

Compose界⾯只允许⼀次测量。这意味着随着布局层级的加深,测量时间也只是线性增长的.下⾯我们就⼀起来看看Compose到底是怎么只测量⼀次就把活给⼲了的,本⽂主要包括以下内容:1. 布局层级过深为什么影响性能?2.

Compose为什么没有布局嵌套问题?3.

Compose测量过程源码分析1. 布局层级过深为什么影响性能?我们总说布局层级过深会影响性能,那么到底是怎么影响的呢?主要是因为在某些情况下ViewGroup会对⼦View进⾏多次测量举个例⼦ 1.

LinearLayout宽度为wrap_content,因此它将选择⼦View的最⼤宽度为其最后的宽度2. 但是有个⼦View的宽度为match_parent,意思它将以LinearLayout的宽度为宽度,这就陷⼊死循环了3. 因此这时候,

LinearLayout 就会先以0为强制宽度测量⼀下⼦View,并正常地测量剩下的其他⼦View,然后再⽤其他⼦View⾥最宽的那个的宽度,⼆次测量这个match_parent的⼦

View,最终得出它的尺⼨,并把这个宽度作为⾃⼰最终的宽度。4. 这是对单个⼦View的⼆次测量,如果有多个⼦View写了match_parent ,那就需要对它们每⼀个都进⾏⼆次测量。5. 除此之外,如果在LinearLayout中使⽤了weight会导致测量3次甚⾄更多,重复测量在Android中是很常见的上⾯介绍了为什么会出现重复测量,那么会有什么影响呢?不过是多测量了⼏次,会对性能有什么⼤的影响吗?之所以需要避免布局层级过深是因为它对性能的影响是指数级的1. 如果我们的布局有两层,其中⽗View会对每个⼦View做⼆次测量,那它的每个⼦View⼀共需要被测量 2 次2. 如果增加到三层,并且每个⽗View依然都做⼆次测量,这时候最下⾯的⼦View被测量的次数就直接翻倍了,变成 4 次3. 同理,增加到 4 层的话会再次翻倍,⼦ View 需要被测量 8 次也就是说,对于会做⼆次测量的系统,层级加深对测量时间的影响是指数级的,这就是Android官⽅⽂档建议我们减少布局层级的原因2.

Compose为什么没有布局嵌套问题?我们知道,Compose只允许测量⼀次,不允许重复测量。如果每个⽗组件对每个⼦组件只测量⼀次,那就直接意味着界⾯中的每个组件只会被测量⼀次这样即使布局层级加深,测量时间却没有增加,把组件加载的时间复杂度从O(2ⁿ) 降到了

O(n)。那么问题就来了,上⾯我们已经知道,多次测量有时是必要的,但是为什么Compose不需要呢?Compose中引⼊了固有特性测量(Intrinsic Measurement)固有特性测量即Compose允许⽗组件在对⼦组件进⾏测量之前,先测量⼀下⼦组件的「固有尺⼨」我们上⾯说的,ViewGroup的⼆次测量,也是先进⾏这种「粗略测量」再进⾏最终的「正式测量」,使⽤固有特性测量可以产⽣同样的效果⽽使⽤固有特性测量之所以有性能优势,主要是因为其不会随着层级的加深⽽加倍,固有特性测量也只进⾏⼀次Compose会先对整个组件树进⾏⼀次Intrinsic测量,然后再对整体进⾏正式的测量。这样开辟两个平⾏的测量过程,就可以避免因为层级增加⽽对同⼀个⼦组件反复测量所导致的测量时间的不断加倍了。总结成⼀句话就是,在Compose⾥疯狂嵌套地写界⾯,和把所有组件全都写进同⼀层⾥⾯,性能是⼀样的!所以Compose没有布局嵌套问题2.1 固有特性测量使⽤假设我们需要创建⼀个可组合项,该可组合项在屏幕上显⽰两个⽤分隔线隔开的⽂本,如下所⽰:为了实现分隔线与最⾼的⽂本⼀样⾼,我们可以怎么做呢?@Composablefun TwoTexts( text1: String, text2: String, modifier: Modifier = Modifier) { Row(modifier = ()) { Text( modifier = Modifier .weight(1f) .padding(start = ) .wrapContentWidth(), text = text1 ) Divider( color = , modifier = Modifier .fillMaxHeight() .width() ) Text( modifier = Modifier .weight(1f) .padding(end = ) .wrapContentWidth(), text = text2 ) }}注意,这⾥给Row的height设置为了,会递归查询它⼦项的最⼩⾼度,其中两个Text的最⼩⾼度即⽂本的宽度,⽽Divider的最⼩⾼度为0 因此最后Row的⾼度即为最长的⽂本的⾼度,⽽Divider的⾼度为fillMaxHeight,也就跟最⾼的⽂本⼀样⾼了如果我们这⾥不设置⾼度为的话,Divider的⾼度是占满屏幕的,如下所⽰3.

Compose测量过程源码分析上⾯我们介绍了固有特性测量是什么,及固有特性测量的使⽤,下⾯我们来看看Compose的测量究竟是怎么实现的3.1 测量⼊⼝我们知道,在Compose中⾃定义Layout是通过Layout⽅法实现的@Composable inline fun Layout( content: @Composable () -> Unit, modifier: Modifier = Modifier, measurePolicy: MeasurePolicy)主要传⼊3个参数1.

content:⾃定义布局的⼦项,我们后续需要对它们测量和定位2.

modifier: 对Layout添加的⼀些修饰modifier3.

measurePolicy: 即测量规则,这个是我们主要需要处理的地⽅measurePolicy中主要有五个接⼝fun interface MeasurePolicy { fun e(measurables: List,constraints: Constraints): MeasureResult fun rinsicWidth(measurables: List,height: Int): Int fun rinsicHeight(measurables: List,width: Int): Int fun rinsicWidth(measurables: List,height: Int): Int fun rinsicHeight(measurables: List,width: Int): Int}可以看出:1. 使⽤固有特性测量的时候,会调⽤对应的IntrinsicMeasureScope⽅法,如使⽤(),就会调⽤minIntrinsicHeight⽅法2. ⽗项测量⼦项时,就是在e⽅法中调⽤e(constraints),但是具体是怎么实现的呢?我们来看个例⼦@Composablefun MeasureTest() { Row() { Layout(content = { }, measurePolicy = { measurables, constraints -> h { e(constraints) } layout(100, 100) { } }) }}⼀个简单的例⼦,我们在measure⽅法中打个断点,如下图所⽰:1. 如下图所⽰,是由Row的MeasurePolicy中开始测量⼦项,rowColumnMeasurePolicy我们定义为ParentPolicy2. 然后调⽤到LayoutNode,OuterMeasurablePlaceable,InnerPlaceable的measure⽅法3. 最后再由InnerPlaceable中调⽤到⼦项的MeasurePolicy,即我们⾃定义Layout实现的部分,我们定义它为ChildPolicy4. ⼦项中也可能会测量它的⼦项,在这种情况下它就变成了⼀个ParentPolicy,然后继续后续的测量综上所述,⽗项在测量⼦项时,⼦项的测量⼊⼝就是e,然后经过⼀系列调⽤到⼦项⾃⼰的MeasurePolicy,也就是我们⾃定义Layout中⾃定义的部分3.2

LayoutNodeWrapper链构建上⾯我们说了,测量⼊⼝是LayoutNode,后续还要经过OuterMeasurablePlaceable,InnerPlaceable的measure⽅法,那么问题来了,这些东西是怎么来的呢?⾸先给出结论1. ⼦项都是以LayoutNode的形式,存在于Parent的children中的2. 给Layout的设置的modifier会以LayoutNodeWrapper链的形式存储在LayoutNode中,然后后续做相应变换由于篇幅原因,关于第⼀点就不在这⾥详述了,有兴趣的同学可以参考:我们这⾥主要看下LayoutNodeWrapper链是怎么构建的 internal val innerLayoutNodeWrapper: LayoutNodeWrapper = InnerPlaceable(this) private val outerMeasurablePlaceable = OuterMeasurablePlaceable(this, innerLayoutNodeWrapper) override fun measure(constraints: Constraints) = e(constraints) override var modifier: Modifier = Modifier set(value) { // …… code field = value // …… code // 创建新的 LayoutNodeWrappers 链 // foldOut 相当于遍历 modifier val outerWrapper = t(innerLayoutNodeWrapper) { mod /*

发布者:admin,转转请注明出处:http://www.yc00.com/web/1687954652a60544.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信