Compose 1.5 发布,性能大幅提升?
The following article is from AndroidPub Author fundroid
Modifier 是 Compose 中的重要概念,为 Composition 中的 LayoutNode 配置各种样式信息以用于后续渲染。在 1.3.0 之前的 Modifier 在实现机制上存在性能问题,从 1.3.0 起 Modifier 系统得到重构,通过 Modifier.Node 的引入,性能大幅提升。从 1.3.0 起,既有的 Modifier 操作符逐渐重构到新的架构。
@Composable
fun Box(
modifier = Modifier
.background(..)
.size(..)
.clickable(..)
)
fun Modifier.clickable(..onClick: () -> Unit): Modifier = composed {
val onClickState = rememberUpdatedState(onClick)
val pressedInteraction = remember { mutableStateOf<PressInteraction.Press?>(null) }
..
return this
..
.composed { remember { FocusRequesterModifier() } }
}
/**
* Materialize any instance-specific [composed modifiers][composed] for applying to a raw tree node.
* Call right before setting the returned modifier on an emitted node.
* You almost certainly do not need to call this function directly.
*/
@Suppress("ModifierFactoryExtensionFunction")
fun Composer.materialize(modifier: Modifier): Modifier {
val result = modifier.foldIn<Modifier>(Modifier) { acc, element ->
acc.then(
val factory = element.factory as Modifier.(Composer, Int) -> Modifier
val composedMod = factory(Modifier, this, 0)
materialize(composedMod)
)
..
}
return result
}
composed {} 和 Composer.materialize() 的初衷是可以 Just-In-Time 地为不同 LayoutNode 生成不同的状态。即使是共用同一个 Modifier ,例如下面这样的 case ,虽然共用 m,但是 Content1 的 onClick 带来的水波纹效果不应该影响到 Content2。
@Composable
fun App() {
val m = Modifier
.padding(10.dp)
.background(Color.Blue)
.clickable {...}
Row {
Content1(m)
Content2(m)
}
}
但是,这样的机制也带来了性能问题。Modifier 现场展开 Modifier.Element 数量远多于表面上看到的操作符数量,这个过程会造成大量内存的开销和更大的 GC 概率。而且 Modifier 的调用处不在 Composable 中,无法智能地跳过重组,每次重组都会执行。
interface Modifier {
/**
* The longer-lived object that is created for each [Modifier.Element] applied to a
* [androidx.compose.ui.layout.Layout]..
*/
abstract class Node : DelegatableNode, OwnerScope {
final override var node: Node = this
private setinternal var parent: Node? = null
internal var child: Node? = null
..
}
}
总体来说 Modifier.Node 带来三个好处:
更少的分配:生成的 Element 的数量大大降低,避免了内存抖动和内存占用。 更轻量的树:状态存在 Node 上,不再依靠 remember {} 存储,Composition 的节点数也随之减少,树的遍历速度也更快了。 更快的重组:Modifier.Node 为重组提供可比较标的物, 非必要不重新生成 Node,重组性能得到提升。
// Draw round rectangle with a custom color
fun Modifier.roundRectangle(
color: Color
) = composed {
// when color changed,create and return new RoundRectanleModifier instance
val modifier = remember(color) {
RoundRectangleModifier(color = color)
}
return modifier
}
// implement DrawModifier
class RoundRectangleModifier(
private val color: Color,
): DrawModifier {
override fun ContentDrawScope.draw() {
drawRoundRect(
color = color,
cornerRadius = CornerRadius(8.dp.toPx(), 8.dp.toPx())
)
}
}
val color by animateColorAsState(..)
Box(modifier = Modifier..roundRectangleNode(color = color)) { .. }
Step 1 :定义 Modifier.Node
@OptIn(ExperimentalComposeUiApi::class)
class RoundRectangleModifierNode(
var color: Color,
): Modifier.Node()
Step 2:实现 DrawModifierNode
class RoundRectangleNodeModifier(
var color: Color,
): DrawModifierNode, .. {
override fun ContentDrawScope.draw() {
drawRoundRect(
color = color,
cornerRadius = CornerRadius(8.dp.toPx(), 8.dp.toPx())
)
}
}
/**
* Represents a [Modifier.Node] which can be a delegate of another [Modifier.Node]. Since
* [Modifier.Node] implements this interface, in practice any [Modifier.Node] can be delegated.
*/
@ExperimentalComposeUiApi
interface DelegatableNode {
val node: Modifier.Node
}
interface DrawModifierNode : DelegatableNode {
fun ContentDrawScope.draw()
..
}
/** ..
* @see androidx.compose.ui.node.LayoutModifierNode
* @see androidx.compose.ui.node.DrawModifierNode
* @see androidx.compose.ui.node.SemanticsModifierNode
* @see androidx.compose.ui.node.PointerInputModifierNode
* @see androidx.compose.ui.node.ParentDataModifierNode
* @see androidx.compose.ui.node.LayoutAwareModifierNode
* @see androidx.compose.ui.node.GlobalPositionAwareModifierNode
* @see androidx.compose.ui.node.IntermediateLayoutModifierNode
*/
@ExperimentalComposeUiApi
abstract class Node : DelegatableNode, OwnerScope { .. }
Step 3:创建 ModifierNodeElement
@OptIn(ExperimentalComposeUiApi::class)
fun Modifier.roundRectangle(
color: Color
) = this then modifierElementOf(
params = color,
create = { RoundRectangleModifierNode(color) },
update = { currentNode -> currentNode.color = color },
definitions = ..
)
params : 标识 Modifier 是否变化的参数 。当该参数发生变化时,将调用 update 回调函数。类似于前面 RoundRectangleModifier 的例子中的 remember {} 的用途。 create: 用于创建新的 Modifier.Node ,我们可以在此处编写 Modifier.Node 的初始化逻辑。 update: 用于更新 Modifier.Node 实例。当前的 Modifier.Node 实例将作为参数传递给此回调函数,并返回更新后的 Modifier.Node。在这里,可以重新设置color( 为什么设置成 var ),并在下次绘制时使用更新后的颜色。
参考
https://www.youtube.com/watch?v=BjGX2RftXsU
https://goodpatch-tech.hatenablog.com/entry/modifier-node-summary
https://android-developers.googleblog.com/2023/08/whats-new-in-jetpack-compose-august-23-release.html
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!