开场

Android开发的都知道一个规矩:布局文件的界面层级尽量地少,越少越好,由于层级的添加会大幅拖慢界面的加载。这种拖慢地主要原因就在于各种Layout的重复丈量。虽然重复丈量对于布局进程是必不可少的,但这也确实让界面层级的数量对加载时刻的影响变成了指数级。而Jetpack Compose是不怕层级嵌套的,由于它从本源上解决了这种问题。它解决的方法也十分巧妙而简略——它不许重复丈量。

View层数和界面加载功用的联系

在定制ViewGroup的布局进程的时分,咱们需求重写两个方法:onMeasure()用来丈量子View,onLayout()用来摆放丈量好的子View。丈量和摆放分明是接连的进程,为什么要拆成两部分呢?由于咱们在ViewGroup里可能会对子View进行屡次丈量。

比方一个纵向的LinearLayout,当它的宽度被设置成wrap_content的时分:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    ...
</LinearLayout>

它会顺次丈量自己一切的子View,然后把他们最宽的那个宽度作为自己终究的宽度。

但……假如它内部有一个子View的宽度是match_parent:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <View
        android:layout_width="match_parent"
        androidLlayout_height="48dp"/>
    <View
        android:layout_width="120dp"
        android:layout_height="48dp" />
    <View
        android:layout_width="160dp"
        android:layout_height="48dp" />
</LinearLayout>

「码上开学——hencoder」Compose笔记(View 嵌套太深会卡?来用 Jetpack Compose,随便套——Intrinsic Measure)

这时分,LinearLayout就会先以0为强制宽度丈量一下这个子View,并正常地丈量剩下的其他子View,然后再用其他子View里最宽的那个宽度,二次丈量这个match_parent的子View,终究得出它的尺度,并把这个宽度作为自己终究的宽度。

「码上开学——hencoder」Compose笔记(View 嵌套太深会卡?来用 Jetpack Compose,随便套——Intrinsic Measure)

「码上开学——hencoder」Compose笔记(View 嵌套太深会卡?来用 Jetpack Compose,随便套——Intrinsic Measure)

这是对单个子View的二次丈量,假如有多个子View写的match_parent,那就需求对它们每一个都进行二次丈量。

「码上开学——hencoder」Compose笔记(View 嵌套太深会卡?来用 Jetpack Compose,随便套——Intrinsic Measure)

「码上开学——hencoder」Compose笔记(View 嵌套太深会卡?来用 Jetpack Compose,随便套——Intrinsic Measure)

总之,在Android里,一个ViewGroup是可能对子View进行二次丈量的。不仅仅二次,有时分还会出现三次乃至更屡次的丈量。而且这不是特别场景,重复测试在Android里是很常见的。

重复丈量是ViewGroup完成正确丈量所必须得手段,但一起也让咱们需求十分注意尽量削减布局的层级。

为什么呢?来看一个最简略的比方,假如咱们的布局有两层,其间父View会对每个子View做二次丈量,那它的每个子View一共需求被丈量2次:

「码上开学——hencoder」Compose笔记(View 嵌套太深会卡?来用 Jetpack Compose,随便套——Intrinsic Measure)

假如添加到三层,并且每个父View依然都做二次丈量,这时分最下班的子View被丈量的次数就直接翻倍了,变成4次:

「码上开学——hencoder」Compose笔记(View 嵌套太深会卡?来用 Jetpack Compose,随便套——Intrinsic Measure)

同理,添加到4层的话会再次翻倍,子View需求被丈量8次:

「码上开学——hencoder」Compose笔记(View 嵌套太深会卡?来用 Jetpack Compose,随便套——Intrinsic Measure)

也便是说,对于会做二测丈量的体系,每个View的丈量算法的时刻杂乱度是O(2ⁿ),其间这个n是View的层级深度。

当然了,实际中并不是每个父View都会进行二次丈量,以及有些父View会对子View做三次或许更屡次的丈量,所以这仅仅一个大略估计,不过——大致便是这个数量级了。

而O(2ⁿ)这种指数型的时刻杂乱度,说白了便是,View的层级每添加1,加载时刻就会翻一倍。

所以为什么Android官方文档会建议咱们的布局文件少一些层级?由于它对功用的影响太大了!

Compose的Intrinsic Measurement

而Compose是制止二次丈量的。

假如每个父组件对子组件只丈量一次,那就直接意味着界面中的每个组织只会被丈量一次:

「码上开学——hencoder」Compose笔记(View 嵌套太深会卡?来用 Jetpack Compose,随便套——Intrinsic Measure)

这样的话,就把组件加载的时刻杂乱度从O(2ⁿ) 降到了 O(n)。

不过……假如禁用二次丈量这么好用的话,Android干嘛不在传统的View体系直接禁掉?——由于它有用啊!

那Compose仅用了二次丈量,它就不用了吗?

这便是Compose巧妙的当地了:Compose禁用了二次丈量,但加入了一个新东西:Intrinsic Measurement,官方把它翻译做「固有特性丈量」。

这个「固有特性丈量」,你要说翻译得不对吧,其实字面上已经十分精确了,但那么翻译却有彻底没有抓住这个功用的灵魂。

所谓的Intrinsic Measurement,指的是Compose允许父组件在对子组件进行丈量之前,先丈量一下子组件的「固有尺度」,直白地说便是「你内部内容的最大或许最小尺度是多少」。这是一种大略的丈量,虽说没有真实的「二次丈量」模式那么自由,但功用并不弱,由于各种Layout里的重复丈量,其实本来便是先进行这种「大略丈量」再进行终究的「正式丈量」的+比方方才说的那种「外面wrap_content里边match_parent」的,对吧?想想是不是?这种「大略」的丈量是很轻的,并不是由于它量的快,而是由于它在机制上不会像传统的二次丈量那样,让组件的丈量时刻随着层级的加深而不断加倍。

当界面需求这种Intrinsic Measurement_也便是说那个所谓的「固有特性丈量」——的时分,Compose会先对整个组件树进行一次Intrinsic丈量,然后再对整体进行正式的丈量。这样拓荒两个平行的丈量进程,就可以防止由于层级添加而对同一子组件重复丈量所导致的丈量时刻的不断加倍了。

「码上开学——hencoder」Compose笔记(View 嵌套太深会卡?来用 Jetpack Compose,随便套——Intrinsic Measure)

总结成一句话便是,在Compose里张狂嵌套写界面,和把一切组件全都写进同一层里边,功用是一样的!

这……还怕嵌套?

方才那个「固有特性丈量」的翻译,我为什么觉得没有灵魂呢?主要是那个「固有特性」指的便是「固有尺度」,也便是这个组件它自身的宽度和高度。而翻译成「固有特性丈量」就有点太直了,直到反而让意义有点扭曲了。不过无伤大雅啊,不管是「固有尺度丈量」还是「固有特性丈量」,这个设计真的很好,它让Compose逃过了Android原生View体系里的一个功用圈套。

事实上,你用一用Compose也会发现,它的功用已经在一些方面超越原生了——尤其是对杂乱场景,比方多组件共同参加的动画。不过目前为止,还仅仅一些方面罢了,并没有全方位超越。

版权声明

本文首发于:View 嵌套太深会卡?来用 Jetpack Compose,随便套——Intrinsic Measurement

微信公众号:扔物线