Jetpack Compose Compilier&Runtime浅析

关于Compose的一些事

Compose的结构

在这里插入图片描述Compose的通用结构如图所示,由代码、编译器插件、runtime库、以及各平台对应的UI库组成。首先需要明确的一点是,Compose的前几层结构,即不包括UI的其余部分,是一套控制树状节点的一套东西,其实是可以完全脱离UI层独立运行的。对于Android UI而言,这个节点就是LayoutNode类。

在这里插入图片描述

对于Android平台的compose,结构是这样的。除了Animation层之外,其他三层每一层都是对上层的封装,一般来说我们使用的都是Material层的,当然Foundation层和UI层也是直接使用的。另外,对于一些自定义要求比较高的自定义控件(指不能直接靠直接组合/封装现有控件的实现的控件)而言,不免要涉及到UI层的LayoutNode(Android Compose的节点类型)和Modifier、DrawScope等等麻烦的东西(这个我自己都没搞懂以后再说吧)。

举个例子,以最常用的Text方法为例。

最常用的Text位于Material层的Text,只是会生成一个mergedStyle

@Composable
fun Text(
    text: String,
    modifier: Modifier = Modifier,
    color: Color = Color.Unspecified,
    fontSize: TextUnit = TextUnit.Unspecified,
    fontStyle: FontStyle? = null,
    fontWeight: FontWeight? = null,
    fontFamily: FontFamily? = null,
    letterSpacing: TextUnit = TextUnit.Unspecified,
    textDecoration: TextDecoration? = null,
    textAlign: TextAlign? = null,
    lineHeight: TextUnit = TextUnit.Unspecified,
    overflow: TextOverflow = TextOverflow.Clip,
    softWrap: Boolean = true,
    maxLines: Int = Int.MAX_VALUE,
    onTextLayout: (TextLayoutResult) -> Unit = {
   },
    style: TextStyle = LocalTextStyle.current
) {
   
    Text(
        AnnotatedString(text),
        modifier,
        color,
        fontSize,
        fontStyle,
        fontWeight,
        fontFamily,
        letterSpacing,
        textDecoration,
        textAlign,
        lineHeight,
        overflow,
        softWrap,
        maxLines,
        emptyMap(),
        onTextLayout,
        style
    )
}

@Composable
fun Text(
    text: AnnotatedString,
    modifier: Modifier = Modifier,
    color: Color = Color.Unspecified,
    fontSize: TextUnit = TextUnit.Unspecified,
    fontStyle: FontStyle? = null,
    fontWeight: FontWeight? = null,
    fontFamily: FontFamily? = null,
    letterSpacing: TextUnit = TextUnit.Unspecified,
    textDecoration: TextDecoration? = null,
    textAlign: TextAlign? = null,
    lineHeight: TextUnit = TextUnit.Unspecified,
    overflow: TextOverflow = TextOverflow.Clip,
    softWrap: Boolean = true,
    maxLines: Int = Int.MAX_VALUE,
    inlineContent: Map<String, InlineTextContent> = mapOf(),
    onTextLayout: (TextLayoutResult) -> Unit = {
   },
    style: TextStyle = LocalTextStyle.current
) {
   
    val textColor = color.takeOrElse {
   
        style.color.takeOrElse {
   
            LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
        }
    }
    val mergedStyle = style.merge(
        TextStyle(
            color = textColor,
            fontSize = fontSize,
            fontWeight = fontWeight,
            textAlign = textAlign,
            lineHeight = lineHeight,
            fontFamily = fontFamily,
            textDecoration = textDecoration,
            fontStyle = fontStyle,
            letterSpacing = letterSpacing
        )
    )
    BasicText(
        text,
        modifier,
        mergedStyle,
        onTextLayout,
        overflow,
        softWrap,
        maxLines,
        inlineContent
    )
}

然后就到了Foundation层的BasicText,就是直接调用CoreText

@Composable
fun BasicText(
    text: AnnotatedString,
    modifier: Modifier = Modifier,
    style: TextStyle = TextStyle.Default,
    onTextLayout: (TextLayoutResult) -> Unit = {
   },
    overflow: TextOverflow = TextOverflow.Clip,
    softWrap: Boolean = true,
    maxLines: Int = Int.MAX_VALUE,
    inlineContent: Map<String, InlineTextContent> = mapOf(),
) {
   
    CoreText(
        text,
        modifier.semantics {
    this.text = text },
        style,
        softWrap,
        overflow,
        maxLines,
        inlineContent,
        onTextLayout
    )
}

再到UI层的CoreText,有remember的state,有controller,还有生成最终LayoutNode的Layout方法

@Composable
@OptIn(InternalFoundationTextApi::class)
internal fun CoreText(
    text: AnnotatedString,
    modifier: Modifier = Modifier,
    style: TextStyle,
    softWrap: Boolean,
    overflow: TextOverflow,
    maxLines: Int,
    inlineContent: Map<String, InlineTextContent>,
    onTextLayout: (TextLayoutResult) -> Unit
) {
   
    require(maxLines > 0) {
    "maxLines should be greater than 0" }

    // selection registrar, if no SelectionContainer is added ambient value will be null
    val selectionRegistrar = LocalSelectionRegistrar.current
    val density = LocalDensity.current
    val resourceLoader = LocalFontLoader.current
    val selectionBackgroundColor = LocalTextSelectionColors.current.backgroundColor

    val (placeholders, inlineComposables) = resolveInlineContent(text, inlineContent)

    // The ID used to identify this CoreText. If this CoreText is removed from the composition
    // tree and then added back, this ID should stay the same.
    // Notice that we need to update selectable ID when the input text or selectionRegistrar has
    // been updated.
    // When text is updated, the selection on this CoreText becomes invalid. It can be treated
    // as a brand new CoreText.
    // When SelectionRegistrar is updated, CoreText have to request a new ID to avoid ID collision.
    val selectableId = rememberSaveable(text, selectionRegistrar) {
   
        selectionRegistrar?.nextSelectableId() ?: SelectionRegistrar.InvalidSelectableId
    }
    val state = remember {
   
        TextState(
            TextDelegate(
                text = text,
                style = style,
                density = density,
                softWrap = softWrap,
                resourceLoader = resourceLoader,
                overflow = overflow,
                maxLines = maxLines,
                placeholders = placeholders
            ),
            selectableId
        )
    }
    state.textDelegate = updateTextDelegate(
        current = state.textDelegate,
        text = text,
        style = style,
        density = density,
        softWrap = softWrap,
        resourceLoader = resourceLoader,
        overflow = overflow,
        maxLines = maxLines,
        placeholders = placeholders
    )
    state.onTextLayout = onTextLayout
    state.selectionBackgroundColor = selectionBackgroundColor

    val controller = remember {
    TextController(state) }
    controller.update(selectionRegistrar)

    Layout(
        content = if (inlineComposables.isEmpty()) {
   
            {
   }
        } else {
   
            {
    InlineChildren(text, inlineComposables) }
        },
        modifier = modifier
            .then(controller.modifiers)
            .then(
                if (selectionRegistrar != null) {
   
                    if (isInTouchMode) {
   
                        Modifier.pointerInput(controller.longPressDragObserver) {
   
                            detectDragGesturesAfterLongPressWithObserver(
                                controller.longPressDragObserver
                            )
                        }
                    } else {
   
                        Modifier.pointerInput(controller.mouseSelectionObserver) {
   
                            mouseSelectionDetector(
                                controller.mouseSelectionObserver,
                                finalPass = true
                            )
                        }
                    }
                } else {
   
                    Modifier
                }
            ),
        measurePolicy = controller.measurePolicy
    )

    DisposableEffect(selectionRegistrar, effect = controller.commit)
}

Compose Compiler

Compose Compiler层其实是kotlin的Compiler plugin

composeOptions {
   
        kotlinCompilerExtensionVersion compose_version
        kotlinCompilerVersion '1.5.10'
    }

虽然Compose在我们一般使用中的表现似乎就只是一个@Composable的注解,但是处理这个注解的并不是一个apt,而是一个Compiler plugin。这个@Composable注解某种意义上不能算是一个普通的注解,主要原因还是因为Compiler plugin和apt能做的事情差太多了。

Kotlin Compiler Plugin和apt的区别

  1. apt运行在实际编译之前,插件是在实际运行之中调用
  2. 插件的速度比apt快很多
  3. 插件可以处理/获取的信息更多,包括静态的代码检测(idea输入中的时候的检测),输出java/IR,支持多平台
  4. 插件没有文档,并且需要适配kotlin版本(这也是为什么compose需要特定的kotlin版本的原因),apt有文档
  5. 插件太难了

这个太难了我也没有深入学习,参考资料在此

另外谷歌官方还有一个推荐用来替代apt的东西,Kotlin Symbol Processing (KSP) KSP是用来开发轻量级插件,速度快,也有文档,不用关心kotlin版本变化,虽然功能上比完整Compiler插件少一点但是也很强大,有兴趣可以看看。

