前言

上一篇文章中我们提到 Compose Compiler 在生成 Composable 函数体时,会通过 ​​Stability#stabilityOf​​ 判断参数的类型稳定性:

parameters.forEachIndexed { slotIndex, param ->
val stability = stabilityOf(param.varargElementType ?: param.type)

stabilities[slotIndex] = stability

val isRequired = param.defaultValue == null
val isUnstable = stability.knownUnstable()
val isUsed = scope.usedParams[slotIndex]

//...

if (isUsed && isUnstable && isRequired) {
// if it is a used + unstable parameter with no default expression, the fn
// will _never_ skip
mightSkip = false
}
}

如果有不稳定的参数存在时,则 ​​mightSkip = false​​,函数体中则不会生成 skipToGroupEnd 的代码,也即函数无法跳过重组,必须参与执行。

什么是稳定类型?

按照官方文档的定义,稳定类型必须符合以下条件:

​​developer.android.com/jetpack/com…​​

  • 对于相同的两个实例,其 equals 的结果将始终相同。
  • 如果类型的某个公共属性发生变化,组合将收到通知。
  • 所有公共属性类型也都是稳定。

另外,以下这些类型被 Compiler 默认为是稳定类型:

  • 所有基元值类型:Boolean、Int、Long、Float、Char 等。
  • 字符串
  • 所有函数类型 (lambda)

一个不稳定的类型,意味着 equals 结果不能保证始终相同,所以对于 Composable 参数来说,不稳定类型的比较结果不值得信赖,因此将始终参与重组。

接下来我们看一下 ​​Stability#stabilityOf​​ 中是如何判断类型稳定性的。

Stability 类型

​Stability#stabilityOf​​​ 的结果是一个 ​​Stability​​ 类型,它有以下几种取值:

sealed class Stability {
// class Foo(val bar: Int)
class Certain(val stable: Boolean) : Stability()

// class Foo(val bar: ExternalType) -> ExternalType.$stable
class Runtime(val declaration: IrClass) : Stability()

// interface Foo { fun result(): Int }
class Unknown(val declaration: IrClass) : Stability()

// class <T> Foo(val value: T)
class Parameter(val parameter: IrTypeParameter) : Stability()

// class Foo(val foo: A, val bar: B)
class Combined(val elements: List<Stability>) : Stability()

companion object {
val Stable: Stability = Certain(true)
val Unstable: Stability = Certain(false)
}
}
  • Certain:有明确的稳定性,Stable 或者 Unstable。
  • Runtime:成员中存在外部类型(来自三方jar包),外部类型稳定性在编译期无法确定,所以需要依靠运行时判断。Compiler 会生成基于 深入浅出 Compose Compiler(5) 类型稳定性 Stability_sedstable 本身也是 Compiler 为 Class 生成的静态变量,后文会提到。
  • Unknown:接口类型,不知道具体实现,无法得知稳定性
  • Parameter:类型带有泛型,稳定性由泛型参数类型决定
  • Combined:有多各成员,稳定性有多个成员共同决定,Combined 的中任意 element 不稳定,则整个类型不稳定

Stability#stabilityOf 推断稳定性

接下来看一下 ​​stabilityOf​​ 的实现,是如何决定 Class 的 Stability

private fun stabilityOf(
declaration: IrClass,
substitutions: Map<IrTypeParameterSymbol, IrTypeArgument>,
currentlyAnalyzing: Set<IrClassifierSymbol>
): Stability {

//...

val symbol = declaration.symbol
if (currentlyAnalyzing.contains(symbol)) return Stability.Unstable
if (declaration.hasStableMarkedDescendant()) return Stability.Stable
if (declaration.isEnumClass || declaration.isEnumEntry) return Stability.Stable
if (declaration.defaultType.isPrimitiveType()) return Stability.Stable

val analyzing = currentlyAnalyzing + symbol
//...
}

首先,以下这些类型,可以立即返回 ​​Stability.Stable​

  • hasStableMarkedDescendant(), 即添加了 @Stable 注解
  • isEnumClass 或者 isEnumEntry
  • isPrimitiveType():基本类型

​stabilityOf​​​ 是一个递归调用,​​currentlyAnalyzing​​ 记录当前类型,如果在递归中发现有当前类型的记录,则意味着此类型无法在递归逻辑中推断稳定性(上一轮不确定,这一轮自然也无法确定),所以判定为 Unstable。

接下来,需要获取类型的 mask 掩码信息。

val fqName = declaration.fqNameWhenAvailable?.toString() ?: ""
val stability: Stability
val mask: Int

if (stableBuiltinTypes.contains(fqName)) {
//带有泛型的接口
mask = stableBuiltinTypes[fqName] ?: 0
stability = Stability.Stable
} else {
// IrDeclarationOrigin.IR_EXTERNAL_DECLARATION_STUB :外部类型
mask = declaration.stabilityParamBitmask() ?: return Stability.Unstable
stability = Stability.Runtime(declaration)
}

