文章目录
- 关于Compose的一些事
-
- Compose的结构
- Compose Compiler
- Compose Runtime
- 基于Android View的Compose
关于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的区别
- apt运行在实际编译之前,插件是在实际运行之中调用
- 插件的速度比apt快很多
- 插件可以处理/获取的信息更多,包括静态的代码检测(idea输入中的时候的检测),输出java/IR,支持多平台
- 插件没有文档,并且需要适配kotlin版本(这也是为什么compose需要特定的kotlin版本的原因),apt有文档
- 插件太难了
这个太难了我也没有深入学习,参考资料在此
另外谷歌官方还有一个推荐用来替代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,