19.2 Compose Recomposer启动流程分析

Posted datian1234

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了19.2 Compose Recomposer启动流程分析相关的知识,希望对你有一定的参考价值。

上一章初始组合流程开始的第一步就是创建 Recomposer 。

接着 Recomposer 又作为构造参数创建了 CompositionImpl 、 ComposerImpl ,又在 androidComposeView#onAttachedToWindow() 触发 onViewTreeOwnersAvailable 回后,调用 composeInitial() 开启初始组合。

Recomposer 重要程度不言而喻,本章我们从 Recomposer 启动流程来了解 Recomposer。

Recomposer 启动流程

class Recomposer(
    effectCoroutineContext: CoroutineContext
) : CompositionContext()

官方的说法是 Recomposer 继承自 CompositionContext, 是一个调度器,用于执行重组来更新一个或多个 Composition 的可组合项中的变化。

归结起来 Recomposer 有两个主要的作用:

  1. 为 Compose 运行提供 CoroutineContext
  2. 启动初始组合和重组

View.createLifecycleAwareWindowRecompose

启动流程要从 Recomposer 创建开始分析,View.createLifecycleAwareWindowRecomposer 先创建 Recompose 对象,在将其与 Lifecycle 关联 。

我们将这个方法拆开,先看上半部分 Recomposer 的创建。

fun View.createLifecycleAwareWindowRecomposer(
    coroutineContext: CoroutineContext = EmptyCoroutineContext,
    lifecycle: Lifecycle? = null
): Recomposer 
    // 1 AndroidUiDispatcher.Main
    val baseContext = if (coroutineContext[ContinuationInterceptor] == null ||
        coroutineContext[MonotonicFrameClock] == null
    ) 
        AndroidUiDispatcher.CurrentThread + coroutineContext
     else coroutineContext
    // 2 包装 AndroidUiDispatcher.Main.frameClock
    val pausableClock = baseContext[MonotonicFrameClock]?.let 
        PausableMonotonicFrameClock(it).apply  pause() 
    
    var systemDurationScaleSettingConsumer: MotionDurationScaleImpl? = null
    // 3 MotionDurationScaleImpl, scaleFactor = 1
    val motionDurationScale = baseContext[MotionDurationScale] ?: MotionDurationScaleImpl().also 
        systemDurationScaleSettingConsumer = it
    
	//将 1 2 3 创建的 CoroutineContext 组合在一起
    val contextWithClockAndMotionScale =
        baseContext + (pausableClock ?: EmptyCoroutineContext) + motionDurationScale
    //创建 recomposer 对象                   
    val recomposer = Recomposer(contextWithClockAndMotionScale)
  	//创建 CoroutineScope 
    val runRecomposeScope = CoroutineScope(contextWithClockAndMotionScale)
    // 省略监听代码 
    return recomposer

从源码可以看出 effectCoroutineContext 是 1、2、3 处创建的三个 CoroutineContext 的组合。

AndroidUiDispatcher.Main 本身就是一个 CombinedContext ,包含了主线程的协程调度器 AndroidUiDispatcher 和基于 choreographer 的 AndroidUiFrameClock。

class AndroidUiDispatcher private constructor(
    val choreographer: Choreographer,
    private val handler: android.os.Handler
) : CoroutineDispatcher() 

	val frameClock: MonotonicFrameClock = AndroidUiFrameClock(choreographer)

	companion object 
        val Main: CoroutineContext by lazy 
            val dispatcher = AndroidUiDispatcher(
                if (isMainThread()) Choreographer.getInstance()
                else runBlocking(Dispatchers.Main)  Choreographer.getInstance() ,
                HandlerCompat.createAsync(Looper.getMainLooper())
            )
            dispatcher + dispatcher.frameClock
        
    
  

pausableClock 是使用 Latch 对 dispatcher.frameClock 进行封装,通过 latch.closeLatch() / latch.openLatch() 来控制 frameClock.withFrameNanos(onFrame) ,最后组合时会替换掉相同 key 的 dispatcher.frameClock。

motionDurationScale 提供时长放大的系数,默认是 1 。拿动画来举例,如果动画时长是 100 ms ,此时 motionDurationScale 的 scaleFactor 是 10 的话, 动画的真正耗时就会变成 1000 ms 。

使用这个组合的 CoroutineContext 创建了 Recomposer 对象和一个 CoroutineScope 之后就是方法的后半部分,添加了两个监听。

