前言
上一篇文章中我们提到 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 的代码,也即函数无法跳过重组,必须参与执行。
什么是稳定类型?
按照官方文档的定义,稳定类型必须符合以下条件:
- 对于相同的两个实例,其 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 会生成基于 ������判断稳定性的运行时代码,stable判断稳定性的运行时代码,stable 本身也是 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 的源码,可以找到答案了
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,可以帮助我们查看代码中类型的稳定性信息
使用方式很简单,只要在 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
目录中生成静态分析的信息
例如下面代码:
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
配套的单元测试,可以帮我们对类型稳定性的理解更清楚: