关于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" }
val selectionRegistrar = LocalSelectionRegistrar.current
val density = LocalDensity.current
val resourceLoader = LocalFontLoader.current
val selectionBackgroundColor = LocalTextSelectionColors.current.backgroundColor
val (placeholders, inlineComposables) = resolveInlineContent(text, inlineContent)
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的注解(们)
@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, int $changed) {
Intrinsics.checkNotNullParameter(function2, "t");
Composer $composer2 = $composer.startRestartGroup(-209824173);
ComposerKt.sourceInformation($composer2, "C(ComposableLambdaTest)24@398L3:Test2.kt#ptgicz");
int $dirty = $changed;
if (($changed & 14) == 0) {
$dirty |= $composer2.changed(function2) ? 4 : 2;
}
if ((($dirty & 11) ^ 2) != 0 || !$composer2.getSkipping()) {
function2.invoke($composer2, Integer.valueOf($dirty & 14));
} else {
$composer2.skipToGroupEnd();
}
ScopeUpdateScope endRestartGroup = $composer2.endRestartGroup();
if (endRestartGroup != null) {
endRestartGroup.updateScope(new Test2Kt$ComposableLambdaTest$1(function2, $changed));
}
}
public static final void ComposableLambdaWithParamTest(Function3<? super Integer, ? super Composer, ? super Integer, Unit> function3, Composer $composer, int $changed) {
Intrinsics.checkNotNullParameter(function3, "t");
Composer $composer2 = $composer.startRestartGroup(-2040920042);
ComposerKt.sourceInformation($composer2, "C(ComposableLambdaWithParamTest)30@520L4:Test2.kt#ptgicz");
int $dirty = $changed;
if (($changed & 14) == 0) {
$dirty |= $composer2.changed(function3) ? 4 : 2;
}
if ((($dirty & 11) ^ 2) != 0 || !$composer2.getSkipping()) {
function3.invoke(Integer.valueOf(LiveLiterals$Test2Kt.INSTANCE.m3723Int$arg0$callinvoke$funComposableLambdaWithParamTest()), $composer2, Integer.valueOf(($dirty << 3) & 112));
} else {
$composer2.skipToGroupEnd();
}
ScopeUpdateScope endRestartGroup = $composer2.endRestartGroup();
if (endRestartGroup != null) {
endRestartGroup.updateScope(new Test2Kt$ComposableLambdaWithParamTest$1(function3, $changed));
}
}
public static final void LambdaTest(Function0<Unit> function0, Composer $composer, int $changed) {
Intrinsics.checkNotNullParameter(function0, "t");
Composer $composer2 = $composer.startRestartGroup(2050120749);
ComposerKt.sourceInformation($composer2, "C(LambdaTest):Test2.kt#ptgicz");
int $dirty = $changed;
if (($changed & 14) == 0) {
$dirty |= $composer2.changed(function0) ? 4 : 2;
}
if ((($dirty & 11) ^ 2) != 0 || !$composer2.getSkipping()) {
function0.invoke();
} else {
$composer2.skipToGroupEnd();
}
ScopeUpdateScope endRestartGroup = $composer2.endRestartGroup();
if (endRestartGroup != null) {
endRestartGroup.updateScope(new Test2Kt$LambdaTest$1(function0, $changed));
}
}
public static final void ComposableLambdaReturnTest(Function2<? super Composer, ? super Integer, Integer> function2, Composer $composer, int $changed) {
Intrinsics.checkNotNullParameter(function2, "t");
Composer $composer2 = $composer.startRestartGroup(992700103);
ComposerKt.sourceInformation($composer2, "C(ComposableLambdaReturnTest)34@535L3:Test2.kt#ptgicz");
int $dirty = $changed;
if (($changed & 14) == 0) {
$dirty |= $composer2.changed(function2) ? 4 : 2;
}
if ((($dirty & 11) ^ 2) != 0 || !$composer2.getSkipping()) {
function2.invoke($composer2, Integer.valueOf($dirty & 14));
} else {
$composer2.skipToGroupEnd();
}
ScopeUpdateScope endRestartGroup = $composer2.endRestartGroup();
if (endRestartGroup != null) {
endRestartGroup.updateScope(new Test2Kt$ComposableLambdaReturnTest$1(function2, $changed));
}
}
public static final void TextTest(String text, Composer $composer, int $changed) {
Composer $composer2;
Intrinsics.checkNotNullParameter(text, "text");
Composer $composer3 = $composer.startRestartGroup(-2000670640);
ComposerKt.sourceInformation($composer3, "C(TextTest)39@585L17:Test2.kt#ptgicz");
int $dirty = $changed;
if (($changed & 14) == 0) {
$dirty |= $composer3.changed(text) ? 4 : 2;
}
if ((($dirty & 11) ^ 2) != 0 || !$composer3.getSkipping()) {
$composer2 = $composer3;
TextKt.m1011TextfLXpl1I(text, null, 0, 0, null, null, null, 0, null, null, 0, 0, false, 0, null, null, $composer2, $dirty & 14, 64, 65534);
} else {
$composer3.skipToGroupEnd();
$composer2 = $composer3;
}
ScopeUpdateScope endRestartGroup = $composer2.endRestartGroup();
if (endRestartGroup != null) {
endRestartGroup.updateScope(new Test2Kt$TextTest$1(text, $changed));
}
}
public static final void RememberTest(Composer $composer, int $changed) {
Object value$iv$iv;
Object value$iv$iv2;
Composer $composer2 = $composer.startRestartGroup(2143490257);
ComposerKt.sourceInformation($composer2, "C(RememberTest)45@685L42,48@749L7,48@732L61:Test2.kt#ptgicz");
if ($changed != 0 || !$composer2.getSkipping()) {
$composer2.startReplaceableGroup(-3687241);
ComposerKt.sourceInformation($composer2, "C(remember):Composables.kt#9igjgp");
Object it$iv$iv = $composer2.rememberedValue();
if (it$iv$iv == Composer.Companion.getEmpty()) {
value$iv$iv = SnapshotStateKt.mutableStateOf$default(Integer.valueOf(LiveLiterals$Test2Kt.INSTANCE.m3723xbaeee748()), null, 2, null);
$composer2.updateRememberedValue(value$iv$iv);
} else {
value$iv$iv = it$iv$iv;
}
$composer2.endReplaceableGroup();
MutableState i$delegate = (MutableState) value$iv$iv;
$composer2.startReplaceableGroup(-3686930);
ComposerKt.sourceInformation($composer2, "C(remember)P(1):Composables.kt#9igjgp");
boolean invalid$iv$iv = $composer2.changed(i$delegate);
Object it$iv$iv2 = $composer2.rememberedValue();
if (invalid$iv$iv || it$iv$iv2 == Composer.Companion.getEmpty()) {
value$iv$iv2 = (Function0) new Test2Kt$RememberTest$1$1(i$delegate);
$composer2.updateRememberedValue(value$iv$iv2);
} else {
value$iv$iv2 = it$iv$iv2;
}
$composer2.endReplaceableGroup();
ButtonKt.Button((Function0) value$iv$iv2, null, false, null, null, null, null, null, null, ComposableSingletons$Test2Kt.INSTANCE.m3688getLambda1$app_debug(), $composer2, 0, 510);
} else {
$composer2.skipToGroupEnd();
}
ScopeUpdateScope endRestartGroup = $composer2.endRestartGroup();
if (endRestartGroup != null) {
endRestartGroup.updateScope(new Test2Kt$RememberTest$2($changed));
}
}
public static final int m3798RememberTest$lambda1(MutableState<Integer> $this$getValue$iv) {
return $this$getValue$iv.getValue().intValue();
}
public static final void m3799RememberTest$lambda2(MutableState<Integer> mutableState, int value) {
mutableState.setValue(Integer.valueOf(value));
}
根据这几个常用的例子,也可以看到Compiler对于代码的修改。首先可以看到,对于一个@Composable方法,就算是空的,也会注入一个composer和一个changed,composer是直接操作生成节点的类,changed是用来智能跳过未改变部分的一个参数。对于@Composable的lambda方法会生成一个固定格式的方法,前面是入参,中间是compoesr和changed,最后是返回值。Compiler层对于代码的表现就这些,要继续理解整个流程,下面继续分析Runtime层。
Compose Runtime
Compose Runtime都有些什么
按顺序介绍一下比较重要的几个部分:
-
Applier接口:操作生成的Node,是使得生成的节点产生管理的地方 -
@Composable:标注产生group的方法 -
Composer接口:直接操作SlotTable和Applier的类,唯一实现ComposerImpl -
Composition接口:有一个继承的接口ControlledComposition,ControlledComposition有一个Runtime层唯一实现CompositionImpl,用于连接CompositionContext和Composer,持有一个composable,是setContent和applyChanges发生的地方 -
CompositionLocal:Composition的本地变量,注册之后用provide提供,用current拿,会在编译的时候生成代码 -
Effects:安全保存side-effect的一些方法。side-effect指的是一些可能会修改全局变量或者读写IO的方法产生的副作用,@Composable方法应该都是无副作用的,因为@Composable方法会运行很多次,而且也不好准确预测一定会运行几次,所以这种side-effect需要用effects里的方法操作一下。 -
MutableState&Snapshot:常用的MutableState是一个继承SnapshotMutableState,SnapshotMutableState和Snapshot是结合使用的,state是用来向Snapshot读写一个值,Snapshot有两个作用,一个是可以订阅读写的observer,另一个是版本隔离,可以切一个分支Snapshot出来然后在分支Shapshot上修改再apply到主的Snapshot,并且可以定义冲突的policy,这个也支持recompose可以并发的运行。 -
PausableMonotonicFrameClock:可以pause和resume的FrameClock,animation都是依靠这个FrameClock,对于Android而言,就是包了一层AndroidUiFrameClock,最后还是调用的Choreographer.FrameCallback -
CompositionContext&Recomposer&CompositionContextImpl:Recomposer和CompositionContextImpl是两种实现。Recomposer是一颗树对应的唯一一个控制整体recompose流程的东西,存了所有的compositions。CompositionContextImpl是ComposerImpl的内部类,存了一些composers,主要是SubCompose用(LazyList之类的)。 -
SlotTable:一个基于GapBuffer的存储结构,groups里存了id等信息,slots里对应groups的数据,一个group可能存了多个slot。每五位groups表示了一个group的信息
SlotTable(GapBuffer)的插入过程
以下插入过程是抽象的过程,不是实际的SlotTable插入过程(因为我看不懂)
当GapBuffer为空的时候是这样的
在插入一些数据之后
然后当我们需要更新slot1的时候
首先先把Gap移到需要插入的位置
然后再插入
移除的过程更简单,先把Gap移到需要移除的位置后面,然后直接把Gap扩大就行了
使用GapBuffer的好处在于,由于UI大部分都是整块更新的,所以可以牺牲移动Gap的性能O(n),让更新UI做到O(1)
举个例子,一个简单的counter:
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Button(
text = "Count: $count",
onClick = { count += 1 }
)
}
注入composer之后差不多是这样:
fun Counter($composer: Composer) {
$composer.start(123)
var count by remember($composer) { mutableStateOf(0) }
Button(
$composer,
text = "Count: $count",
onClick = { count += 1 },
)
$composer.end()
}
它存储起来的时候应该是这样:
Recompose的插入删除的过程也是一样的,顺序的对比已有的group的id和新的id决定要不要替换等等,实际的过程比上面的复杂很多。
Android UI的setContent过程(初始化过程)
public fun ComponentActivity.setContent(
parent: CompositionContext? = null,
content: @Composable () -> Unit
) {
...
ComposeView(this).apply {
...
setContent(content)
...
setContentView(this, DefaultActivityContentLayoutParams)
}
}
从上面可以看到我们最底层的view是一个ComposeView
ComposeView
fun setContent(content: @Composable () -> Unit) {
...
createComposition()
...
}
fun createComposition() {
...
ensureCompositionCreated()
}
private fun ensureCompositionCreated() {
...
composition = setContent(resolveParentCompositionContext()) {
Content()
...
}
private fun resolveParentCompositionContext() = parentContext
?: findViewTreeCompositionContext()?.also { cachedViewTreeCompositionContext = it }
?: cachedViewTreeCompositionContext
?: windowRecomposer.also { cachedViewTreeCompositionContext = it }
private fun View.createLifecycleAwareViewTreeRecomposer(): Recomposer {
val currentThreadContext = AndroidUiDispatcher.CurrentThread
val pausableClock = currentThreadContext[MonotonicFrameClock]?.let {
PausableMonotonicFrameClock(it).apply { pause() }
}
val contextWithClock = currentThreadContext + (pausableClock ?: EmptyCoroutineContext)
val recomposer = Recomposer(contextWithClock)
val runRecomposeScope = CoroutineScope(contextWithClock)
val viewTreeLifecycleOwner = checkNotNull(ViewTreeLifecycleOwner.get(this)) {
"ViewTreeLifecycleOwner not found from $this"
}
addOnAttachStateChangeListener(
object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View?) {}
override fun onViewDetachedFromWindow(v: View?) {
removeOnAttachStateChangeListener(this)
recomposer.cancel()
}
}
)
viewTreeLifecycleOwner.lifecycle.addObserver(
object : LifecycleEventObserver {
override fun onStateChanged(lifecycleOwner: LifecycleOwner, event: Lifecycle.Event) {
val self = this
@Suppress("NON_EXHAUSTIVE_WHEN")
when (event) {
Lifecycle.Event.ON_CREATE ->
runRecomposeScope.launch(start = CoroutineStart.UNDISPATCHED) {
try {
recomposer.runRecomposeAndApplyChanges()
} finally {
lifecycleOwner.lifecycle.removeObserver(self)
}
}
Lifecycle.Event.ON_START -> pausableClock?.resume()
Lifecycle.Event.ON_STOP -> pausableClock?.pause()
Lifecycle.Event.ON_DESTROY -> {
recomposer.cancel()
}
}
}
}
)
return recomposer
}
internal fun ViewGroup.setContent(
parent: CompositionContext,
content: @Composable () -> Unit
): Composition {
GlobalSnapshotManager.ensureStarted()
val composeView =
if (childCount > 0) {
getChildAt(0) as? AndroidComposeView
} else {
removeAllViews(); null
} ?: AndroidComposeView(context).also { addView(it.view, DefaultLayoutParams) }
return doSetContent(composeView, parent, content)
}
@OptIn(InternalComposeApi::class)
private fun doSetContent(
owner: AndroidComposeView,
parent: CompositionContext,
content: @Composable () -> Unit
): Composition {
if (inspectionWanted(owner)) {
owner.setTag(
R.id.inspection_slot_table_set,
Collections.newSetFromMap(WeakHashMap<CompositionData, Boolean>())
)
enableDebugInspectorInfo()
}
val original = Composition(UiApplier(owner.root), parent)
val wrapped = owner.view.getTag(R.id.wrapped_composition_tag)
as? WrappedComposition
?: WrappedComposition(owner, original).also {
owner.view.setTag(R.id.wrapped_composition_tag, it)
}
wrapped.setContent(content)
return wrapped
}
@OptIn(InternalComposeApi::class)
override fun setContent(content: @Composable () -> Unit) {
owner.setOnViewTreeOwnersAvailable {
if (!disposed) {
val lifecycle = it.lifecycleOwner.lifecycle
lastContent = content
if (addedToLifecycle == null) {
addedToLifecycle = lifecycle
lifecycle.addObserver(this)
} else if (lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
original.setContent {
@Suppress("UNCHECKED_CAST")
val inspectionTable =
owner.getTag(R.id.inspection_slot_table_set) as?
MutableSet<CompositionData>
?: (owner.parent as? View)?.getTag(R.id.inspection_slot_table_set)
as? MutableSet<CompositionData>
if (inspectionTable != null) {
@OptIn(InternalComposeApi::class)
inspectionTable.add(currentComposer.compositionData)
currentComposer.collectParameterInformation()
}
LaunchedEffect(owner) { owner.keyboardVisibilityEventLoop() }
LaunchedEffect(owner) { owner.boundsUpdatesEventLoop() }
CompositionLocalProvider(LocalInspectionTables provides inspectionTable) {
ProvideAndroidCompositionLocals(owner, content)
}
}
}
}
}
}
override fun setContent(content: @Composable () -> Unit) {
check(!disposed) { "The composition is disposed" }
this.composable = content
parent.composeInitial(this, composable)
}
internal override fun composeInitial(
composition: ControlledComposition,
content: @Composable () -> Unit
) {
val composerWasComposing = composition.isComposing
composing(composition, null) {
composition.composeContent(content)
}
if (!composerWasComposing) {
Snapshot.notifyObjectsInitialized()
}
composition.applyChanges()
synchronized(stateLock) {
if (_state.value > State.ShuttingDown) {
if (composition !in knownCompositions) {
knownCompositions += composition
}
}
}
if (!composerWasComposing) {
Snapshot.notifyObjectsInitialized()
}
}
private inline fun <T> composing(
composition: ControlledComposition,
modifiedValues: IdentityArraySet<Any>?,
block: () -> T
): T {
val snapshot = Snapshot.takeMutableSnapshot(
readObserverOf(composition), writeObserverOf(composition, modifiedValues)
)
try {
return snapshot.enter(block)
} finally {
applyAndCheck(snapshot)
}
}
override fun composeContent(content: @Composable () -> Unit) {
synchronized(lock) {
drainPendingModificationsForCompositionLocked()
composer.composeContent(takeInvalidations(), content)
}
}
internal fun composeContent(
invalidationsRequested: IdentityArrayMap<RecomposeScopeImpl, IdentityArraySet<Any>?>,
content: @Composable () -> Unit
) {
runtimeCheck(changes.isEmpty()) { "Expected applyChanges() to have been called" }
doCompose(invalidationsRequested, content)
}
private fun doCompose(
invalidationsRequested: IdentityArrayMap<RecomposeScopeImpl, IdentityArraySet<Any>?>,
content: (@Composable () -> Unit)?
) {
runtimeCheck(!isComposing) { "Reentrant composition is not supported" }
trace("Compose:recompose") {
snapshot = currentSnapshot()
invalidationsRequested.forEach { scope, set ->
val location = scope.anchor?.location ?: return
invalidations.add(Invalidation(scope, location, set))
}
invalidations.sortBy { it.location }
nodeIndex = 0
var complete = false
isComposing = true
try {
startRoot()
observeDerivedStateRecalculations(
start = {
childrenComposing++
},
done = {
childrenComposing--
},
) {
if (content != null) {
startGroup(invocationKey, invocation)
invokeComposable(this, content)
endGroup()
} else {
skipCurrentGroup()
}
}
endRoot()
complete = true
} finally {
isComposing = false
invalidations.clear()
providerUpdates.clear()
if (!complete) abortRoot()
}
}
}
然后在我们的@Composable方法中,最终都会调用到以下两个方法
@Suppress("NONREADONLY_CALL_IN_READONLY_COMPOSABLE", "UnnecessaryLambdaCreation")
@Composable inline fun <T : Any, reified E : Applier<*>> ComposeNode(
noinline factory: () -> T,
update: @DisallowComposableCalls Updater<T>.() -> Unit
) {
if (currentComposer.applier !is E) invalidApplier()
currentComposer.startNode()
if (currentComposer.inserting) {
currentComposer.createNode { factory() }
} else {
currentComposer.useNode()
}
Updater<T>(currentComposer).update()
currentComposer.endNode()
}
@Suppress("NONREADONLY_CALL_IN_READONLY_COMPOSABLE", "UnnecessaryLambdaCreation")
@Composable inline fun <T : Any, reified E : Applier<*>> ReusableComposeNode(
noinline factory: () -> T,
update: @DisallowComposableCalls Updater<T>.() -> Unit
) {
if (currentComposer.applier !is E) invalidApplier()
currentComposer.startReusableNode()
if (currentComposer.inserting) {
currentComposer.createNode { factory() }
} else {
currentComposer.useNode()
}
currentComposer.disableReusing()
Updater<T>(currentComposer).update()
currentComposer.enableReusing()
currentComposer.endNode()
}
可以看到都调用了Composer.createNode
@Suppress("UNUSED")
override fun <T> createNode(factory: () -> T) {
validateNodeExpected()
runtimeCheck(inserting) { "createNode() can only be called when inserting" }
val insertIndex = nodeIndexStack.peek()
val groupAnchor = writer.anchor(writer.parent)
groupNodeCount++
recordFixup { applier, slots, _ ->
@Suppress("UNCHECKED_CAST")
val node = factory()
slots.updateNode(groupAnchor, node)
@Suppress("UNCHECKED_CAST") val nodeApplier = applier as Applier<T>
nodeApplier.insertTopDown(insertIndex, node)
applier.down(node)
}
recordInsertUpFixup { applier, slots, _ ->
@Suppress("UNCHECKED_CAST")
val nodeToInsert = slots.node(groupAnchor)
applier.up()
@Suppress("UNCHECKED_CAST") val nodeApplier = applier as Applier<Any?>
nodeApplier.insertBottomUp(insertIndex, nodeToInsert)
}
}
insertFixups列表是在end一个节点的时候如果需要记录插入再使用
private fun recordInsert(anchor: Anchor) {
if (insertFixups.isEmpty()) {
val insertTable = insertTable
recordSlotEditingOperation { _, slots, _ ->
slots.beginInsert()
slots.moveFrom(insertTable, anchor.toIndexFor(insertTable))
slots.endInsert()
}
} else {
val fixups = insertFixups.toMutableList()
insertFixups.clear()
realizeUps()
realizeDowns()
val insertTable = insertTable
recordSlotEditingOperation { applier, slots, rememberManager ->
insertTable.write { writer ->
fixups.fastForEach { fixup ->
fixup(applier, writer, rememberManager)
}
}
slots.beginInsert()
slots.moveFrom(insertTable, anchor.toIndexFor(insertTable))
slots.endInsert()
}
}
}
override fun applyChanges() {
synchronized(lock) {
val manager = RememberEventDispatcher(abandonSet)
try {
applier.onBeginChanges()
slotTable.write { slots ->
val applier = applier
changes.fastForEach { change ->
change(applier, slots, manager)
}
changes.clear()
}
applier.onEndChanges()
manager.dispatchRememberObservers()
manager.dispatchSideEffects()
if (pendingInvalidScopes) {
pendingInvalidScopes = false
observations.removeValueIf { scope -> !scope.valid }
derivedStates.removeValueIf { derivedValue -> derivedValue !in observations }
}
} finally {
manager.dispatchAbandons()
}
drainPendingModificationsLocked()
}
}
最后看一眼对于Android UI,UIApplier是如何插入LayoutNode的
internal class UiApplier(
root: LayoutNode
) : AbstractApplier<LayoutNode>(root) {
...
override fun insertBottomUp(index: Int, instance: LayoutNode) {
current.insertAt(index, instance)
}
...
}
internal fun insertAt(index: Int, instance: LayoutNode) {
check(instance._foldedParent == null) {
"Cannot insert $instance because it already has a parent." +
" This tree: " + debugTreeToString() +
" Other tree: " + instance._foldedParent?.debugTreeToString()
}
check(instance.owner == null) {
"Cannot insert $instance because it already has an owner." +
" This tree: " + debugTreeToString() +
" Other tree: " + instance.debugTreeToString()
}
if (DebugChanges) {
println("$instance added to $this at index $index")
}
instance._foldedParent = this
_foldedChildren.add(index, instance)
onZSortedChildrenInvalidated()
if (instance.isVirtual) {
require(!isVirtual) { "Virtual LayoutNode can't be added into a virtual parent" }
virtualChildrenCount++
}
invalidateUnfoldedVirtualChildren()
instance.outerLayoutNodeWrapper.wrappedBy = innerLayoutNodeWrapper
val owner = this.owner
if (owner != null) {
instance.attach(owner)
}
}
这个LayoutNode是怎么measure,layout,draw的部分就和Runtime没关系了,以后再说
Recompose的过程
首先明确一下我们这里分析的recompose流程是分析因为mustableState发生变化引起的recompose(别的情况暂时我也不知道)
fun <T> mutableStateOf(
value: T,
policy: SnapshotMutationPolicy<T> = structuralEqualityPolicy()
): MutableState<T> = createSnapshotMutableState(value, policy)
internal open class SnapshotMutableStateImpl<T>(
value: T,
override val policy: SnapshotMutationPolicy<T>
) : StateObject, SnapshotMutableState<T> {
@Suppress("UNCHECKED_CAST")
override var value: T
get() = next.readable(this).value
set(value) = next.withCurrent {
if (!policy.equivalent(it.value, value)) {
next.overwritable(this, it) { this.value = value }
}
}
private var next: StateStateRecord<T> = StateStateRecord(value)
}
internal inline fun <T : StateRecord, R> T.overwritable(
state: StateObject,
candidate: T,
block: T.() -> R
): R {
var snapshot: Snapshot = snapshotInitializer
return sync {
snapshot = Snapshot.current
this.overwritableRecord(state, snapshot, candidate).block()
}.also {
notifyWrite(snapshot, state)
}
}
根据上面的snapshot的类型区别(MutableSnapshot或者GlobalSnapshot)会有两个路径。
MutableSnapshot就是在@Composable中调用时候的Snapshot,GlobalSnapshot就是在@Composable外部(比如一个Button的onClick的方法里)
GlobalSnapshot中
对于GlobalSnapshot,上面提到过,在ViewGroup.setContent方法里,会调用GlobalSnapshotManager.ensureStarted()
internal object GlobalSnapshotManager {
private val started = AtomicBoolean(false)
fun ensureStarted() {
if (started.compareAndSet(false, true)) {
val channel = Channel<Unit>(Channel.CONFLATED)
CoroutineScope(AndroidUiDispatcher.Main).launch {
channel.consumeEach {
Snapshot.sendApplyNotifications()
}
}
Snapshot.registerGlobalWriteObserver {
channel.trySend(Unit)
}
}
}
}
这个很简单,注册一个writeObserver,接收到事件就Snapshot.sendApplyNotifications()
fun sendApplyNotifications() {
val changes = sync {
currentGlobalSnapshot.get().modified?.isNotEmpty() == true
}
if (changes)
advanceGlobalSnapshot()
}
private fun <T> advanceGlobalSnapshot(block: (invalid: SnapshotIdSet) -> T): T {
val previousGlobalSnapshot = currentGlobalSnapshot.get()
val result = sync {
takeNewGlobalSnapshot(previousGlobalSnapshot, block)
}
val modified = previousGlobalSnapshot.modified
if (modified != null) {
val observers: List<(Set<Any>, Snapshot) -> Unit> = sync { applyObservers.toMutableList() }
observers.fastForEach { observer ->
observer(modified, previousGlobalSnapshot)
}
}
return result
}
applyObservers有两个注册的地方,一个是SnapshotStateObserver(暂时不知道干嘛的,目测是给UI层用的),另一个是Recomposer的recompositionRunner,会存到Recomposer的snapshotInvalidations列表里
讲完modified数据丢到哪之后就必须要讲一下,Recomposer是怎么监听变化然后自动recompose的
首先入口方法是在Recomposer.runRecomposeAndApplyChanges(或者是runRecomposeConcurrentlyAndApplyChanges,当然这个并发方法Android没用到)的方法,对于Android UI而言是在View.createLifecycleAwareViewTreeRecomposer()调用的时候注册了LifecycleEventObserver,onCreate的时候自动调用的
suspend fun runRecomposeAndApplyChanges() = recompositionRunner { parentFrameClock ->
val toRecompose = mutableListOf<ControlledComposition>()
val toApply = mutableListOf<ControlledComposition>()
while (shouldKeepRecomposing) {
awaitWorkAvailable()
if (
synchronized(stateLock) {
if (!hasFrameWorkLocked) {
recordComposerModificationsLocked()
!hasFrameWorkLocked
} else false
}
) continue
parentFrameClock.withFrameNanos { frameTime ->
if (broadcastFrameClock.hasAwaiters) {
trace("Recomposer:animation") {
broadcastFrameClock.sendFrame(frameTime)
Snapshot.sendApplyNotifications()
}
}
trace("Recomposer:recompose") {
synchronized(stateLock) {
recordComposerModificationsLocked()
compositionInvalidations.fastForEach { toRecompose += it }
compositionInvalidations.clear()
}
val modifiedValues = IdentityArraySet<Any>()
val alreadyComposed = IdentityArraySet<ControlledComposition>()
while (toRecompose.isNotEmpty()) {
try {
toRecompose.fastForEach { composition ->
alreadyComposed.add(composition)
performRecompose(composition, modifiedValues)?.let {
toApply += it
}
}
} finally {
toRecompose.clear()
}
if (modifiedValues.isNotEmpty()) {
synchronized(stateLock) {
knownCompositions.fastForEach { value ->
if (
value !in alreadyComposed &&
value.observesAnyOf(modifiedValues)
) {
toRecompose += value
}
}
}
}
}
if (toApply.isNotEmpty()) {
changeCount++
try {
toApply.fastForEach { composition ->
composition.applyChanges()
}
} finally {
toApply.clear()
}
}
synchronized(stateLock) {
deriveStateLocked()
}
}
}
}
}
private suspend fun awaitWorkAvailable() {
if (!hasSchedulingWork) {
suspendCancellableCoroutine<Unit> { co ->
synchronized(stateLock) {
if (hasSchedulingWork) {
co.resume(Unit)
} else {
workContinuation = co
}
}
}
}
}
private fun deriveStateLocked(): CancellableContinuation<Unit>? {
if (_state.value <= State.ShuttingDown) {
knownCompositions.clear()
snapshotInvalidations.clear()
compositionInvalidations.clear()
compositionsAwaitingApply.clear()
workContinuation?.cancel()
workContinuation = null
return null
}
val newState = when {
runnerJob == null -> {
snapshotInvalidations.clear()
compositionInvalidations.clear()
if (broadcastFrameClock.hasAwaiters) State.InactivePendingWork else State.Inactive
}
compositionInvalidations.isNotEmpty() ||
snapshotInvalidations.isNotEmpty() ||
compositionsAwaitingApply.isNotEmpty() ||
concurrentCompositionsOutstanding > 0 ||
broadcastFrameClock.hasAwaiters -> State.PendingWork
else -> State.Idle
}
_state.value = newState
return if (newState == State.PendingWork) {
workContinuation.also {
workContinuation = null
}
} else null
}
@OptIn(ExperimentalComposeApi::class)
private suspend fun recompositionRunner(
block: suspend CoroutineScope.(parentFrameClock: MonotonicFrameClock) -> Unit
) {
val parentFrameClock = coroutineContext.monotonicFrameClock
withContext(broadcastFrameClock) {
val callingJob = coroutineContext.job
registerRunnerJob(callingJob)
val unregisterApplyObserver = Snapshot.registerApplyObserver { changed, _ ->
synchronized(stateLock) {
if (_state.value >= State.Idle) {
snapshotInvalidations += changed
deriveStateLocked()
} else null
}?.resume(Unit)
}
...
}
private fun performRecompose(
composition: ControlledComposition,
modifiedValues: IdentityArraySet<Any>?
): ControlledComposition? {
if (composition.isComposing || composition.isDisposed) return null
return if (
composing(composition, modifiedValues) {
if (modifiedValues?.isNotEmpty() == true) {
composition.prepareCompose {
modifiedValues.forEach { composition.recordWriteOf(it) }
}
}
composition.recompose()
}
) composition else null
}
override fun recompose(): Boolean = synchronized(lock) {
drainPendingModificationsForCompositionLocked()
composer.recompose(takeInvalidations()).also { shouldDrain ->
if (!shouldDrain) drainPendingModificationsLocked()
}
}
internal fun recompose(
invalidationsRequested: IdentityArrayMap<RecomposeScopeImpl, IdentityArraySet<Any>?>
): Boolean {
runtimeCheck(changes.isEmpty()) { "Expected applyChanges() to have been called" }
if (invalidationsRequested.isNotEmpty() || invalidations.isNotEmpty()) {
doCompose(invalidationsRequested, null)
return changes.isNotEmpty()
}
return false
}
可以看到试图继续死循环的地方相当多
MutableSnapshot中
private inline fun <T> composing(
composition: ControlledComposition,
modifiedValues: IdentityArraySet<Any>?,
block: () -> T
): T {
val snapshot = Snapshot.takeMutableSnapshot(
readObserverOf(composition), writeObserverOf(composition, modifiedValues)
)
try {
return snapshot.enter(block)
} finally {
applyAndCheck(snapshot)
}
}
private fun applyAndCheck(snapshot: MutableSnapshot) {
val applyResult = snapshot.apply()
if (applyResult is SnapshotApplyResult.Failure) {
error(
"Unsupported concurrent change during composition. A state object was " +
"modified by composition as well as being modified outside composition."
)
}
}
基于Android View的Compose
一个别的地方看到的例子
@Composable
fun TextView(
text: String,
onClick: () -> Unit = {}
) {
val context = LocalContext.current
ComposeNode<TextView, ViewApplier>(
factory = {
TextView(context)
},
update = {
set(text) {
this.text = text
}
set(onClick) {
setOnClickListener { onClick() }
}
},
)
}
@Composable
fun LinearLayout(children: @Composable () -> Unit) {
val context = LocalContext.current
ComposeNode<LinearLayout, ViewApplier>(
factory = {
LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
)
}
},
update = {},
content = children,
)
}
class ViewApplier(val view: FrameLayout) : AbstractApplier<View>(view) {
override fun onClear() {
(view as? ViewGroup)?.removeAllViews()
}
override fun insertBottomUp(index: Int, instance: View) {
(current as? ViewGroup)?.addView(instance, index)
}
override fun insertTopDown(index: Int, instance: View) {
}
override fun move(from: Int, to: Int, count: Int) {
TODO()
}
override fun remove(index: Int, count: Int) {
(view as? ViewGroup)?.removeViews(index, count)
}
}
@Composable
fun AndroidViewApp() {
var count by remember { mutableStateOf(1) }
LinearLayout {
TextView(
text = "This is the Android TextView!!",
)
repeat(count) {
TextView(
text = "Android View!!TextView:$it $count",
onClick = {
count++
}
)
}
}
}
private fun runApp(context: Context): FrameLayout {
val composer = Recomposer(Dispatchers.Main)
val channel = Channel<Unit>(Channel.CONFLATED)
CoroutineScope(Dispatchers.Main).launch {
channel.consumeEach {
Snapshot.sendApplyNotifications()
}
}
Snapshot.registerGlobalWriteObserver {
channel.trySend(Unit)
}
val mainScope = MainScope()
mainScope.launch(start = CoroutineStart.UNDISPATCHED) {
withContext(coroutineContext + DefaultMonotonicFrameClock) {
composer.runRecomposeAndApplyChanges()
}
}
mainScope.launch {
composer.state.collect {
println("composer:$it")
}
}
val rootDocument = FrameLayout(context)
Composition(ViewApplier(rootDocument), composer).apply {
setContent {
CompositionLocalProvider(LocalContext provides context) {
AndroidViewApp()
}
}
}
return rootDocument
}
以上,TextView和LinearLayout两个方法是创建ComposeNode的方法,ViewApplier操作实际的ViewGroup树。
最后在runApp方法中,是一个Compose系统需要的最少的东西,一个Recomposer,一个跑recompose的地方,一个Composition,还需要初始化GlobalSnapshot的监听(也可以没有,就是不能在@Composable之外的地方刷新),不操作实际Node连Applier都可以是个空的
|