本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!
前言
上一篇文章中我们提到 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 本身也是 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 的源码,可以找到答案了
@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,可以帮助我们查看代码中类型的稳定性信息
使用方式很简单,只要在 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
配套的单元测试,可以帮我们对类型稳定性的理解更清楚: