Jetpack Compose 测量流程源码分析,Android面试题汇总

跟踪 Modifier & MeasurePolicy


为了方便跟踪代码,我来给代码设置点跟踪器 (别搞丢了)😜 下面的代码中 modifier 参数达到的位置我会用📍 标记, measurePolicy 到达的位置用 📌 标记

Layout.kt → Layout 函数源码

@Composable inline fun Layout(

content: @Composable () -> Unit,

modifier: Modifier = Modifier,

measurePolicy: MeasurePolicy

) {

val density = LocalDensity.current

val layoutDirection = LocalLayoutDirection.current

ReusableComposeNode<ComposeUiNode, Applier>(

factory = ComposeUiNode.Constructor,

update = {

set(measurePolicy, ComposeUiNode.SetMeasurePolicy) // 👈 📌 measurePolicy 在这

set(density, ComposeUiNode.SetDensity)

set(layoutDirection, ComposeUiNode.SetLayoutDirection)

},

skippableUpdate = materializerOf(modifier), // 👈 📍 modifier 在这

content = content

)

}

从上面源码可以看出,Layout 函数体中没有做什么处理,核心内容就是调用 ReusableComposeNode 函数。

@Composable 注解的函数建议首字母大写已区分普通函数,看代码的时候总觉的 ReusableComposeNode 是个类,点进去发现它是个 Composable 函数 😂 。

Composables.kt → ReusableComposeNode 函数

inline fun <T, reified E : Applier<*>> ReusableComposeNode(

noinline factory: () -> T,

update: @DisallowComposableCalls Updater.() -> Unit,

noinline skippableUpdate: @Composable SkippableUpdater.() -> Unit,

content: @Composable () -> Unit

) {

if (currentComposer.applier !is E) invalidApplier()

currentComposer.startNode()

if (currentComposer.inserting) {

currentComposer.createNode(factory)

} else {

currentComposer.useNode()

}

//执行update函数

Updater(currentComposer).update() // 👈 📌 measurePolicy 在这

//执行skippableUpdate函数

SkippableUpdater(currentComposer).skippableUpdate() // 👈 📍 modifier 在这函数中

currentComposer.startReplaceableGroup(0x7ab4aae9)

content()

currentComposer.endReplaceableGroup()

currentComposer.endNode()

}

先只看和 modifier 有关的,即 SkippableUpdater<T>(currentComposer).skippableUpdate() 这句话,其他的先不看。

这里函数的参数都是函数参数类型,如果 Kotlin 不太熟悉的,可能看着比较晕 😵 ,下面为了方便分析一下,就把代码给它摊平了。

这里的 “代码给它摊平了” 是指去掉函数回调和一层层的调用,直接写到一起,避免函数跳来跳去,方便解释。下文中提到的摊平都是这个意思。

下面我就试着把它摊平看看。

//源代码

SkippableUpdater(currentComposer).skippableUpdate()

//0️⃣ 根据 ComposeNode 传入参数知

skippableUpdate=materializerOf(modifier) // 👈 📍 modifier

//1️⃣ materializerOf 函数返回值就是函数类型 SkippableUpdater.() -> Unit

internal fun materializerOf(

modifier: Modifier

): @Composable SkippableUpdater.() -> Unit = {

// 📍 这里只是对 modifier链中存在的 ComposedModifier 进行处理一下,返回值还是 Modifier

val materialized = currentComposer.materialize(modifier)

update { set(materialized, ComposeUiNode.SetModifier) } // 👈 📍 modifier

}

//结合代码 0️⃣ 和代码 1️⃣ 可知 SkippableUpdater(currentComposer).skippableUpdate()

//<=> 等价于代码如下

val skippableUpdater=SkippableUpdater(currentComposer)

val materialized = currentComposer.materialize(modifier)

skippableUpdater.update { set(materialized, ComposeUiNode.SetModifier) }

好像还差点还有个update 还是没有摊平

inline class SkippableUpdater constructor(

@PublishedApi internal val composer: Composer

) {

inline fun update(block: Updater.() -> Unit) {

composer.startReplaceableGroup(0x1e65194f)

Updater(composer).block()

composer.endReplaceableGroup()

}

}

