深度解析 Compose 的 Modifier 原理 -- DrawModifier

其实原理性分析的文章,真的很难讲的通俗易懂,讲的简单了就没必要写了,讲的繁琐难懂往往大家也不乐意看,所以只能尽量想办法,找个好的角度(比如从 Demo 代码示例出发)慢慢带着大家去钻源码,如果确实能帮助到大家完全理解了文章所讲述到的源码理论,那就值了。

在正式开始分析 DrawModifier 之前,建议你先看看 【LayoutModifier 和 Modifier.layout 用法及原理】这篇文章,毕竟它是作为 Modifier 原理解析的第一篇文章,对你了解整个 Modifier 架构还是很有帮助的,或者说它是最基础的一篇文章,如果不熟悉,后面的系列 Modifier 你可能会看的比较费劲… …


Modifier 系列文章

  📑 《 深入解析 Compose 的 Modifier 原理 - - Modifier、CombinedModifier 》

  📑 《 深度解析 Compose 的 Modifier 原理 - - Modifier.composed()、ComposedModifier 》

  📑 《 深入解析 Compose 的 Modifier 原理 - - LayoutModifier 和 Modifier.layout 》


一、话题引入


老样子,我们从 Demo 开始,首先我们看两个简单的场景:

1.1 问题场景 1

一个绿色背景的 Text():

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeBlogTheme {
                Box(Modifier.background(Color.Green)) {
                    Text("Hi Compose")
                }
            }
        }
    }
}

效果如下:

在这里插入图片描述


如果我改动一下代码:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            ComposeBlogTheme {
                Box(Modifier.background(Color.Green).size(120.dp)) {
                    Text("Hi Compose")
                }
            }
        }
    }
}

只是给 Box 加了一个尺寸:

在这里插入图片描述

嗯,效果不错,那如果我再改一次呢?

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            ComposeBlogTheme {
                Box(Modifier.background(Color.Green).size(120.dp).background(Color.Red)) {
                    Text("Hi Compose")
                }
            }
        }
    }
}

又加了一个 background,看下效果:

在这里插入图片描述

思考一个问题:为什么此时的 Box 是红色,而不是绿色? 如果你看了 LayoutModifier 的原理解析,这边就会产生一个困惑:Modifier 不是从右边开始遍历,以此往左,最终应该是绿色背景,而不应该是红色背景啊?

所以,问题出在哪?我们不妨先看下 Modifier.background() 源码:

fun Modifier.background(
    color: Color,
    shape: Shape = RectangleShape
) = this.then(
    Background(
        ... ...
    )
)

它又调用了 Background

private class Background constructor(
    ... ...
) : DrawModifier, InspectorValueInfo(inspectorInfo) {

这里可以发现 Background 又是 DrawModifier 的实现类,所以我们可以给出一个猜测:难道是因为 DrawModifier 影响了方块的 Backgroud?

==> 答案是肯定的!

1.2 问题场景 2

在之前我们分析 LayoutModifier 文章中提到过:如果我们需要自己创建一个 LayoutModifier,可以通过 Modifier.layout(),同样的如果我们要创建一个 DrawModifier,可以通过 Modifier.drawWithContent(),比如:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            ComposeBlogTheme {
                Box(Modifier.background(Color.Green).size(120.dp).drawWithContent {  }) {
                    Text("Hi Compose")
                }
            }
        }
    }
}

看下效果:

在这里插入图片描述

???不对啊,Text() 哪去了?我现在再来修改下代码:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            ComposeBlogTheme {
                Box(Modifier.background(Color.Green).size(120.dp).drawWithContent { drawContent() }) {
                    Text("Hi Compose")
                }
            }
        }
    }
}

仅仅是在 drawWithContent 里面加了一个 drawContent(),再看下效果:

在这里插入图片描述

???Text() 又出来了,为什么?为什么要加 drawContent(), drawContent() 又是什么?难道它负责绘制 Text()?

==> 答案是肯定的!

所以接下来,我们就带着这两个问题正式进入这篇文章的主题!


二、DrawModifier


