if (existingComposeView != null) with(existingComposeView) { setParentCompositionContext(parent) setContent(content) } else ComposeView(this).apply { // Set content and parent **before** setContentView // to have ComposeView create the composition on attach setParentCompositionContext(parent) setContent(content) // Set the view tree owners before setting the content view so that the inflation process // and attach listeners will see them already present setOwners() setContentView(this, DefaultActivityContentLayoutParams) } }
funcreateComposition() { check(parentContext != null || isAttachedToWindow) { "createComposition requires either a parent reference or the View to be attached" + "to a window. Attach the View or call setParentCompositionReference." } ensureCompositionCreated() }
internalabstractval compoundHashKey: Int internalabstractval collectingParameterInformation: Boolean /** * The [CoroutineContext] with which effects for the composition will be executed in. **/ abstractval effectCoroutineContext: CoroutineContext internalabstractval recomposeCoroutineContext: CoroutineContext internalabstractfuncomposeInitial( composition: ControlledComposition, content: @Composable () -> Unit ) internalabstractfuninvalidate(composition: ControlledComposition) internalabstractfuninvalidateScope(scope: RecomposeScopeImpl) }
privatefun IrSimpleFunction.copyWithComposerParam(): IrSimpleFunction { assert(explicitParameters.lastOrNull()?.name != KtxNameConventions.COMPOSER_PARAMETER) { "Attempted to add composer param to $this, but it has already been added." } return copy().also { fn -> val oldFn = this
// NOTE: it's important to add these here before we recurse into the body in // order to avoid an infinite loop on circular/recursive calls transformedFunctionSet.add(fn) transformedFunctions[oldFn] = fn
// The overridden symbols might also be composable functions, so we want to make sure // and transform them as well fn.overriddenSymbols = overriddenSymbols.map { it.owner.withComposerParamIfNeeded().symbol }
// if we are transforming a composable property, the jvm signature of the // corresponding getters and setters have a composer parameter. Since Kotlin uses the // lack of a parameter to determine if it is a getter, this breaks inlining for // composable property getters since it ends up looking for the wrong jvmSignature. // In this case, we manually add the appropriate "@JvmName" annotation so that the // inliner doesn't get confused. fn.correspondingPropertySymbol?.let { propertySymbol -> if (!fn.hasAnnotation(DescriptorUtils.JVM_NAME)) { val propertyName = propertySymbol.owner.name.identifier val name = if (fn.isGetter) { JvmAbi.getterName(propertyName) } else { JvmAbi.setterName(propertyName) } fn.annotations += jvmNameAnnotation(name) } }
val valueParametersMapping = explicitParameters .zip(fn.explicitParameters) .toMap()
val currentParams = fn.valueParameters.size val realParams = currentParams - fn.contextReceiverParametersCount
// $composer // val COMPOSER_PARAMETER = Name.identifier("\$composer") val composerParam = fn.addValueParameter { name = KtxNameConventions.COMPOSER_PARAMETER type = composerType.makeNullable() origin = IrDeclarationOrigin.DEFINED isAssignable = true }
// $changed[n] // val CHANGED_PARAMETER = Name.identifier("\$changed") val changed = KtxNameConventions.CHANGED_PARAMETER.identifier for (i in0 until changedParamCount(realParams, fn.thisParamCount)) { fn.addValueParameter( if (i == 0) changed else"$changed$i", context.irBuiltIns.intType ) }
// $default[n] if (oldFn.requiresDefaultParameter()) { val defaults = KtxNameConventions.DEFAULT_PARAMETER.identifier for (i in0 until defaultParamCount(currentParams)) { fn.addValueParameter( if (i == 0) defaults else"$defaults$i", context.irBuiltIns.intType, IrDeclarationOrigin.MASK_FOR_DEFAULT_FUNCTION ) } }
internalclassCompositionImpl( /** * The parent composition from [rememberCompositionContext], for sub-compositions, or the an * instance of [Recomposer] for root compositions. */ privateval parent: CompositionContext,
/** * The applier to use to update the tree managed by the composition. */ privateval applier: Applier<*>,
// Side effects run after lifecycle observers so that any remembered objects // that implement RememberObserver receive onRemembered before a side effect // that captured it and operates on it can run. manager.dispatchRememberObservers() manager.dispatchSideEffects()
... } finally { // Only dispatch abandons if we do not have any late changes. The instances in the // abandon set can be remembered in the late changes. if (this.lateChanges.isEmpty()) manager.dispatchAbandons() } }
@ExperimentalComposeUiApi fun View.createLifecycleAwareWindowRecomposer( coroutineContext: CoroutineContext = EmptyCoroutineContext, lifecycle: Lifecycle? = null ): Recomposer { // Only access AndroidUiDispatcher.CurrentThread if we would use an element from it, // otherwise prevent lazy initialization. val baseContext = if (coroutineContext[ContinuationInterceptor] == null || coroutineContext[MonotonicFrameClock] == null ) { AndroidUiDispatcher.CurrentThread + coroutineContext } else coroutineContext
...
val recomposer = Recomposer(contextWithClockAndMotionScale) val runRecomposeScope = CoroutineScope(contextWithClockAndMotionScale) val viewTreeLifecycle = checkNotNull(lifecycle ?: ViewTreeLifecycleOwner.get(this)?.lifecycle) { ... }
suspendfunrunRecomposeAndApplyChanges() = recompositionRunner { parentFrameClock -> val toRecompose = mutableListOf<ControlledComposition>() val toInsert = mutableListOf<MovableContentStateReference>() val toApply = mutableListOf<ControlledComposition>() val toLateApply = mutableSetOf<ControlledComposition>() val toComplete = mutableSetOf<ControlledComposition>()
funclearRecompositionState() { ... }
funfillToInsert() { ... }
while (shouldKeepRecomposing) { awaitWorkAvailable()
// Don't await a new frame if we don't have frame-scoped work if ( synchronized(stateLock) { if (!hasFrameWorkLocked) { recordComposerModificationsLocked() !hasFrameWorkLocked } elsefalse } ) continue
// 与下一帧对齐任务以合并更改。 parentFrameClock.withFrameNanos { frameTime -> // Dispatch MonotonicFrameClock frames first; this may produce new // composer invalidations that we must handle during the same frame. if (broadcastFrameClock.hasAwaiters) { trace("Recomposer:animation") { // Propagate the frame time to anyone who is awaiting from the // recomposer clock. broadcastFrameClock.sendFrame(frameTime)
// Ensure any global changes are observed Snapshot.sendApplyNotifications() } }
trace("Recomposer:recompose") { // Drain any composer invalidations from snapshot changes and record // composers to work on synchronized(stateLock) { recordComposerModificationsLocked()
compositionInvalidations.fastForEach { toRecompose += it } compositionInvalidations.clear() }
// Perform recomposition for any invalidated composers val modifiedValues = IdentityArraySet<Any>() val alreadyComposed = IdentityArraySet<ControlledComposition>() while (toRecompose.isNotEmpty() || toInsert.isNotEmpty()) { ... toRecompose.fastForEach { composition -> alreadyComposed.add(composition) performRecompose(composition, modifiedValues)?.let { toApply += it } } ...
// Find any trailing recompositions that need to be composed because // of a value change by a composition. This can happen, for example, if // a CompositionLocal changes in a parent and was read in a child // composition that was otherwise valid. if (modifiedValues.isNotEmpty()) { synchronized(stateLock) { knownCompositions.fastForEach { value -> if ( value !in alreadyComposed && value.observesAnyOf(modifiedValues) ) { toRecompose += value } } } }
if (toRecompose.isEmpty()) { ... fillToInsert() while (toInsert.isNotEmpty()) { toLateApply += performInsertValues(toInsert, modifiedValues) fillToInsert() } ... } }
@ComposeCompilerApi inlinefun<T> Composer.cache(invalid: Boolean, block: @DisallowComposableCalls () -> T): T { @Suppress("UNCHECKED_CAST") return rememberedValue().let { if (invalid || it === Composer.Empty) { val value = block() updateRememberedValue(value) value } else it } as T }
internalclassComposerImpl(...): Composer { ...
@PublishedApi @OptIn(InternalComposeApi::class) internalfunupdateValue(value: Any?) { if (inserting) { writer.update(value) if (value is RememberObserver) { record { _, _, rememberManager -> rememberManager.remembering(value) } abandonSet.add(value) } } else { val groupSlotIndex = reader.groupSlotIndex - 1 if (value is RememberObserver) { abandonSet.add(value) } recordSlotTableOperation(forParent = true) { _, slots, rememberManager -> if (value is RememberObserver) { rememberManager.remembering(value) } when (val previous = slots.set(groupSlotIndex, value)) { is RememberObserver -> rememberManager.forgetting(previous) is RecomposeScopeImpl -> { val composition = previous.composition if (composition != null) { previous.release() composition.pendingInvalidScopes = true } } } } } }
privatefuncurrentCompositionLocalScope(group: Int? = null): CompositionLocalMap { if (group == null) providerCache?.let { return it } if (inserting && writerHasAProvider) { var current = writer.parent while (current > 0) { if (writer.groupKey(current) == compositionLocalMapKey && writer.groupObjectKey(current) == compositionLocalMap ) { @Suppress("UNCHECKED_CAST") val providers = writer.groupAux(current) as CompositionLocalMap providerCache = providers return providers } current = writer.parent(current) } } ... providerCache = parentProvider return parentProvider }
@InternalComposeApi overridefunstartProviders(values: Array<outProvidedValue<*>>) { val parentScope = currentCompositionLocalScope() startGroup(providerKey, provider) // The group is needed here because compositionLocalMapOf() might change the number or // kind of slots consumed depending on the content of values to remember, for example, the // value holders used last time. startGroup(providerValuesKey, providerValues) val currentProviders = invokeComposableForResult(this) { compositionLocalMapOf(values, parentScope) } endGroup() val providers: CompositionLocalMap val invalid: Boolean if (inserting) { providers = updateProviderMapGroup(parentScope, currentProviders) invalid = false writerHasAProvider = true } else { @Suppress("UNCHECKED_CAST") val oldScope = reader.groupGet(0) as CompositionLocalMap
@Suppress("UNCHECKED_CAST") val oldValues = reader.groupGet(1) as CompositionLocalMap
// skipping is true iff parentScope has not changed. if (!skipping || oldValues != currentProviders) { providers = updateProviderMapGroup(parentScope, currentProviders)
/** * Return the value provided by the nearest [CompositionLocalProvider] component that invokes, directly or * indirectly, the composable function that uses this property. * * @sample androidx.compose.runtime.samples.consumeCompositionLocal */ @OptIn(InternalComposeApi::class) inlineval current: T @ReadOnlyComposable @Composable get() = currentComposer.consume(this) }
val holderToLayoutNode = hashMapOf<AndroidViewHolder, LayoutNode>() val layoutNodeToHolder = hashMapOf<LayoutNode, AndroidViewHolder>()
overridefunonMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { // Layout will be handled by component nodes. However, we act like proper measurement // here in case ViewRootImpl did forceLayout(). require(MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) require(MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) setMeasuredDimension( MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec) ) // Remeasure children, such that, if ViewRootImpl did forceLayout(), the holders // will be set PFLAG_LAYOUT_REQUIRED and they will be relaid out during the next layout. // This will ensure that the need relayout flags will be cleared correctly. holderToLayoutNode.keys.forEach { it.remeasure() } }
overridefunonLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { // Layout was already handled by component nodes, but replace here because // the View system has forced relayout on children. This method will only be called // when forceLayout is called on the Views hierarchy. holderToLayoutNode.keys.forEach { it.layout(it.left, it.top, it.right, it.bottom) } }
overridefunrequestLayout() { // Hack to cleanup the dirty layout flag on ourselves, such that this method continues // to be called for further children requestLayout(). cleanupLayoutState(this) // requestLayout() was called by a child, so we have to request remeasurement for // their corresponding layout node. for (i in0 until childCount) { val child = getChildAt(i) val node = holderToLayoutNode[child] if (child.isLayoutRequested && node != null) { node.requestRemeasure() } } }