summary
- Background: too many layout levels and too nested layouts will lead to exponential growth in measurement time, which will eventually affect layout performance.
- Status quo: Compose does not have the above layout nesting problem, because it fundamentally solves the impact of layout level on layout performance.
- Solution principle: the Compose interface only allows one measurement, that is, with the deepening of the layout level, the measurement time only increases linearly.
This article will mainly explain:
- How does too much nesting affect layout performance?
- How does ComposeUI solve nesting problems?
- Why can ComposeUI allow only one measurement?
- Source code analysis of ComposeUI measurement process
1. How does too much nesting affect layout performance?
The main reason is that the View Group will measure the child views multiple times. Suppose: the layout attribute of the parent layout is wrap_content and sub layout are match_parent, the layout process is:
- The parent layout first measures the child views with 0 as the forced width, and then continues to measure the remaining child views
- Then measure the match twice with the widest width in other sub views_ The child View of parent finally obtains its size, and takes this width as its final width.
That is, this process makes a secondary measurement on a single sub View.
The impact of layout nesting on performance is exponential, that is, the parent layout will measure each child view twice, and the child view will also measure the following child views twice, which is equivalent to O(2 ⁿ) measurement.
2. How does composeui solve the nesting problem?
ComposeUI stipulates that only one measurement is allowed, and repeated measurement is not allowed. That is, each parent layout only measures each sub component once, that is, the measurement complexity becomes: O(n).
3. Why can ComposeUI only allow one measurement?
ComposeUI introduces: intrinsic measurement. That is, Compose allows the parent component to measure the "inherent size" of the quantum component before measuring the child component, which is equivalent to the first "rough measurement" of the two measurements mentioned above.
This fixed characteristic measurement is only one measurement of the whole component layout tree, so as to avoid increasing the number of measurements with the deepening of the hierarchy.
4. Source code analysis of composeui measurement process
This paper mainly analyzes the measurement process of fixed characteristic measurement. Here we first introduce the construction of LayoutNodeWrapper chain
4.1 LayoutNodeWrapper
Let's look at two core conclusions:
- Child views exist in Parent - children in the form of LayoutNode
- The modifier of the settings given to Layout will be stored in LayoutNode in the form of LayoutNodeWrapper chain, and then corresponding transformation will be made later
The following describes the construction of LayoutNodeWrapper:
- The default LayoutNodeWrapper chain consists of layoutnode, outermeasurable placeable and innerplaceable
- When a modifier is added, the LayoutNodeWrapper chain will be updated and the modifier will be inserted as a node
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 / / create a new" LayoutNodeWrappers "chain / / foldout is equivalent to traversing" 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 {/ /... Some {modifier judgments} if (mod is} KeyInputModifier) {wrapper = ModifiedKeyInputNode(wrapper, MOD). Assignchained (towrap)} if are omitted (mod is PointerInputModifier) { wrapper = PointerInputDelegatingWrapper(wrapper, mod).assignChained(toWrap) } if (mod is NestedScrollModifier) { wrapper = NestedScrollDelegatingWrapper(wrapper, mod). Assignchained (towrap)} / / Layout related {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...} / / suppose: set some modifiers for Layout size(100.dp). padding(10.dp). background(Color.Blue)
The corresponding LayoutNodeWrapper chain is shown in the following figure
<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>
In this way, the next measure is called in a chain until the last node InnerPlaceable, and finally the measure() written in the custom Layout is called
4.2 inherent characteristic measurement - implementation principle
Conclusion: a Modifier is inserted into the LayoutNodeWrapper chain
@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 { //Before formal measurement, obtain a constraint according to the inherent characteristic measurement , val , contentconstraints = calculateContentConstraints(measurable, constraints) / / formal measurement , 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)}
Summary description:
- IntrinsicSize.Min is actually a Modifier
- Minintrinsichightmodifier will first call calculateContentConstraints to calculate constraints between measurements
- calculateContentConstraints will recursively call the mininsiciheight of the child item and find the maximum value, so that the height of the parent item is determined
- After the inherent characteristic measurement is completed, call measurable Measure, start the real recursive measurement
So far, the explanation on the problem of layout nesting level and its principle of Compose UI has been completed.
last
Share with you the learning materials I collected on Android source code analysis. I hope they are useful to you and look forward to making progress with you
1. Deeply analyze the source code of wechat MMKV
2. Deeply analyze Alibaba routing framework ARouter
Source code
3. In depth analysis of AsyncTask source code (one)
Android built-in asynchronous task execution Library)
4. In depth analysis of Volley source code (a Google
Launched network request framework)
5. In depth analysis of Retrofit source code
6. In depth analysis of OkHttp source code
Due to the limited length of the article and more details, friends who need to learn materials can click here Get it for free!