这里 ​​if..else​​​ 针对类型获取了 ​​mask​​​,mask 表示类型中存在泛型参数需要验证其类型的稳定性,1 表示需要验证。 下面分别来看一下 ​​if​​​ 和 ​​else​​ 代码块中是哪些类型,以及其 mask 来自哪里:

  • if 中的 mask 来自 stableBuiltinTypes 。这其中预定义了一些常见的带有泛型接口及其 mask:
private val stableBuiltinTypes = mapOf(
"kotlin.Pair" to 0b11,
"kotlin.Triple" to 0b111,
"kotlin.Comparator" to 0,
"kotlin.Result" to 0b1,
"kotlin.ranges.ClosedRange" to 0b1,
"kotlin.ranges.ClosedFloatingPointRange" to 0b1,
// Guava
"com.google.common.collect.ImmutableList" to 0b1,
"com.google.common.collect.ImmutableEnumMap" to 0b11,
"com.google.common.collect.ImmutableMap" to 0b11,
"com.google.common.collect.ImmutableEnumSet" to 0b1,
"com.google.common.collect.ImmutableSet" to 0b1,
// Kotlinx immutable
"kotlinx.collections.immutable.ImmutableList" to 0b1,
"kotlinx.collections.immutable.ImmutableSet" to 0b1,
"kotlinx.collections.immutable.ImmutableMap" to 0b11,
// Dagger
"dagger.Lazy" to 0b1,
)
  • else 中的 mask 来自 ​​@StabilityInferred​​ 注解,这个注解也是 Compiler 生成的,待会儿介绍。
private fun IrAnnotationContainer.stabilityParamBitmask(): Int? =
(annotations.findAnnotation(ComposeFqNames.StabilityInferred)
?.getValueArgument(0) as? IrConst<*>
)?.value as? Int

有了 mask 之后,我们看一下 mask 如何用的