就像我们前面在分析 LayoutModifier 的时候说过,所有组件最终都会被转换为一个 LayoutNode,这个LayoutNode 包含了所有的测量数据,那同样它也会包含你对组件设定的 Modifier,所以最终经过一些列转换,也会传到 LayoutNode 里面,那 LayoutNode 里面必然会存在一个 modifer 属性来处理你所设定的所有 Modifier,我们之前看过 LayoutModifer 的处理逻辑了,那么如果是一个 DrawModifier 呢?

2.1 LayoutNode.modifier

正如前面所说,LayoutNode 中会对 Modifier 做处理:

override var modifier: Modifier = Modifier
    set(value) {
        ... ...
        
        val outerWrapper = modifier.foldOut(innerLayoutNodeWrapper) { mod, toWrap ->
            if (mod is RemeasurementModifier) {
                mod.onRemeasurementAvailable(this)
            }

            // 处理 DrawModifier 的逻辑
            toWrap.entities.addBeforeLayoutModifier(toWrap, mod)

            if (mod is OnGloballyPositionedModifier) {
                getOrCreateOnPositionedCallbacks() += toWrap to mod
            }

            // 处理 LayoutModifier 的逻辑
            val wrapper = if (mod is LayoutModifier) {
                // Re-use the layoutNodeWrapper if possible.
                (reuseLayoutNodeWrapper(toWrap, mod)
                    ?: ModifiedLayoutNode(toWrap, mod)).apply { onInitialize() }
            } else {
                toWrap
            }
            wrapper.entities.addAfterLayoutModifier(wrapper, mod)
            wrapper
        }
        ... ...
    }

处理 DrawModifier 的核心代码就在:

toWrap.entities.addBeforeLayoutModifier(toWrap, mod)

现在就存在两个问题:

  1. topWrap 是什么?
// 比如,这是我们最开始时候例子里面的 Modifier
Modifier.background(Color.Green).size(120.dp)

当 foldOut 开始遍历的时候:

val outerWrapper = modifier.foldOut(innerLayoutNodeWrapper) { mod, toWrap ->

最初的 toWrap 是:innerLayoutNodeWrapper,也就是 InnerPlaceable。

internal val innerLayoutNodeWrapper: LayoutNodeWrapper = InnerPlaceable(this)

遵循从右往左遍历的规则,首先是处理 .size(120).dp,它是 LayoutModifier,所以会被 ModifiedLayoutNode 包起来:

ModifiedLayoutNode[
	LayoutModifier         // size(120.dp)
	+
	innerLayoutNodeWrapper // Box()
]

处理完后开始处理 background(Color.Green)

val outerWrapper = modifier.foldOut(innerLayoutNodeWrapper) { mod, toWrap ->
	... ...
	toWrap.entities.addBeforeLayoutModifier(toWrap, mod)
	...
}

这个时候的 toWrap 就是嵌套之后的 ModifiedLayoutNode,也就是一个 LayoutNodeWrapper对象,mod 就是 DrawModifier。

  1. entities 是什么?
/**
 * All [LayoutNodeEntity] elements that are associated with this [LayoutNodeWrapper].
 */
val entities = EntityList()

看它的注释:与此 [LayoutNodeWrapper] 关联的所有 [LayoutNodeEntity] 元素。

LayoutNodeEntity 又是什么?

/**
 * Base class for entities in [LayoutNodeWrapper]. a [LayoutNodeEntity] is
 * a node in a linked list referenced from [EntityList].
 */
internal open class LayoutNodeEntity<T : LayoutNodeEntity<T, M>, M : Modifier>(
    val layoutNodeWrapper: LayoutNodeWrapper,
    val modifier: M
)

我们也来解读一下注释,也就是两层意思:

1. [LayoutNodeWrapper] 中 entites 的基类。 
2. [LayoutNodeEntity] 是从 [EntityList] 引用的链表中的节点。

通过这个注释我们就可以得到两个核心信息:

1. [LayoutNodeWrapper] 中 entites 的基类。 
    ==> 基类?那就是说可能有很多这个基类的子类?

2. [LayoutNodeEntity] 是从 [EntityList] 引用的链表中的节点。
    ==> 会有一个包含链表的 EntityList?这个基类的子类都是链表的节点?

2.2 EntityList.addBeforeLayoutModifier()

带着这两个猜测,我们现在来重点看下 addBeforeLayoutModifier() 方法:

internal value class EntityList(
    val entities: Array<LayoutNodeEntity<*, *>?> = arrayOfNulls(TypeCount)
) {
    fun addBeforeLayoutModifier(layoutNodeWrapper: LayoutNodeWrapper, modifier: Modifier) {
        if (modifier is DrawModifier) {
            add(DrawEntity(layoutNodeWrapper, modifier), DrawEntityType.index)
        }
        if (modifier is PointerInputModifier) {
            add(PointerInputEntity(layoutNodeWrapper, modifier), PointerInputEntityType.index)
        }
        if (modifier is SemanticsModifier) {
            add(SemanticsEntity(layoutNodeWrapper, modifier), SemanticsEntityType.index)
        }
        if (modifier is ParentDataModifier) {
            add(SimpleEntity(layoutNodeWrapper, modifier), ParentDataEntityType.index)
        }
    }
    ... ...
}

看到没,在 addBeforeLayoutModifier 里面做了 modifier is DrawModifier 判断。

if (modifier is DrawModifier) {
    add(DrawEntity(layoutNodeWrapper, modifier), DrawEntityType.index)
}

这里我们兵分三路:

  1. DrawEntity 是什么?
internal class DrawEntity(
    layoutNodeWrapper: LayoutNodeWrapper,
    modifier: DrawModifier
) : LayoutNodeEntity<DrawEntity, DrawModifier>(layoutNodeWrapper, modifier), OwnerScope {
	... ...
}

咦?它原来是 LayoutNodeEntity 的子类!是不是解答了我们刚才的第一个猜想?- - 传进来的 DrawModifier,会被封装进一个 DrawEntity 对象里面,大概是这么个意思:

Modifier.background(Color.Green).size(120.dp)

DrawEntity[
	DrawModifier               // background(Color.Green)
	+ 
	ModifiedLayoutNode[
		LayoutModifier         // size(120.dp)
		+
		innerLayoutNodeWrapper // Box()
	]
]
  1. DrawEntityType.index 是啥?
value class EntityType<T : LayoutNodeEntity<T, M>, M : Modifier>(val index: Int)

companion object {
    val DrawEntityType = EntityType<DrawEntity, DrawModifier>(0)
    ... ...
}

原来就是一个取值的工作,返回一个固定值:0,这个值有什么用?我们先往下看。

  1. add 做了什么?

现在我们来看看 add 把 DrawEntity 给加到哪里去了:

private fun <T : LayoutNodeEntity<T, *>> add(entity: T, index: Int) {
    @Suppress("UNCHECKED_CAST")
    val head = entities[index] as T?
    entity.next = head
    entities[index] = entity
}

这是一个典型的数组 + 链表操作。

我们先看下 entities 是个啥?

internal value class EntityList(
    val entities: Array<LayoutNodeEntity<*, *>?> = arrayOfNulls(TypeCount)
) {
    fun addBeforeLayoutModifier(layoutNodeWrapper: LayoutNodeWrapper, modifier: Modifier) {
        if (modifier is DrawModifier) {
            add(DrawEntity(layoutNodeWrapper, modifier), DrawEntityType.index)
        }
        ... ...
    }
    ... ...
}

好熟悉的代码…,原来 entities 是一个数组,而且是一个长度为 7 的数组,并且它的元素都是 LayoutNodeEntity 对象。

private const val TypeCount = 7

还记得我们刚刚说的 DrawEntityType.index 的返回值:0 吗?它被传了进来,意思就是 entities 数组的 0 号为就是专门用来放 DrawModifier的。同时 entities 这个数组里面的每一个元素是一个链表。

比如有 1 个 DrawModifier,大概是这么个样子存放:

[
	LayoutNodeEntity(DrawModifier1),
	null,
	null,
	null,
	null,
	null,
	null,
]

比如有 2 个 DrawModifier,大概是这么个样子存放:

[
	LayoutNodeEntity(DrawModifier2) -> LayoutNodeEntity(DrawModifier1),
	null,
	null,
	null,
	null,
	null,
	null,
]

以此类推,后进来的就插入表头,一个链接一个。


三、 DrawModifier 对绘制的影响


现在我们已经很清楚 LayoutNode 是如何处理 DrawModifier 的了,接下来我们就看看 DrawModifier 是如何对绘制产生精细影响的。

在 LayoutNode 中,绘制是由 draw() 函数处理的。

internal fun draw(canvas: Canvas) = outerLayoutNodeWrapper.draw(canvas)

这里的 outerLayoutNodeWrapper 是什么?

3.1 draw()

fun draw(canvas: Canvas) {
    // layer: 是一个独立绘制的图层,它只是在一块额外的区域绘制而已,大多数时候是没有的
    val layer = layer
    if (layer != null) {
        layer.drawLayer(canvas)
    } else {
        val x = position.x.toFloat()
        val y = position.y.toFloat()
        canvas.translate(x, y)
        drawContainedDrawModifiers(canvas)
        canvas.translate(-x, -y)
    }
}

大多数情况下,我们并不需要考虑 layer 图层,所以这里可以当它为 null 处理。所以我们只需要关心 else 分支内部的逻辑,核心代码就一行:drawContainedDrawModifiers(canvas)

2.6 drawContainedDrawModifiers()

private fun drawContainedDrawModifiers(canvas: Canvas) {
    val head = entities.head(EntityList.DrawEntityType)
    if (head == null) {
        performDraw(canvas)
    } else {
        head.draw(canvas)
    }
}

这段代码的逻辑很清晰,主要就是三步:

  1. 取 DrawEntity 链表表头
val head = entities.head(EntityList.DrawEntityType)
  1. 表头如果为空,也就是没有设置过 DrawModifier
if (head == null) {
    performDraw(canvas)
}

那么就会执行 performDraw(),我们来看看它做了什么:

open fun performDraw(canvas: Canvas) {
    wrapped?.draw(canvas)
}

wrapped 是什么?

internal open val wrapped: LayoutNodeWrapper? get() = null

它就是当前的 LayoutNodeWrapper 对象,而 LayoutNodeWrapper 是一个抽象类。

注意: 我们现在分析的场景是没有设置 DrawModifier,那么实际代码可能是这样:

Box(Modifier.padding(10.dp).size(120.dp))

对应的内部 Modifier 的结构大概是这个样子:

ModifiedLayoutNode[
	LayoutModifier             // padding(10.dp)
	+
	ModifiedLayoutNode[
		LayoutModifier         // size(120.dp)
		+
		innerLayoutNodeWrapper // Box()
	]
]

ModifiedLayoutNode 本身就是一个 LayoutNodeWrapper 的子类,所以,你现在知道上面的 wrapped 是什么了吗?

再来看看 wrapped.draw() 干了什么:

fun draw(canvas: Canvas) {
    val layer = layer
    if (layer != null) {
        layer.drawLayer(canvas)
    } else {
        val x = position.x.toFloat()
        val y = position.y.toFloat()
        canvas.translate(x, y)
        drawContainedDrawModifiers(canvas)
        canvas.translate(-x, -y)
    }
}

又跳到 draw() 方法了,而你还记得 drawContainedDrawModifiers 是干嘛的吗?它的核心就是用来检查是否有 DrawModifier 的链表的表头,如果没有就一直往内部找:

ModifiedLayoutNode[
	LayoutModifier             // padding(10.dp) --> 没有设置 DrawModifier,就去找内部有没有设置 DrawModifier
	+
	ModifiedLayoutNode[
		LayoutModifier         // size(120.dp) --> 没有设置 DrawModifier,就去找内部有没有设置 DrawModifier
		+
		innerLayoutNodeWrapper // Box() --> 没有设置 DrawModifier,它没有内部了
	]
]

所以我们总结下:不管你设置了多少 Modifier.**,在绘制的时候,都会遍历一遍,只要没有 DrawModifier 就不会进行任何绘制动作。

  1. 表头不为空,有设置 DrawModifier
else {
    head.draw(canvas)
}

现在我们设置了 DrawModifier,那么就会执行 head.draw(),我们再来看看它又做了什么:

// DrawEntity.kt
fun draw(canvas: Canvas) {
    ... ...

    val drawScope = layoutNode.mDrawScope
    // 1. 核心代码
    drawScope.draw(canvas, size, layoutNodeWrapper, this) {
        with(drawScope) {
            with(modifier) {
                draw()
            }
        }
    }
}

// LayoutNodeDrawScope.kt
internal inline fun draw(
    canvas: Canvas,
    size: Size,
    layoutNodeWrapper: LayoutNodeWrapper,
    drawEntity: DrawEntity,
    block: DrawScope.() -> Unit
) {
    val previousDrawEntity = this.drawEntity
    this.drawEntity = drawEntity
    // 2. 核心代码
    canvasDrawScope.draw(
        layoutNodeWrapper.measureScope,
        layoutNodeWrapper.measureScope.layoutDirection,
        canvas,
        size,
        block
    )
    this.drawEntity = previousDrawEntity
}

// CanvasDrawScope.kt
inline fun draw(
    density: Density,
    layoutDirection: LayoutDirection,
    canvas: Canvas,
    size: Size,
    block: DrawScope.() -> Unit
) {
    ... ...
    this.block() // 3. 核心代码
    ... ...
}

最终调用了 block(),这个 block 太熟了,它肯定是一个传进来的 Lambda 表达式,也就是:

在这里插入图片描述

所以我们的重点就转移到研究这段 lambda 表达式的工作内容了。

它里面也有一个 draw(),我们看看它做了啥:

// DrawModifier.kt
@JvmDefaultWithCompatibility
interface DrawModifier : Modifier.Element {

    fun ContentDrawScope.draw()
}

咦?是 DrawModifier 接口的 draw() 方法,那肯定有某个这个接口的实现类实现了这个 draw() 方法,我们可以搜索下看看:

在这里插入图片描述

看到没,Background

所以我们现在可以来看下 Background 的内部的 draw() 逻辑:

private class Background constructor(
    ... ...
) : DrawModifier, InspectorValueInfo(inspectorInfo) {
    ... ...

    override fun ContentDrawScope.draw() {
        if (shape === RectangleShape) {
            // shortcut to avoid Outline calculation and allocation
            drawRect()
        } else {
            drawOutline()
        }
        drawContent()
    }
    ... ...
}

咦?出现了一个 drawContent()

还记得我们文章开头第二个疑惑的问题吗?我们回顾一下代码:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            ComposeBlogTheme {
                Box(Modifier.background(Color.Green).size(120.dp).drawWithContent { drawContent() }) {
                    Text("Hi Compose")
                }
            }
        }
    }
}