有一点需要注意:这个 CoroutineContext 中目前为止是没有 Job 的

    addOnAttachStateChangeListener(
        object : View.OnAttachStateChangeListener 
            override fun onViewAttachedToWindow(v: View) 
            override fun onViewDetachedFromWindow(v: View) 
                removeOnAttachStateChangeListener(this)
                recomposer.cancel()
            
        
    )
    viewTreeLifecycle.addObserver(
        object : LifecycleEventObserver 
            override fun onStateChanged(
                lifecycleOwner: LifecycleOwner,
                event: Lifecycle.Event
            ) 
                val self = this
                when (event) 
                    Lifecycle.Event.ON_CREATE -> 
                        runRecomposeScope.launch(start = CoroutineStart.UNDISPATCHED) 
                            var durationScaleJob: Job? = null
                            try 
                                durationScaleJob = systemDurationScaleSettingConsumer?.let 
                                    val durationScaleStateFlow = getAnimationScaleFlowFor(
                                        context.applicationContext
                                    )
                                    it.scaleFactor = durationScaleStateFlow.value
                                    launch 
                                        durationScaleStateFlow.collect  scaleFactor ->
                                            it.scaleFactor = scaleFactor
                                        
                                    
                                
                                recomposer.runRecomposeAndApplyChanges()
                             finally 
                                durationScaleJob?.cancel()
                                lifecycleOwner.lifecycle.removeObserver(self)
                            
                        
                    
                    Lifecycle.Event.ON_START -> pausableClock?.resume()
                    Lifecycle.Event.ON_STOP -> pausableClock?.pause()
                    Lifecycle.Event.ON_DESTROY -> 
                        recomposer.cancel()
                    
                
            
        
    )

ON_START,ON_STOP 中使用 pausableClock 恢复和挂起 frameClock.withFrameNanos(onFrame)

ON_CREATE 时在 runRecomposeScope 中开启协程调用 recomposer.runRecomposeAndApplyChanges()。

runRecomposeScope.launch
  	//监听 scaleFactor 变化及时赋值给 CoroutineContext 中的 motionDurationScale  
	durationScaleJob = systemDurationScaleSettingConsumer?.let 
		val durationScaleStateFlow = getAnimationScaleFlowFor(
			context.applicationContext
			)
		it.scaleFactor = durationScaleStateFlow.value
		launch 
			durationScaleStateFlow.collect  scaleFactor ->
				it.scaleFactor = scaleFactor
			
		
	
	recomposer.runRecomposeAndApplyChanges()

recomposer.runRecomposeAndApplyChanges

方法的作用是在协程中开启一个和 Recomposer 生命周期相同的循环,循环体中先判断当前是否有要处理的工作,如果没有就挂起协程,如果有就在 frameClock.withFrameNanos(onFrame) 中开启重组逻辑。

方法是在 ON_CREATE 时启动的,在生命周期中这个方法只会调用一次。

suspend fun runRecomposeAndApplyChanges() = recompositionRunner  block 