//结合SkippableUpdater 的update 函数,skippableUpdater.update { set(materialized, ComposeUiNode.SetModifier) }

// <=>等价于👇

composer.startReplaceableGroup(0x1e65194f)

// 2️⃣ 📍 modifier 最终传给了这个 set 方法

Updater(currentComposer).set(materialized, ComposeUiNode.SetModifier)

composer.endReplaceableGroup()

代码 2️⃣ 这里是调用了一个 set 方法,直接看有点晕,调来调用去,分析起来太多了,不能跑偏了,直接说重点。

companion object {

val Constructor: () -> ComposeUiNode = LayoutNode.Constructor

//ComposeUiNode.SetModifier

val SetModifier: ComposeUiNode.(Modifier) -> Unit = { this.modifier = it }

}

// ComposeUiNode.SetModifier 也是个函数类型,调用 set(materialized, ComposeUiNode.SetModifier)

//最终会触发SetModifier 函数的执行也就是

this.modifier=materialized //📍 modifier

// 👆 this是LayoutNode 对象 是通过触发 ComposeUiNode.Constructor创建的

关于从 set(materialized, ComposeUiNode.SetModifier) 是如何到触发 SetModifier 函数的,这里我就分析了,可以通过 debug 很容易验证这一结论。如果你真的想去分析如何执行的话,分析之前建议先看一下 深入详解 Jetpack Compose | 实现原理 这篇文章。(友情提醒,如何真要分析这段别陷进去了,别忘记我们看源码的目的。)

通过上面的分析,我们追踪的 modifier 被赋值给了 LayoutNode 成员的 modifier ,这种是个赋值语句,在 kotlin 相当于调用的成员变量的set 方法 LayoutNode.kt

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

// 代码 0️⃣

outerMeasurablePlaceable.outerWrapper = outerWrapper // 👈 📍 modifier

……

}

**👆 代码片段-1 **

上述代码主要是将Modifier 链转换LayoutNodeWrapper 链的过程,通过Modifier 的 foldOut 函数 遍历Modifier 链上的所有元素,并根据不同的Modifier 创建不同的 LayoutNodeWrapper。关于Modifier 的foldOut 函数的作用不懂的可以看我之前写的 Modifier源码,Kotlin高阶函数用的真6 这篇文章。

在上面的代码中根据 Modifier 类型创建不同的 LayoutNodeWrapper,这些不同的 Modifier 都是 Modifier.Element 的直接实现类或接口,如 KeyInputModifier、PointerInputModifier、LayoutModifier 等。上面代码都是 if 判断,没有else,也就是说如果 Modifier 不在这些类别范围内就没法创建对应的LayoutNodeWrapper,也就相等于我们设置的 Modifier 没有用。所以我自定义Modifer 一定要在这个类型范围内,否则是没有用的。在JetPack Compose 内置的Modifier.Element 子类或接口如下。(Tips. Android studio 查看类的继承关系 菜单栏Navgate-> Type Hierarchy ; 快捷键 Ctrl+H )

换个思路继续跟踪


上面分析到那里路好像断了, 没法继续了。思考一下这里只是分析了Layout 函数执行时,只是初始化的准备工作。它的大小和位置如果确认等操作这里似乎没有执行。我们刚才是把 ParentLayout 当做父容器来看待的,父容器一般是管理自己的 children 的大小和位置,换一种思路,ParentLayout 出来做父容器,它也可以作为 child 呀,如下面代码情况。

setContent {

ParentLayout{

Box() {}

// 👇 可以看做是 上面 ParentLayout 的 child,也可以看做是下面 ChildLayout 的父容器

ParentLayout(

Modifier

.size(100.dp)

.padding(10.dp)

.background(Color.Blue)

) {

ChildLayout(Modifier.size(100.dp)) {}

}

}

}

下面就从 ParentLayout 布局作为 child 的时候来分析一下,如果作为 child 那么分析入口就应该它从的父容器 MeasurePolicy 的 measure 函数开始分析了。

val measurePolicy = MeasurePolicy { measurables, constraints ->

val placeables = measurables.map { child ->

//代码 0️⃣

child.measure(constraints)

}

……

}