return stability + Stability.Combined(
declaration.typeParameters.mapIndexedNotNull { index, irTypeParameter ->
if (mask and (0b1 shl index) != 0) {
val sub = substitutions[irTypeParameter.symbol]
if (sub != null)
stabilityOf(sub, substitutions, analyzing)
else
Stability.Parameter(irTypeParameter)
} else null
}

如上,基于 mask 来决定哪些类型参数需要进一步判断稳定类型,参数的稳定型类型会合并到 Stability 返回。​​+​​ 运算符重载的实现如下:

operator fun plus(other: Stability): Stability = when {
other is Certain -> if (other.stable) this else other
this is Certain -> if (stable) other else this
else -> Combined(listOf(this, other))
}

继续看 stabilityOf 的剩余实现

if (declaration.origin == IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB) {
//对于Java的类型,没法在Kotlin Compiler中推断稳定性,返回 Unstable
return Stability.Unstable
}

if (declaration.isInterface) {
//如果是接口类型,由于不知道具体实现是否是稳定类型,故返回 Unknown
return Stability.Unknown(declaration)
}

var stability = Stability.Stable

//如果 Class 有成员,则判断成员稳定性
for (member in declaration.declarations) {
when (member) {
is IrProperty -> {
member.backingField?.let {
if (member.isVar && !member.isDelegated) return Stability.Unstable
stability += stabilityOf(it.type, substitutions, analyzing)
}
}
is IrField -> {
stability += stabilityOf(member.type, substitutions, analyzing)
}
}
}

如上,当类型有成员或者属性时,会依次判断成员的 Stability 并合并返回。值得注意的是,如果类型中有 ​​var​​​成员,则该类型被认为是 ​​Stability.Unstable​​ 的。

如果是一个 Kt 的接口,则被认为是 ​​Stability.Unknown​​​,因为其实现类的稳定性不可知。​​Unknown​​​ 后续也会被当做 ​​Unstable​​ 处理。

此外,如果是一个Java类型,因为无法在 Kt 编译器中推断稳定性,也被认为是 ​​Unstable​​ 的。 掘金的 ​​Pika​​ 大佬曾在我的书《Jetpack Compose 从入门到实战》中发现一处错误,还特意联系我进行了指正:

深入浅出 Compose Compiler(5) 类型稳定性 Stability_Android Jetpack_02

现在通过 Compose Compiler 的源码,可以解答这原因了L

@Composable
fun Foo(bar: List<String>) {
bar.forEach { Text(it) }
}

Foo 的参数 List 是一个 Java 类型,所以被认为是 ​​Unstable​​,编译后的函数体中也不会有 skipToGroupEnd 的相关代码:

@Composable
fun Foo(bar: List<String>, $composer: Composer<*>, $changed: Int) {
$composer.startRestartGroup(405544596)
bar.forEach { Text(it) }
$composer.endRestartGroup().updateScope {
Foo(bar, $changed)
}
}

在此也特别感谢 ​​Pika​​ 的反馈和指正! 后续加印的时候会进行在书中勘误

@StabilityInferred 注解

前面提到了部分 Class 的 mask 来自 ​​@StabilityInferred​​​ 注解,这个注解是 Compiler 为 Class 添加的辅助信息,帮助分析稳定性。相关实现在 ​​ClassStabilityTransformer#visitClass​​​ 中,这里除了添加 ​​@StabilityInferred​​​ 注解,还会为 Class 生成 ​​$stable​​​ 静态变量,服务于 ​​Stability.Runtime​​ 的代码生成。

override fun visitClass(declaration: IrClass): IrStatement {
val result = super.visitClass(declaration)
val cls = result as? IrClass ?: return result

if (
cls.visibility != DescriptorVisibilities.PUBLIC ||
cls.isEnumClass ||
cls.isEnumEntry ||
cls.isInterface ||
cls.isAnnotationClass ||
cls.isAnonymousObject ||
cls.isExpect ||
cls.isInner ||
cls.isFileClass ||
cls.isCompanion ||
cls.defaultType.isInlineClassType()
) return cls

if (declaration.hasStableMarker()) {
return cls
}

//...
}

对于符合上述条件的类型,则直接 ​​return​​,不会生成辅助信息。因为这些类型的稳定性是明确的。

val stability = stabilityOf(declaration.defaultType).normalize()

var parameterMask = 0 //生成 mask,添加到 @StabilityInferred 注解
val stableExpr: IrExpression //生成 $stable = xx

if (cls.typeParameters.isNotEmpty()) {
val symbols = cls.typeParameters.map { it.symbol }
var externalParameters = false

stability.forEach {
when (it) {
is Stability.Parameter -> {
val index = symbols.indexOf(it.parameter.symbol)
if (index != -1) {
// the stability of this parameter matters for the stability of the
// class
parameterMask = parameterMask or 0b1 shl index
} else {
externalParameters = true
}
}
else -> {
/* No action necessary */
}
}
}
stableExpr = if (externalParameters)
irConst(UNSTABLE)
else
stability.irStableExpression { irConst(STABLE) } ?: irConst(UNSTABLE)
} else {
stableExpr = stability.irStableExpression() ?: irConst(UNSTABLE)
}

生成 mask 之后,添加 ​​@StabilityInferred​​ 注解等:

//添加 @StabilityInferred 注解,注解中包含 mask
cls.annotations = cls.annotations + IrConstructorCallImpl(
UNDEFINED_OFFSET,
UNDEFINED_OFFSET,
StabilityInferredClass.defaultType,
StabilityInferredClass.constructors.first(),
0,
0,
1,
null
).also {
it.putValueArgument(0, irConst(parameterMask))
}

//生成 $stable 静态变量
val stabilityField = makeStabilityField().also { f ->
f.parent = cls
f.initializer = IrExpressionBodyImpl(
UNDEFINED_OFFSET,
UNDEFINED_OFFSET,
stableExpr
)
}

if (context.platform.isJvm()) {
cls.declarations += stabilityField
}

测试 Compose 类型稳定性

上面的分析可以感受到 Compiler 对于类型稳定性的判断非常复杂,因为这对于提升 Compose 重组至关重要,稳定类型越多,重组的性能越好。Compose 1.2 之后新增了工具 Compose Compiler Metrics,可以帮助我们查看代码中类型的稳定性信息

参考:​​chris.banes.dev/posts/compo…​​

使用方式很简单,只要在 root 的 build.gradle 中添加一下配置

subprojects {
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
kotlinOptions {
freeCompilerArgs += [
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination="
+ project.buildDir.absolutePath + "/compose_metrics"
]
freeCompilerArgs += [
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination="
+ project.buildDir.absolutePath + "/compose_metrics"
]
}
}
}

然后执行 ​​.gradlew assemble​​​ 命令时,就可以在 ​​build/compose_metrics​​ 目录中生成静态分析的信息

深入浅出 Compose Compiler(5) 类型稳定性 Stability_Android_03

例如下面代码:

class Foo(val value: Int)

class Foo2(var value: Int)

class Foo3<T>(val value: T)

@Stable
class Foo4(var value: Int)

检测的结果是:

stable class Foo {
stable val value: Int
<runtime stability> = Stable
}
unstable class Foo2 {
stable var value: Int
<runtime stability> = Unstable
}
runtime class Foo3 {
runtime val value: T
<runtime stability> = Parameter(T)
}
stable class Foo4 {
stable var value: Int
}

另外,谷歌工程师测试驱动意识很强,Compose Compiler 源码中提供了 ​​ClassStabilityTransform​​ 配套的单元测试,可以帮我们对类型稳定性的理解更清楚:

​​cs.android.com/androidx/pl…​​