Compose Recomposer启动流程分析

news2024/12/25 9:24:48

初始组合流程开始的第一步就是创建 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 中恢复挂起,这样又起到了统一控制作用。


Android 知识点归整

Android 性能调优系列https://qr18.cn/FVlo89
Android 车载学习指南https://qr18.cn/F05ZCM
Android Framework核心知识点笔记https://qr18.cn/AQpN4J
Android 音视频学习笔记https://qr18.cn/Ei3VPD
Jetpack全家桶(含Compose)https://qr18.cn/A0gajp
Kotlin 入门到精进https://qr18.cn/CdjtAF
Flutter 基础到进阶实战https://qr18.cn/DIvKma
Android 八大知识体系https://qr18.cn/CyxarU
Android 中高级面试题锦https://qr18.cn/CKV8OZ

后续如有新知识点,将会持续更新,尽请期待……

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/350669.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java连接docker里面的hbase

原代码代码一直卡着报错如下&#xff1a;java.io.UncheckedI0Exception:org.apache.hadoop.hbase.client.RetriesExhaustedException: Cant get the location for replica 0at org.apache.hadoop.hbase.client.ResultScanner$1.hasNext(ResultScanner.java:55)at hbase.SelectS…

基于C++的考试系统

一、需求分析 ​ 设计一个简单的考试系统&#xff0c;可进行C与Java的考试&#xff0c;考生姓名、考生学号、C题目、Java题目、C答案&#xff0c;Java答案分别存入…/txt文件数据/路径下的StudentsName、StudentsNum、QuestionC&#xff0c;QuestionJ、AnswerC、AnswerJtxt文件…

【一般人不会告诉你】比肩chatgtp的5款AI网站

话不多说&#xff0c;直接上连接 1. Dall-E: https://labs.openai.com/ 2. Codeformer: https://shangchenzhou.com/projects/Co... 3. Playground AI: https://playgroundai.com/ 4. Clip Drop: https://clipdrop.co/relight 5. Astria: https://www.strmr.com/examples …

Apache服务关闭攻防场景模拟实验

一、场景概述 攻击机攻击已知的公共Apache web服务器&#xff0c;导致Apache服务关闭&#xff0c;web网站无法访问。攻击者利用安全外壳 (SSH) 暴力攻击访问服务器&#xff0c;并上传后门文件和脚本&#xff0c;每分钟将服务器的用户名称和密码发送给攻击机以保持对服务器的访…

【分享】如何通过集简云将ChatGPT人工智能接入到我们的抖音中?

ChatGPT是一款非常强大的人工智能产品&#xff0c;可以有创造性的回复和创作文字&#xff0c;图片&#xff0c;适用于很多办公场景。这篇文章将介绍如何将ChatGPT接入到我们的抖音中。 在集简云中的ChatGPT应用 目前集简云提供了两个ChatGPT应用: OpenAI(ChatGPT&#xff09;内…

什么是同步整流和异步整流

在设计降压型DCDC电路的时候&#xff0c;经常会听到同步整流&#xff08;synchronous&#xff09;和异步整流&#xff08;asynchronous&#xff09;。那么什么是同步整流&#xff0c;什么是异步整流呢从这两种电路的拓扑来看&#xff0c;异步整流型外围有一个续流二极管&#x…

07 OpenCV各类滤波

1 均值滤波 均值滤波器是一种基本的线性滤波器&#xff0c;常用于图像处理中的平滑操作。在 OpenCV 中&#xff0c;可以使用cv2.blur()函数或cv2.boxFilter()函数进行均值滤波操作。 均值滤波器的原理是将每个像素的灰度值替换为其周围像素灰度值的平均值。其核心思想是去除图…

Android App开发基础

文章目录一 App的开发特点1.1 App的运行环境1.2 App开发语言1.3 java语言开发1.4 Kotlin语言开发1.5 XML1.6 App连接的数据库二 App的工程结构2.1 App工程目录结构2.2 构建工具Grade2.3 编译配置文件build.gradle2.4 运行配置文件AndroidManifest.xml2.4.1 application2.4.2 ac…

科研试剂供应Pyrene-PEG3-Propargyl,芘甲酰胺-三聚乙二醇-丙炔

Pyrene-PEG3-Propargyl&#xff0c;Propargyl-PEG3-Pyrene&#xff0c;芘甲酰胺-三聚乙二醇-丙炔&#xff0c;芘甲酰胺三聚乙二醇丙炔反应特点&#xff1a;Pyrene-PEG3-Propargyl属于炔基点击试剂&#xff0c;点击化学的概念对化学合成领域有很大的贡献&#xff0c;在药 物开发…

