深入剖析 Jetpack Compose:currentComposer与Activity和currentRecomposeScope 与 Compose函数的对应关系

引言

Jetpack Compose 作为现代 Android 开发中声明式 UI 编程的翘楚,革新了开发者构建用户界面的方式。其中,currentComposerActivitycurrentRecomposeScopeCompose 函数之间的一一对应关系,犹如两条隐秘的线索,串联起 Compose 高效渲染与精准状态管理的奥秘。理解这两组对应关系,不仅是掌握 Compose 运行机制的关键,更是编写出稳定、高效 Compose 应用的不二法门。接下来,我们将深入 Composition 源码,揭开这两组对应关系的神秘面纱。

一、currentComposerActivity 的一一对应关系

1. currentComposer 概述

在 Jetpack Compose 的宏大舞台上,currentComposerComposer 实例的身份,扮演着核心导演的角色。它就像一位技艺精湛的指挥家,凭借着对节奏和细节的精准把控,协调着 Compose 应用中各个可组合函数的有序执行。Composer 细致地跟踪着所有可组合函数的状态和执行顺序,如同一位严谨的史官,记录着哪些 UI 部分需要重新渲染,确保整个应用界面能够流畅、及时地更新。

2. ActivityComposition 的关联

在 Android 开发的传统版图中,Activity 一直是承载用户界面的核心容器。当引入 Jetpack Compose 来构建界面时,setContent 方法成为了连接传统与现代的桥梁。以下是一个简单而经典的示例:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            // Compose UI 内容
            Text(text = "Hello, Compose!")
        }
    }
}

在上述代码中,调用 setContent 方法后,系统内部会创建一个 ComposeView,并调用其 setContent 方法,进而触发 Composition 的创建以及 Composer 的初始化。这一系列操作,如同一场精心编排的舞蹈,将 Activity 与 Compose UI 紧密地联系在一起。

我们也可以采用一种更具层次感的写法,将 Compose UI 优雅地适配到传统 Android View 体系中:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(ComposeView(this).apply {
            setContent {
                // Compose UI 内容
                Text(text = "Hello, Compose!")
            }
        })
    }
}

更令人惊喜的是,Compose 还支持将传统 Android View 融入到 Compose UI 中,实现两者的混合开发,示例如下:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            AndroidView(factory = { context -> TextView(context) }) {
                it.text = "Hello, Android!"
            }
        }
    }
}

3. 从 Composition 源码看对应关系

CompositionImpl 类中,composer 作为一个只读属性,与 CompositionImpl 实例的创建紧密相连。当 CompositionImpl 被实例化时,composer 也会同步诞生,代码如下:

private val composer: ComposerImpl =
    ComposerImpl(
        applier = applier,
        parentContext = parent,
        slotTable = slotTable,
        abandonSet = abandonSet,
        changes = changes,
        lateChanges = lateChanges,
        composition = this
    ).also {
        parent.registerComposer(it)
    }

详细解释

  • applierApplier 继承自 AbstractApplier<LayoutNode>,它是 Compose 组合体与实际 UI 系统之间的关键纽带。不同的 Activity 就像不同风格的建筑,有着各自独特的 UI 布局需求。applier 的使命就是将 Compose 生成的虚拟 UI 结构,如同一位技艺高超的工匠,精准地转化为实际的 UI 元素,确保每个 Activity 的界面都能完美呈现。
  • parentContextCompositionContext 为组合体提供了必要的上下文信息,就像为一场戏剧演出搭建了舞台和背景。它负责管理组合体的生命周期,并协调各种相关操作。每个 Activity 都拥有自己独立的上下文环境,不同 Activity 对应的 parentContext 各不相同。这种独立性就像为每个 Activity 配备了专属的指挥室,保证了每个 Activity 中的 Composer 可以独立运行,互不干扰。
  • slotTableSlotTable 是一个用于存储组合信息的精密数据库,它记录着可组合函数的状态和位置。每个 Activity 中的 Composer 都拥有自己独立的 slotTable,就像每个城市都有自己的图书馆,各自独立地跟踪和管理其内部可组合函数的状态,确保信息的准确性和独立性。

由于每个 Activity 中的 ComposeView 都会创建独立的 Composition 实例,而每个 Composition 实例又会进一步创建独立的 Composer 实例。这是因为 Activity 本身就是独立的 UI 容器,不同 Activity 之间的 UI 是相互隔离的。每个 Activity 中的 ComposeView 会根据自身的 CompositionContext 创建独立的 Composer,从而实现了 currentComposerActivity 的一一对应关系。

4. 对应关系的意义

这种一一对应关系为不同 Activity 中的 Compose UI 提供了独立管理和渲染的能力。每个 ActivityComposer 就像一位独立的项目经理,独立跟踪其内部可组合函数的状态和执行顺序。当某个 Activity 中的可组合函数状态发生变化时,只会触发该 Activity 对应的 Composer 进行重组操作,而不会对其他 Activity 的 UI 产生任何影响。这种独立性保证了 UI 的稳定性和可靠性,同时也提高了应用的性能和响应速度,为用户带来了更加流畅的使用体验。

二、currentRecomposeScopeCompose 函数的一一对应关系

1. currentRecomposeScope 概述

currentRecomposeScope 作为 RecomposeScope 实例,在 Compose 函数的运行过程中扮演着智能监控者的角色。它就像一个时刻保持警觉的哨兵,密切关注着 Compose 函数内部状态的变化。当 Compose 函数内部的状态发生改变时,currentRecomposeScope 会根据状态变化的情况,精准地决定是否对该函数进行重组操作,确保 UI 能够及时反映最新的状态信息。

