调试JetPack组成重新组件的最佳方法是什么?调试器,标准记录(即log.d)中的断点,或者我们需要自定义记录?
JetPack组成中的重新分配是一个复杂的主题。这很复杂,因为有时您不知道为什么要重新组建某个功能,这不是您根据知识所期望的。因此,您需要调试。
调试器中的断点
首先在调试器中使用断点进行调试重新组件。但是,这种方法有一些局限性。
-
它不会告诉您重新组件范围信息(即
$currentRecomposeScope
-参见下文) -
它不会跟踪已经发生了多少个重新组件
标准记录
因此,要通过记录进行调试,您可以使用Log.d
。看起来像这个
Log.d("DebugRecomposition", "RecompositionExample() function scope")
但它错过了一个重要的信息,该信息并未告诉当前的重新组件范围信息。此信息很重要,因为不同的合并功能仍然可以具有相同的重新组件范围 - 请参见下面的说明。
要打印此重建范围信息,您可以使用$currentRecomposeScope
。现在,伐木看起来像这样
Log.d("DebugRecomposition", "RecompositionExample() function scope $currentRecomposeScope")
日志输出看起来像这样:
D/DebugRecomposition: RecompositionExample() function androidx.compose.runtime.RecomposeScopeImpl@894fab8
此RecomposeScopeImpl@894fab8
是此重新组合范围的唯一ID。如果另一个可复合函数具有相同的唯一ID,则意味着它也属于同一重建范围。
好吧,仍然有一条缺失的信息 - 重新组件计数。从技术上讲,您仍然可以手动计算日志语句,但这非常麻烦且容易出错。因此,您需要自定义记录。
自定义记录
我从这个非常好的帖子中窃取了关于重新编写的here的自定义记录代码,我对此进行了一些修改,因为我认为有些东西是不必要的。
这是修改版本:
class RecompositionCounter(var value: Int)
@Composable
inline fun LogCompositions(tag: String, msg: String) {
if (BuildConfig.DEBUG) {
val recompositionCounter = remember { RecompositionCounter(0) }
Log.d(tag, "$msg ${recompositionCounter.value} $currentRecomposeScope")
recompositionCounter.value++
}
}
-
我更名为
Ref
classRecompositionCounter
to更好地反映这是重新组件计数 -
我删除了
SideEffect {}
并在登录后移动计数器增量。我认为我们不需要koude6 here。 -
我添加了
$currentRecomposeScope
作为我认为重要的其他信息。
inline
是为了确保调用此组合函数的父母具有相同的合并函数范围。换句话说,当父母重新组建父时,此LogCompositions()
功能肯定会被调用。
例子
让我们看下面的一个简单示例。
@Composable
fun RecompositionExample() {
var count by remember { mutableStateOf(0) }
LogCompositions("DebugRecomposition", "RecompositionExample() function scope")
Column {
LogCompositions("DebugRecomposition", "Column() content scope")
MyButton(onClick = { count++ }, text = count.toString())
}
}
@Composable
fun MyButton(
onClick: () -> Unit,
text: String) {
LogCompositions("DebugRecomposition", "MyButton() function")
Button(onClick = onClick) {
LogCompositions("DebugRecomposition", "Button() content")
Text(
text = text,
)
}
}
MyButton
是Button()
的包装器,其中Text()
在其内容lambda中。
请注意功能范围和内容范围。
-
功能范围是函数内部的范围。
内容范围是尾声范围,该功能的最后一个lambda参数。
日志输出在启动过程中看起来像这样:
D/DebugRecomposition: RecompositionExample() function scope 0 androidx.compose.runtime.RecomposeScopeImpl@894fab8
D/DebugRecomposition: Column() content scope 0 androidx.compose.runtime.RecomposeScopeImpl@894fab8
D/DebugRecomposition: MyButton() function 0 androidx.compose.runtime.RecomposeScopeImpl@399bf6
D/DebugRecomposition: Button() content 0 androidx.compose.runtime.RecomposeScopeImpl@dc1e8e2
- 您注意到
RecompositionExample()
和Column()
具有相同的重建范围。这是因为常见布局,例如Column()
,Row()
和Box()
都是“内联”综合函数。因此,他们具有与呼叫者相同的范围。
如果单击按钮,日志输出如下:
D/DebugRecomposition: RecompositionExample() function scope 1 androidx.compose.runtime.RecomposeScopeImpl@894fab8
D/DebugRecomposition: Column() content scope 1 androidx.compose.runtime.RecomposeScopeImpl@894fab8
D/DebugRecomposition: MyButton() function 1 androidx.compose.runtime.RecomposeScopeImpl@399bf6
D/DebugRecomposition: Button() content 1 androidx.compose.runtime.RecomposeScopeImpl@dc1e8e2
-
单击按钮时,
count
状态被突变。因此,所有重新组合读取状态的范围都将被重新组成。 -
在
column()
范围中,它读取text = count.toString()
的count
状态。因此,column()
被重新组成。因为column()
和RecompositionExample()
具有相同的重建范围,所以RecompositionExample()
也已重新组件。 -
MyButton()
由于更改了输入参数text
而被重新组建。读取text
的范围将被重新组成。因此,Button()
和Text()
也被重新组件。Text()
没有日志记录,因此它不会显示在日志中。
结论
如前所述,重新组合是一个复杂的话题。本文不关注为什么以及如何进行重新分配。它在上面的示例中涵盖了一点,但这只是一个相当基本的演示。
本文展示了如何使用自定义记录LogCompositions()
来弄清楚重新组件的行为。我认为,重新组成是掌握JetPack组成的最重要的概念,了解其工作原理是至关重要的。
源代码
github存储库:Demo_UnderstandComposeConcept
最初出版于https://vtsen.hashnode.dev 。