private suspend fun recompositionRunner(block: suspend CoroutineScope.(parentFrameClock: MonotonicFrameClock) -> Unit

runRecomposeAndApplyChanges 具体实现是调用 recompositionRunner 并传递了 block ,recompositionRunner 作用是设置重组运行的环境,具体业务逻辑在 block 中实现。

    @OptIn(ExperimentalComposeApi::class)
    private suspend fun recompositionRunner(
        block: suspend CoroutineScope.(parentFrameClock: MonotonicFrameClock) -> Unit
    ) 
      	//pausableClock 
        val parentFrameClock = coroutineContext.monotonicFrameClock
        withContext(broadcastFrameClock) 
			//runnerJob
            val callingJob = coroutineContext.job
            registerRunnerJob(callingJob)

			//为快照添加监听
            val unregisterApplyObserver = Snapshot.registerApplyObserver  changed, _ ->	
              	//如果快照改变后 Recompose 是 PendingWork 状态
              	//恢复 workContinuation 协程
                synchronized(stateLock) 
                    if (_state.value >= State.Idle) 
                       snapshotInvalidations += changed
                        deriveStateLocked()
                     else null
                ?.resume(Unit)
            

            addRunning(recomposerInfo)

            try 
				//初始化时将所有 Composition 设置成 invalidate
                synchronized(stateLock) 
                    knownCompositions.fastForEach  it.invalidateAll() 
                
				//以 pausableClock 为参数调用 block
                coroutineScope 
                    block(parentFrameClock)
                
             finally 
                unregisterApplyObserver.dispose()
                synchronized(stateLock) 
                    if (runnerJob === callingJob) 
                        runnerJob = null
                    
                    deriveStateLocked()
                
                removeRunning(recomposerInfo)
            
        
    

方法中修改了当前 CoroutineContext[MonotonicFrameClock] 由 pausableClock 替换成 broadcastFrameClock , 原来的pausableClock 以参数的形式传递给了 block 。为快照添加全局监听,改变时调用 deriveStateLocked() 。

接着看 block 中的实现,删除了部分代码

    suspend fun runRecomposeAndApplyChanges() = recompositionRunner  parentFrameClock ->

		//省略 
        while (shouldKeepRecomposing) 
          	//判断是否需要挂起
            awaitWorkAvailable(
            if (
                synchronized(stateLock) 
                    if (!hasFrameWorkLocked) 
                        recordComposerModificationsLocked()
                        !hasFrameWorkLocked
                     else false
                
            ) continue
            parentFrameClock.withFrameNanos  frameTime ->
				//确保 broadcastFrameClock awaiter 中会发生的改变
              	//在同一帧处理
                if (broadcastFrameClock.hasAwaiters) 
                    trace("Recomposer:animation") 
                        broadcastFrameClock.sendFrame(frameTime)
                        Snapshot.sendApplyNotifications()
                    
                

                trace("Recomposer:recompose") 
                  	//省略 

                  	//启动重组
                    while (toRecompose.isNotEmpty() || toInsert.isNotEmpty()) 
                        try 
                            toRecompose.fastForEach  composition ->
                                alreadyComposed.add(composition)
                                performRecompose(composition, modifiedValues)?.let 
                                    toApply += it
                                

                         catch (e: Exception) 
                            processCompositionError(e, recoverable = true)
                            clearRecompositionState()
                            return@withFrameNanos
                         finally 
                            toRecompose.clear()
                        
                      	//省略 

                    
					//省略 
                    synchronized(stateLock) 
                        deriveStateLocked()
                    
                
            

            discardUnusedValues()
        
    

通过循环条件 shouldKeepRecomposing 可以看出 while 循环会一直运行直到 Recomposer 关闭。

    private val shouldKeepRecomposing: Boolean
        get() = synchronized(stateLock)  !isClosed  ||
            effectJob.children.any  it.isActive  

    fun close() 
        if (effectJob.complete()) 
            synchronized(stateLock) 
                isClosed = tru
            
        
    

方法体中先判断是否需要挂起,如果不需要最后会调用 performRecompose() 来执行重组,这个循环会在 Recomposer 声明周期中一直运行。

Recomposer 启动流程有两个重要的地方

  • while 循环的挂起与恢复
  • CoroutineContext 的变化

挂起与恢复

循环的第一步是调用 awaitWorkAvailable() 方法,检查当前是否有需要执行的工作,如果没有就挂起并将其赋值给 workContinuation 属性。

    private val hasSchedulingWork: Boolean
        get() = synchronized(stateLock) 
            snapshotInvalidations.isNotEmpty() ||
                compositionInvalidations.isNotEmpty() ||
                broadcastFrameClock.hasAwaiters
        

    private suspend fun awaitWorkAvailable() 
        if (!hasSchedulingWork) 
            suspendCancellableCoroutine<Unit>  co -
                synchronized(stateLock) 
                    if (hasSchedulingWork) 
                        co.resume(Unit
                     else 
                        workContinuation = co
                    
                
            
        

恢复操作与 deriveStateLocked() 有关

    private fun deriveStateLocked(): CancellableContinuation<Unit>? 
        if (_state.value <= State.ShuttingDown) 
            //..
        

        val newState = when 
            errorState != null -> 
                State.Inactive
           
            runnerJob == null -> 
            //..
            else -> State.Idle
        

        _state.value = newState
        return if (newState == State.PendingWork) 
            workContinuation.also 
                workContinuation = null
            
         else null
    

当新状态是 State.PendingWork 时 deriveStateLocked() 会返回挂起的协程 workContinuation 。

在 recompositionRunner() 注册快照全局监听的回调方法中就是具体恢复协程的实现,类似的调用 Recomposer 中有多处。

                synchronized(stateLock) 
                    if (_state.value >= State.Idle) 
                        snapshotInvalidations += change
                        deriveStateLocked()
                     else nul
                ?.resume(Unit)

Recomposer 中的 CoroutineContext

Recomposer 中的 CoroutineContext 有两处 CoroutineContext

  1. 启动流程中 while 循环所在协程的 CoroutineContext
  2. Recomposer 对外(系统其他组件不是开发者)提供的 effectCoroutineContext

它们最先都来自 View.createLifecycleAwareWindowRecomposer ,作为参数创建了 Recomposer 和 runRecomposeScope。

其中没有 Job

此时启动流程还没有开始,所以 runRecomposeScope 中 CoroutineContext 并没有变化。 Recomposer 已经创建好了,先来看 Recomposer.effectCoroutineContext

Recomposer.effectCoroutineContext

// 等号右边的 effectCoroutineContext 是构造参数
// broadcastFrameClock 的 key 是 MonotonicFrameClock
internal override val effectCoroutineContext: CoroutineContext 
    effectCoroutineContext + broadcastFrameClock + effectJob

effectCoroutineContext 属性随着 Recomposer 对象一起创建,看源码可知 effectCoroutineContext 将传入参数中的 pausableClock 替换成了 broadcastFrameClock ,并添加了 effectJob。

effectJob

用于控制所有 Effects Api 或者在 @Composable 函数中启动协程的父Job

fun LaunchedEffect(
    key1: Any?,
    block: suspend CoroutineScope.() -> Unit
) 
    val applyContext = currentComposer.applyCoroutineContext
    remember(key1)  LaunchedEffectImpl(applyContext, block) 


internal class LaunchedEffectImpl(
    parentCoroutineContext: CoroutineContext,
    private val task: suspend CoroutineScope.() -> Unit
) : RememberObserver 
    private val scope = CoroutineScope(parentCoroutineContext)
    private var job: Job? = null

    override fun onRemembered() 
        job?.cancel("Old job was still running!")
        job = scope.launch(block = task)
    

    override fun onForgotten() 
        job?.cancel()
        job = null
    

    override fun onAbandoned() 
        job?.cancel()
        job = null
    

//Context 中的 Job 作为父Job 创建 CoroutineScope 中的 Job
public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
    ContextScope(if (context[Job] != null) context else context + Job())
@Composable
inline fun rememberCoroutineScope(
    crossinline getContext: @DisallowComposableCalls () -> CoroutineContext =
         EmptyCoroutineContext 
): CoroutineScope 
    val composer = currentComposer
    val wrapper = remember 
        CompositionScopedCoroutineScopeCanceller(
            createCompositionCoroutineScope(getContext(), composer)
        )
    
    return wrapper.coroutineScope


@PublishedApi
@OptIn(InternalComposeApi::class)
internal fun createCompositionCoroutineScope(
    coroutineContext: CoroutineContext,
    composer: Composer
) = if (coroutineContext[Job] != null) 
    CoroutineScope(
        Job().apply 
            completeExceptionally(
                IllegalArgumentException(
                    "CoroutineContext supplied to " +
                        "rememberCoroutineScope may not include a parent job"
                )
            )
        
    )
 else 
    val applyContext = composer.applyCoroutineContext
  	//Context 中的 Job 作为父Job 创建 CoroutineScope 中的 Job
    CoroutineScope(applyContext + Job(applyContext[Job]) + coroutineContext)

//上章分析时我们知道 parentContext 就是 Recomposer 对象,    
override val applyCoroutineContext: CoroutineContext
        @TestOnly get() = parentContext.effectCoroutineContext

所以 rememberCoroutineScope() 和 Effects Api 创建的协程都是 Recomposer.effectCoroutineContext 中 effectJob 的子 Job。

启动 Recomposer 协程中的 CoroutineContext

View.createLifecycleAwareWindowRecomposer 后半段 Lifecycle 监听的 ON_CREATE 中 runRecomposeScope.launch 会默认创建一个 StandaloneCoroutine 类型的 Job,这个 Job 会作为 Recomposer 执行流程中所有协程的父 Job ,控制 Recomposer 运行协程。随后执行 recompositionRunner()

    @OptIn(ExperimentalComposeApi::class)
    private suspend fun recompositionRunner(
        block: suspend CoroutineScope.(parentFrameClock: MonotonicFrameClock) -> Unit
    ) 
      	//pausableClock 
        val parentFrameClock = coroutineContext.monotonicFrameClock
        withContext(broadcastFrameClock) 
			//runnerJob
            val callingJob = coroutineContext.job
            registerRunnerJob(callingJob)
            try 
                coroutineScope 
                    block(parentFrameClock)
                
             finally 
        
    

先使用 withContext() 方法 将pausableClock 替换成了 broadcastFrameClock,跟 Recomposer.effectCoroutineContext 中的是一个对象。接着调用 registerRunnerJob() 方法将 runRecomposeScope.launch 中创建的 Job 赋值给 Recompose.runnerJob。

至此 Compose 运行环境中的两个父 Job 都出现了: Recompose.runnerJob、effectJob。

broadcastFrameClock

Recomposer 两个 CoroutineContext 中的 MonotonicFrameClock 都是它 。

Recomposer.effectCoroutineContext 是在赋值时替换后直接对外提供的, runRecomposeScope.coroutineContext 却不是简单的替换,先了解 BroadcastFrameClock 作用再来详细分析。

class BroadcastFrameClock(
    private val onNewAwaiters: (() -> Unit)? = null
) : MonotonicFrameClock 

    private class FrameAwaiter<R>(val onFrame: (Long) -> R, val continuation: Continuation<R>) 
        fun resume(timeNanos: Long) 
            continuation.resumeWith(runCatching  onFrame(timeNanos) )
        
    

    private val lock = Any()
    private var failureCause: Throwable? = null
    private var awaiters = mutableListOf<FrameAwaiter<*>>()
    private var spareList = mutableListOf<FrameAwaiter<*>>()

    val hasAwaiters: Boolean get() = synchronized(lock)  awaiters.isNotEmpty() 

    fun sendFrame(timeNanos: Long) 
        synchronized(lock) 
            val toResume = awaiters
            awaiters = spareList
            spareList = toResume

            for (i in 0 until toResume.size) 
                toResume[i].resume(timeNanos)
            
            toResume.clear()
        
    

    override suspend fun <R> withFrameNanos(
        onFrame: (Long) -> R
    ): R = suspendCancellableCoroutine  co ->
        lateinit var awaiter: FrameAwaiter<R>
        val hasNewAwaiters = synchronized(lock) 
            val cause = failureCause
            if (cause != null) 
                co.resumeWithException(cause)
                return@suspendCancellableCoroutine
            
            awaiter = FrameAwaiter(onFrame, co)
            val hadAwaiters = awaiters.isNotEmpty()
            awaiters.add(awaiter)
            !hadAwaiters
        

        co.invokeOnCancellation 
            synchronized(lock) 
                awaiters.remove(awaiter)
            
        

        if (hasNewAwaiters && onNewAwaiters != null) 
            try 
                onNewAwaiters.invoke()
             catch (t: Throwable) 
                fail(t)
            
        
    

    private fun fail(cause: Throwable) 
        synchronized(lock) 
            if (failureCause != null) return
            failureCause = cause
            awaiters.fastForEach  awaiter ->
                awaiter.continuation.resumeWithException(cause)
            
            awaiters.clear()
        
    

    fun cancel(
        cancellationException: CancellationException = CancellationException("clock cancelled")
    ) 
        fail(cancellationException)
    

不同之处在于 BroadcastFrameClock.withFrameNanos() 并不会直接运行 onFrame() 回调,而是把 onFrame() 封装成了 FrameAwaiter 保存到 awaiters 中在 sendFrame() 方法中一起执行。

分析 runRecomposeScope.coroutineContext 中的 MonotonicFrameClock

  1. 在 View.createLifecycleAwareWindowRecomposer 中 lifecycle 监听中挂起/恢复协程
Lifecycle.Event.ON_START -> pausableClock?.resume()
Lifecycle.Event.ON_STOP -> pausableClock?.pause()
  1. 在 recompositionRunner() 中替换成与 Recomposer.effectCoroutineContex 中相同的 broadcastFrameClock,将原来的 pausableClock 作为参数传递给 block

3.在 block while 循环中设置 pausableClock.onFrame() 为触发时先调用 broadcastFrameClock.sendFrame() 执行 broadcastFrameClock.awaiters 中的 onFrame()

parentFrameClock.withFrameNanos  frameTime ->

                if (broadcastFrameClock.hasAwaiters) 
                    trace("Recomposer:animation") 
                        broadcastFrameClock.sendFrame(frameTime)

                        Snapshot.sendApplyNotifications()
                    
                

这样 @Composable 函数中所有使用 CoroutineContext[MonotonicFrameClock] 监听的 onFrame() 会添加到 broadcastFrameClock.awaiters 中等待 pausableClock.onFrame() 一起执行,提高工作效率。

此外 pausableClock 会在 ON_START ,ON_STOP 中恢复挂起,这样又起到了统一控制作用。

作者:给大佬们点赞
链接:https://juejin.cn/post/7199908667642347579

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。


相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

全套视频资料:

一、面试合集

二、源码解析合集


三、开源框架合集


欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取↓↓↓

以上是关于19.2 Compose Recomposer启动流程分析的主要内容,如果未能解决你的问题,请参考以下文章

你如何定义十进制19.2

docker compose 服务启动顺序控制

docker-compose启动容器失败了怎么办

docker-compose快速启动nginx

如何在 apex 19.2 中使用数据库中的表

docker-compose 在主机重启后启动容器。哪个?