2. Compose 函数与 RecomposeScope 的关联

CompositionImpl 中,当调用 setContentrecompose 方法时,系统会为传入的 @Composable 函数创建独立的 RecomposeScope。以 composeContent 方法为例:

override fun composeContent(content: @Composable () -> Unit) {
    guardChanges {
        synchronized(lock) {
            drainPendingModificationsForCompositionLocked()
            guardInvalidationsLocked { invalidations ->
                val observer = observer()
                if (observer != null) {
                    observer.onBeginComposition(
                        this,
                        invalidations.asMap() as Map<RecomposeScope, Set<Any>?>
                    )
                }
                composer.composeContent(invalidations, content)
                observer?.onEndComposition(this)
            }
        }
    }
}

在上述代码中,composer.composeContent 方法会为 content 这个 @Composable 函数创建对应的 RecomposeScope,为该函数的状态监控和重组操作提供支持。

3. 从 Composition 源码看对应关系

CompositionImpl 中,RecomposeScope 的管理是通过 ScopeMap 来实现的。下面是 recordReadOf 方法的代码示例:

override fun recordReadOf(value: Any) {
    // Not acquiring lock since this happens during composition with it already held
    if (!areChildrenComposing) {
        composer.currentRecomposeScope?.let {
            it.used = true
            val alreadyRead = it.recordRead(value)
            if (!alreadyRead) {
                if (value is StateObjectImpl) {
                    value.recordReadIn(ReaderKind.Composition)
                }

                observations.add(value, it)

                // Record derived state dependency mapping
                if (value is DerivedState<*>) {
                    val record = value.currentRecord
                    derivedStates.removeScope(value)
                    record.dependencies.forEachKey { dependency ->
                        if (dependency is StateObjectImpl) {
                            dependency.recordReadIn(ReaderKind.Composition)
                        }
                        derivedStates.add(dependency, value)
                    }
                    it.recordDerivedStateValue(value, record.currentValue)
                }
            }
        }
    }
}

详细解释

  • composer.currentRecomposeScope:在组合过程中,Composer 会为每个 @Composable 函数维护一个当前的 RecomposeScope。这个 RecomposeScope 就像一个专属的日志本,会详细记录该函数读取的状态对象信息。
  • recordRead:当 @Composable 函数读取一个状态对象时,RecomposeScope 会调用 recordRead 方法记录这个读取操作。通过这种方式,当状态对象发生变化时,RecomposeScope 就能够准确知道哪些 @Composable 函数需要进行重组操作,实现精准的状态更新。
  • observationsderivedStatesobservations 是一个 ScopeMap,它用于存储状态对象和对应的 RecomposeScope 之间的映射关系,就像一个联系人列表,记录着状态对象和 RecomposeScope 之间的关联。derivedStates 则用于记录派生状态的依赖关系。通过这些映射关系,RecomposeScope 可以精确地跟踪状态变化,并根据变化情况决定是否触发重组操作。这里的 derivedState 实际上对应 derivedStateOf 函数,用于处理派生状态的计算和更新。

每个 @Composable 函数都拥有自己独立的 RecomposeScope,用于跟踪其内部状态的变化。当状态发生变化时,对应的 RecomposeScope 会立即触发重组操作,确保 Compose 函数能够及时更新 UI,为用户呈现最新的界面内容。

4. 对应关系的意义

这种一一对应关系为每个 Compose 函数的重组过程提供了独立可控的能力。不同的 Compose 函数可能依赖不同的状态,就像不同的工匠在不同的工坊里工作,各自依赖不同的工具和材料。通过独立的 RecomposeScope,可以精确控制每个函数的重组时机,避免不必要的重组操作。例如,当某个 Compose 函数的某个状态发生变化时,只有该函数对应的 RecomposeScope 会触发重组操作,而不会影响其他 Compose 函数。这大大提高了 Compose 应用的性能和响应速度,减少了系统资源的浪费,使应用更加高效和流畅。

三、总结

currentComposerActivity 一一对应,currentRecomposeScopeCompose 函数一一对应,这两组对应关系是 Jetpack Compose 实现高效渲染和精准状态管理的核心机制。通过对 Composition 源码的深入分析,我们清晰地看到了 Compose 是如何利用这些机制实现 UI 的动态更新和高效管理的。

在实际开发过程中,开发者应充分利用这些特性,合理设计 Compose 函数,科学管理 Activity 中的 Compose UI。通过优化函数结构和状态管理,能够进一步提升应用的性能和用户体验。同时,持续深入研究 Compose 的源码,有助于开发者更好地应对开发中遇到的各种问题,充分发挥 Jetpack Compose 的最大潜力。

值得注意的是,从源码分析可以看出,Compose UI 框架采用了空间换时间的策略。传统的 setContentView 方法需要进行解析 XML 的 IO 操作,这会消耗一定的时间和资源。而 Compose 框架避免了这一操作,提高了渲染速度,但在渲染过程中会创建大量对象,占用一定的内存空间。因此,在使用 Compose 布局时,开发者应尽量减少复杂布局的使用,优化内存占用。不过,随着现代手机内存容量的不断增大,这一问题对应用性能的影响通常较小。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 228,333评论 6 531
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 98,491评论 3 416
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 176,263评论 0 374
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,946评论 1 309
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,708评论 6 410
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,186评论 1 324
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,255评论 3 441
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,409评论 0 288
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,939评论 1 335
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,774评论 3 354
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,976评论 1 369
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,518评论 5 359
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,209评论 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,641评论 0 26
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,872评论 1 286
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,650评论 3 391
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,958评论 2 373

推荐阅读更多精彩内容