本文为稀土技术社区首发签约文章,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 的代码,也即函数无法越过重组,必须参加履行。

什么是安稳类型?

依照官方文档的界说,安稳类型必须契合以下条件:

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 会生成根据 stable判别安稳性的运行时代码,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 表明需求验证。 下面别离来看一下 ifelse 代码块中是哪些类型,以及其 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

现在经过 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,能够帮助咱们查看代码中类型的安稳性信息

参阅: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

例如下面代码:

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…