现在我们就可以来看看 drawWithContent 的内部代码:

// DrawModifier.kt
fun Modifier.drawWithContent(
    onDraw: ContentDrawScope.() -> Unit
): Modifier = this then DrawWithContentElement(onDraw)

@OptIn(ExperimentalComposeUiApi::class)
private data class DrawWithContentElement(
    val onDraw: ContentDrawScope.() -> Unit
) : ModifierNodeElement<DrawWithContentModifier>() {
    override fun create() = DrawWithContentModifier(onDraw) // 创建 DrawWithContentModifier
	... ...
}

@OptIn(ExperimentalComposeUiApi::class)
private class DrawWithContentModifier(
    var onDraw: ContentDrawScope.() -> Unit
) : Modifier.Node(), DrawModifierNode {

    override fun ContentDrawScope.draw() {
        onDraw()  // 这不就类似于 block?
    }
}

所以实际上调用的是啥?

在这里插入图片描述

也是调用了 drawContent()。

现在我们就可以重点研究下 drawContent() 到底是做了什么了!

interface ContentDrawScope : DrawScope {
    /**
     * Causes child drawing operations to run during the `onPaint` lambda.
     */
    fun drawContent()
}

又是一个接口内部的方法,再搜一下哪个地方实现了 - - 全局只有一个地方实现了,如下:

internal class LayoutNodeDrawScope(
    private val canvasDrawScope: CanvasDrawScope = CanvasDrawScope()
) : DrawScope by canvasDrawScope, ContentDrawScope {

    private var drawEntity: DrawEntity? = null

    override fun drawContent() {
        drawIntoCanvas { canvas ->
            val drawEntity = drawEntity!!
            val nextDrawEntity = drawEntity.next
            if (nextDrawEntity != null) {
                nextDrawEntity.draw(canvas)
            } else {
                drawEntity.layoutNodeWrapper.performDraw(canvas)
            }
        }
    }
	... ...
}

drawIntoCanvas 是什么?

inline fun DrawScope.drawIntoCanvas(block: (Canvas) -> Unit) = block(drawContext.canvas)

又是一个 block,所以我们只关心 lambda 表达式内部的逻辑即可,分两步走:

  1. 如果 DrawModifier 链表的下一个节点不为 null,说明还有 DrawModifier,从 DrawModifier 链表头开始遍历调用 DrawModifier 处理绘制。
  2. 如果 DrawModifier 链表的下一个节点为 null,说明只有一个 DrawModifier 链表头或没有更多 DrawModifier,就让你包裹的子节点的 DrawModifier 链表上去绘制。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux查看物理CPU个数、核数、逻辑CPU个数