nvdiffrast

nvidiffrast主页https://arxiv.org/pdf/2011.03277.pdfhttps://github.com/NVlabs/nvdiffrast CSC2547 Differentiable Rendering A Survey https://www.youtube.com/watch?v7LU0KcnSTc4 DR https://blog.qarnot.com/an-overview-of-differentiable-rendering/ 知乎 https:…

找不到工作的测试员一大把,大厂却招不到优秀软件测试员?高薪难寻测试工程师。

测试工程师招了快一个月了&#xff0c;实在招不到合适的&#xff0c;已经在被解雇的边缘了。。。” 初级测试工程师非常多&#xff0c;但真正掌握测试思维、能力强的优秀测试太少了&#xff01; 据我所知&#xff0c; 当下的测试人员不少状态都是这样的&#xff1a; 在工作中…

设计模式C++实现18:迭代器模式(Iterator)

意图&#xff1a;提供一种方法顺序访问一个聚合对象中的各个元素&#xff0c;而又不需要暴露该对象的内部表示。 上下文&#xff1a;集合对象内部结构常常变化各异。对于这些集合对象&#xff0c;能否在不暴露其内部结构的同时&#xff0c;让外部Client透明地访问其中包含的元素…

动态规划(楼梯和股票问题)

❤ 作者主页&#xff1a;李奕赫揍小邰的博客 ❀ 个人介绍&#xff1a;大家好&#xff0c;我是李奕赫&#xff01;(&#xffe3;▽&#xffe3;)~* &#x1f34a; 记得点赞、收藏、评论⭐️⭐️⭐️ &#x1f4e3; 认真学习!!!&#x1f389;&#x1f389; 文章目录动态规划&…

抓包工具Charles(一)-下载安装与设置

无论是在测试、开发工作中&#xff0c;抓包都是很重要、很常用的技能。Charles作为一款抓包工具&#xff0c;能够满足大部分的工作需求。 文章目录一、下载地址二、安装三、安装根证书&#xff08;电脑&#xff09;四、设置五、抓包附录&#xff1a;[零基础入门接口功能测试教程…

<JVM上篇:内存与垃圾回收篇>10 - StringTable

笔记来源&#xff1a;尚硅谷 JVM 全套教程&#xff0c;百万播放&#xff0c;全网巅峰&#xff08;宋红康详解 java 虚拟机&#xff09; 文章目录10.1. String 的基本特性10.1.1. String 在 jdk9 中存储结构变更MotivationDescription10.1.2. String 的基本特性10.2. String 的内…

ARMv8 同步和信号量(Synchronization and semaphores)简介

目录 1&#xff0c;Load-Exclusive/Store-Exclusive 概述 2&#xff0c;独占式访问指令与非共享内存( Exclusive access instructions and Non-shareable memory locations) 3&#xff0c;独占式访问指令与共享内存( Exclusive access instructions and shareable memory lo…

文华财经期货波段趋势K线结构主图阶梯公式源码,极品多空红绿轨道指标公式

期货指标公式是通过数学逻辑角度计算而来&#xff0c;仅是期货分析环节中的一个辅助工具。期货市场具有不确定性和不可预测性的&#xff0c;请正常对待和使用指标公式! 第一步&#xff1a;判大势 所谓判大势&#xff0c;就是判断当前行情趋势&#xff0c;是入场前的必备步骤。…

中汽协确认参加2023EVH新能源动力总成年会

演讲主题&#xff1a;中国汽车工业经济运行情况 演讲大纲&#xff1a; 对中国汽车工业近期发展态势进行分析2023年汽车行业预测 陈士华 现任中国汽车工业协会副秘书长&#xff0c;兼任中国汽车工业经济技术信息研究所有限公司总经理 1999年3月至今&#xff0c;就职于中国汽…

接口自动化测试框架(Java 实现)

需求分析 需求点需求分析通过 yaml 配置接口操作和用例后续新增接口和测试用例只需要编写 yaml 文件即可实现。可以支持接口间的参数传递具有参数依赖的接口可以进行变量的抽取和参数赋值。支持全局、用例维度的变量存储比如时间截命名法中要用到的时间截后缀。支持用例软断言…

Go语言入门【10】Map

Map map是一种键值对形式的数据结构&#xff0c;一个键对应一个值&#xff0c;可以通过键快速检索出其对应的value值&#xff0c;在map中key的值是唯一的&#xff0c;value的值不唯一&#xff0c;并且map中保存的数据是无序的。 Map声明 声明Map可以使用map关键字进行声明&a…