Compose Compiler结构

在这里插入图片描述

上张图,有兴趣的自己看吧

Compose的注解(们)

@Composable

首先从概念上来说,@Composable的方法的意义是发射一个节点,不管是UI节点或者是别的数据节点,这个从下面的反编译的结果也是可以看到的。

@ExperimentalComposeApi/@ComposeCompilerApi/@InternalComposeApi

跟平常使用没什么关系

@DisallowComposableCalls

禁止标注的方法内部调用@Composable方法,用于防止inline方法里的lambda方法参数发射节点。举个例子最常用的remember方法

@Composable
inline fun <T> remember(calculation: @DisallowComposableCalls () -> T): T =
    currentComposer.cache(false, calculation)
@ReadOnlyComposable

不生成节点只读的@Composable,举个例子

@Composable
@ReadOnlyComposable
fun stringResource(@StringRes id: Int): String {
   
    val resources = resources()
    return resources.getString(id)
}

编译后

public static final String stringResource(int id, Composer $composer, int $changed) {
   
        ComposerKt.sourceInformationMarkerStart($composer, 383449052, "C(stringResource)35@1191L11:StringResources.android.kt#ccshc7");
        String string = resources($composer, 0).getString(id);
        Intrinsics.checkNotNullExpressionValue(string, "resources.getString(id)");
        ComposerKt.sourceInformationMarkerEnd($composer);
        return string;
    }
@NonRestartableComposable

标记的方法生成的结果不可重启/跳过

@Composable
@NonRestartableComposable
fun NonRestartableTestWithSth(text: String) {
   
    Text(text = text)
}
public static final void NonRestartableTestWithSth(String text, Composer $composer, int $changed) {
   
        Intrinsics.checkNotNullParameter(text, "text");
        $composer.startReplaceableGroup(1990981932);
        ComposerKt.sourceInformation($composer, "C(NonRestartableTestWithSth)66@1046L17:Test2.kt#ptgicz");
        TextKt.m1011TextfLXpl1I(text, null, 0, 0, null, null, null, 0, null, null, 0, 0, false, 0, null, null, $composer, $changed & 14, 64, 65534);
        $composer.endReplaceableGroup();
    }

可以看到变成了ReplaceableGroup,并且changed也没有用到

@Immutable/@Stable

定义略有不同,但都是标记纯函数或者不可变成员等等帮助Compiler优化recompose过程

Compose Compiler对代码做的事

@Composable
fun EmptyTest() {
   

}

@Composable
fun notEmptyTest(): Int {
   
    return 1
}

@Composable
fun ComposableLambdaTest(t: @Composable () -> Unit) {
   
    t()
}

@Composable
fun ComposableLambdaWithParamTest(t: @Composable (i:Int) -> Unit) {
   
    t(1)
}

@Composable
fun LambdaTest(t: () -> Unit) {
   
    t()
}

@Composable
fun ComposableLambdaReturnTest(t: @Composable () -> Int) {
   
    t()
}

@Composable
fun TextTest(text:String){
   
    Text(text = text)
}

@Composable
fun RememberTest() {
   
    var i by remember {
   
        mutableStateOf(1)
    }
    Button(onClick = {
    i++ }) {
   
        Text(text = "test")
    }
}
 public static final void EmptyTest(Composer $composer, int $changed) {
   
        Composer $composer2 = $composer.startRestartGroup(-532712360);
        ComposerKt.sourceInformation($composer2, "C(EmptyTest):Test2.kt#ptgicz");
        if ($changed == 0 && $composer2.getSkipping()) {
   
            $composer2.skipToGroupEnd();
        }
        ScopeUpdateScope endRestartGroup = $composer2.endRestartGroup();
        if (endRestartGroup != null) {
   
            endRestartGroup.updateScope(new Test2Kt$EmptyTest$1($changed));
        }
    }

    public static final int notEmptyTest(Composer $composer, int $changed) {
   
        $composer.startReplaceableGroup(1071305719);
        ComposerKt.sourceInformation($composer, "C(notEmptyTest):Test2.kt#ptgicz");
        int r0 = LiveLiterals$Test2Kt.INSTANCE.m3723Int$funnotEmptyTest();
        $composer.endReplaceableGroup();
        return r0;
    }

    public static final void ComposableLambdaTest(Function2<? super Composer, ? super Integer, Unit> function2, Composer $composer, 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值