文章目录 总核数总逻辑CPU数查看物理CPU个数查看每个物理CPU中core的个数(即核数)查看逻辑CPU的个数 总核数 总核数 物理CPU个数 X 每颗物理CPU的核数 总逻辑CPU数 总逻辑CPU数 物理CPU个数 X 每颗物理CPU的核数 X 超线程数 查看物理CPU个数 cat /proc/cpuinfo| grep “…

湖南大学-数据库系统-2015期末考试解析

【写在前面】 这是2015年的卷子&#xff0c;应该是我能找到最老的一张了&#xff0c;遂做了并与同学校对了答案。答案仅供参考。这张难度不大&#xff0c;都是基础题。 一.单选题&#xff08;每题 2 分&#xff0c;共 20 分&#xff09; 1、在数据库中&#xff0c;下列说法&a…

企业工商基本信息API:一站式掌握企业核心数据

引言 在当今快速发展的商业环境中&#xff0c;了解企业的基本信息是每个业务决策者的基本需求。然而&#xff0c;手动收集和处理这些信息既耗时又容易出错。企业工商基本信息查询API的出现&#xff0c;为企业提供了一个高效、准确的一站式解决方案。 企业工商基本信息API 企…

win10录音功能大盘点,帮你轻松搞定录音

“有人知道win10系统怎么录音吗&#xff1f;在网上找到了一段英语听力&#xff0c;本来打算保存下来&#xff0c;但是发现不能下载&#xff0c;我也不会使用电脑录音&#xff0c;真的很头疼&#xff0c;有人能帮帮我吗。” 在Windows 10系统中&#xff0c;录音是一项常见但往往…

PPT插件-布局参考-增加便携尺寸功能

PPT自带的尺寸为很久的尺寸&#xff0c;很多尺寸不常用&#xff0c;这里增加一些画册尺寸&#xff0c;用于PPT排版设计。 软件介绍 PPT大珩助手是一款全新设计的Office PPT插件&#xff0c;它是一款功能强大且实用的PPT辅助工具&#xff0c;支持Wps Word和Office Word&#x…

Python——字符串的拼接

print("某某程序员" "月薪过万") name "吱昂张程序员" address "**大学" tel 19819208830 print("我是:"name"我的地址在&#xff1a;"address)#通过占位的形式完成字符串换的拼接 name"吱昂张" me…

【项目实战】Cadence工具的使用2

代码覆盖率的收集 双击total&#xff0c;打开imc工具。total 下的文件是代码覆盖率文件 找到DUT模块&#xff01;从图中可以看到代码的覆盖率已经是94.43% 添加exclude文件&#xff0c;注意和Synopsys的后缀不同。 导入.vRefine文件 代码覆盖率为100%。 原因是我们添加了exclu…

VS2022 | 调整适配虚幻5的设置

VS2022 | 调整适配虚幻5的设置

Spring学习 Spring事务控制

7.1.事务介绍 7.1.1.什么是事务&#xff1f; 当你需要一次执行多条SQL语句时&#xff0c;可以使用事务。通俗一点说&#xff0c;如果这几条SQL语句全部执行成功&#xff0c;则才对数据库进行一次更新&#xff0c;如果有一条SQL语句执行失败&#xff0c;则这几条SQL语句全部不…

工业自动化中RFID标签的应用案例