代码 0️⃣ 进行调用 child 的测量方法,从函数参数来看,只知道 child 是个 Measurable 的类型,但 Measurable 是个接口,我们需要知道 child 具体是 Measurable 那个实现类,我们才好分析 measure 函数的逻辑

👆 图片-0

通过debug 的方式,可以看出 child 是 LayoutNode 对象(为什么是LayoutNode 下面分析就知道了),那么就去看看 LayoutNode 的measure函数。

LayoutNode.kt → measure 函数

override fun measure(constraints: Constraints) =

outerMeasurablePlaceable.measure(constraints)

LayoutNode 的 measure 调用了 outerMeasurablePlaceable 的 measure 函数,这个 outerMeasurablePlaceable **代码片段-1 代码 0️⃣ **也出现了outerMeasurablePlaceable.outerWrapper = outerWrapper // 👈 📍 modifier 而且这个outerMeasurablePlaceable 的属性 outerWrapper 就包含 modifier 信息。我们又找到了 modifier 的藏身之处,好像又找到些线索。我们继续跟踪代码吧。

LayoutNodeWrapper 链中的测量流程分析⛓


OuterMeasurablePlaceable.kt

override fun measure(constraints: Constraints): Placeable {

……

remeasure(constraints)

return this

}

fun remeasure(constraints: Constraints): Boolean {

val owner = layoutNode.requireOwner()

……

if (layoutNode.layoutState == LayoutState.NeedsRemeasure ||

measurementConstraints != constraints

) {

measuredOnce = true

layoutNode.layoutState = LayoutState.Measuring

measurementConstraints = constraints

val outerWrapperPreviousMeasuredSize = outerWrapper.size

owner.snapshotObserver.observeMeasureSnapshotReads(layoutNode) {

outerWrapper.measure(constraints)// 0️⃣ 👈 📍 modifier

}

layoutNode.layoutState = LayoutState.NeedsRelayout

……

return sizeChanged

}

return false

}

👆 代码片段-2 代码 0️⃣ 处 我们看到包含 modifier 信息的 outerWrapper 调用了 它的 measure 方法。outerWrapper 是 LayoutNodeWrapper 类型的,它就是在代码片段1 处根据不同 Modifer 创建的 LayoutNodeWrapper 链。我们给 ParentLayout 的 Modifer 设置为 Modifier.size(100.dp).padding(10.dp).background(Color.Blue) 。那么对应的LayoutNodeWrapper 链如下图所示

Jetpack compose 测量流程-LayoutNodeWrapper链 (3)

👆 图-1

由 图-1 知代码片段-2 处的代码outerWrapper 为 ModifiedLayoutNode 类型。 ModifiedLayoutNode

internal class ModifiedLayoutNode(

wrapped: LayoutNodeWrapper,

modifier: LayoutModifier

) : DelegatingLayoutNodeWrapper(wrapped, modifier) {

override fun measure(constraints: Constraints): Placeable = performingMeasure(constraints) {

with(modifier) { 👈 📍 modifier

measureResult = measureScope.measure(wrapped, constraints)

this@ModifiedLayoutNode

}

}

protected inline fun performingMeasure(constraints: Constraints, block: () -> Placeable

): Placeable {

measurementConstraints = constraints

val result = block()

layer?.resize(measuredSize)

return result

}

……

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

尾声

面试成功其实都是必然发生的事情,因为在此之前我做足了充分的准备工作,不单单是纯粹的刷题,更多的还会去刷一些Android核心架构进阶知识点,比如:JVM、高并发、多线程、缓存、热修复设计、插件化框架解读、组件化框架设计、图片加载框架、网络、设计模式、设计思想与代码质量优化、程序性能优化、开发效率优化、设计模式、负载均衡、算法、数据结构、高级UI晋升、Framework内核解析、Android组件内核等。

不仅有学习文档,视频+笔记提高学习效率,还能稳固你的知识,形成良好的系统的知识体系。这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。

Android进阶学习资料库

一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!

image

大厂面试真题

PS:之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

《2017-2021字节跳动Android面试历年真题解析》

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

[外链图片转存中…(img-f2MmFnB9-1712139864201)]

《2017-2021字节跳动Android面试历年真题解析》

[外链图片转存中…(img-Uib7pss8-1712139864201)]

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

  • 20
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值