RFID标签是实现RFID数据采集的重要载体&#xff0c;利用RFID标签&#xff0c;可以将所有产品的信息写入标签中&#xff0c;大部分的RFID标签都以不干胶标签的形式使用&#xff0c;只需要在物品包装上贴RFID标签就可以。下面我们就一起来了解一下&#xff0c;工业自动化中RFID标…

编程代码设计GUI界面

前情提要 GUI界面有元件拖动和编程代码两种设计方式&#xff0c;元件拖动比较直观&#xff0c;编程代码更加细致。本来搞了一个包含各种元件的项目&#xff0c;最后发现代码比较长&#xff0c;一下子扔出来对初学者非常不友好&#xff0c;所以我们分开一段一段来添加&#xff…

Eureka注册中心Eureka提供者与消费者,Eureka原理分析,创建EurekaServer和注册user-service

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、Eureka提供者与消费者二、Eureka原理分析eurekaeureka的作用eureka总结 三、创建EurekaServer和注册user-service创建EurekaServer总结 服务的拉取总结-Eur…

Adding Conditional Control to Text-to-Image Diffusion Models——【论文笔记】

本文发表于ICCV2023 论文地址&#xff1a;ICCV 2023 Open Access Repository (thecvf.com) 官方实现代码&#xff1a;lllyasviel/ControlNet: Let us control diffusion models! (github.com) Abstract 论文提出了一种神经网络架构ControlNet,可以将空间条件控制添加到大型…

Spark---RDD(双值类型转换算子)

文章目录 1.RDD双值类型算子1.1 intersection1.2 union1.3 subtract1.4 zip 1.RDD双值类型算子 RDD双Value算子就是对两个RDD进行操作或行动&#xff0c;生成一个新的RDD。 1.1 intersection 对源 RDD 和参数 RDD 求交集后返回一个新的 RDD 函数定义&#xff1a; def inters…

在macos上查看当前进程的栈信息

概述 在调试程序时&#xff0c;如cpu莫名的高或低&#xff0c;一个常用的方式就是打印当前进行的调用栈&#xff0c;然后确认各线程的执行函数是否有异常。 在linux系统中可以使用pstack命令&#xff0c;直接打印各线程的栈信息&#xff0c;可惜在macos上没有该命令。一种解决…

了解VR虚拟现实的沉浸式效果及其技术特点!

VR虚拟现实体验装置作为近年来人气火爆的科技产品&#xff0c;以其独特的沉浸式体验效果吸引了众多用户&#xff0c;那么&#xff0c;你知道这种VR体验装置是如何实现沉浸式体验效果的吗&#xff1f;它又具备了哪些技术特点呢&#xff1f; 一、真实的场景体验 VR虚拟现实技术通…

IOCDI

控制反转IOC、依赖注入DI 控制反转IOC&#xff1a;删除new对象的代码&#xff0c;交给IOC容器管理&#xff0c;加上Component 依赖注入DI&#xff1a;Autowired &#xff0c;就可以实现程序运行时IOC容器自动注入需要的依赖对象 Bean 声明bean的四大注解&#xff1a; Compon…

SSL证书安装在哪?

安装SSL证书的具体步骤取决于你使用的服务器软件和操作系统。一般来说&#xff0c;SSL证书通常用于加密网站上的数据传输&#xff0c;因此安装过程主要涉及到Web服务器的配置。以下是一般步骤&#xff0c;但请注意这可能因你的具体环境而异。 永久免费SSL证书_永久免费https证…

Retro-2 选择性抑制剂 1201652-50-7星戈瑞

Retro-2选择性抑制剂1201652-50-7是一种化学结构独特的化合物&#xff0c;具有高度选择性和高效性。其化学结构包含多个关键基团&#xff0c;这些基团在抑制Retro-2酶的同时&#xff0c;对其他酶的影响较小。 Retro-2选择性抑制剂1201652-50-7通过与Retro-2酶结合&#xff0c;…

金和OA C6 GetHomeInfo SQL注入漏洞

产品简介 金和OA协同办公管理系统软件&#xff08;简称金和OA&#xff09;&#xff0c;本着简单、适用、高效的原则&#xff0c;贴合企事业单位的实际需求&#xff0c;实行通用化、标准化、智能化、人性化的产品设计&#xff0c;充分体现企事业单位规范管理、提高办公效率的核…
最新文章