从 Compose 宿主、生命周期与 Android 互操作迁移到 ViewCompose
本文将 Jetpack Compose 的 Android 宿主、生命周期、状态 owner 与 Android View 互操作行为 映射到 ViewCompose。这是一份工程对比,而不是声称名称相近的 API 具有相同语义。
- 来源状态: Jetpack Compose UI 与 Runtime 1.12.0、Activity 1.13.0、Lifecycle 2.11.0 和 SavedState 1.5.0。
- 目标状态:
viewcompose-android、viewcompose-lifecycle-androidx、viewcompose-viewmodel-androidx与viewcompose-renderer-android0.1.0-alpha02,以及底层viewcompose-host-android0.1.0-alpha05 引擎。 - 最后核验: 2026-08-28。
- 重新核验负责人:
viewcompose-android、viewcompose-host-android、viewcompose-lifecycle-androidx、viewcompose-viewmodel-androidx和viewcompose-renderer-android的维护者。
相关页面:迁移总览 · 从 Compose Navigation 迁移
验证模型
本文的上游部分是对 AndroidX 稳定版文档和发布说明的语义复核:
- 在 View 中使用 Compose
- 在 Compose 中使用 View
ComponentActivity.setContent- Composition 生命周期
Composition- Lifecycle 2.11 发布说明
- SavedState 发布说明
本地可执行基线是 Compose 1.7.8、Activity 1.12.4、Lifecycle 2.11.0 和 Kotlin 2.2.10。 下文引用的仓库测试和已编译样例依据这组依赖验证 ViewCompose 行为。它们不代表实际执行了 上游 Compose 1.12.0 或 Activity 1.13.0,但 Lifecycle Family 已与审阅过的 2.11.0 基线一致。 因此,只要任一基线发生变化,重新核验就必须同时重复官方语义复核和本地测试运行。
本文涉及的 ViewCompose 契约分别由 Android 聚合层、 Android 宿主引擎、 生命周期、 ViewModel 和 渲染器模块负责。
可编译的成对起点
下面的对照先展示最小 Activity 根宿主和原生 View 路径,不包含后续的生命周期与清理策略。
两个片段都从 :samples:compose-migration 提取;qaQuick 会编译对应源码并拒绝文档漂移。
Compose 源码:
fun ComponentActivity.installComposeInteropSample() {
setContent {
ComposeInteropSample()
}
}
@Composable
private fun ComposeInteropSample() {
AndroidView(
factory = { context -> TextView(context) },
update = { view -> view.text = "Native TextView" },
)
}
ViewCompose 目标:
fun ComponentActivity.installViewComposeInteropSample() {
setMaterial3UiContent {
ViewComposeInteropSample()
}
}
private fun UiTreeBuilder.ViewComposeInteropSample() {
AndroidView(
factory = { context -> TextView(context) },
update = { view ->
(view as TextView).text = "Native TextView"
},
)
}
该示例只证明 Callback 逃生路径的公共安装、Factory 与可安全重放的 Update。可复用集成应使用
AndroidViewAdapter<V, S>,让 View 类型、状态、构造身份、复用策略与清理组成同一个可编译
契约。两种形式都不会隐式继承 Compose 的释放或复用语义。
能力矩阵
状态值仅使用 Supported、Partially supported、Intentionally different 和 Unsupported。
| 概念 | Compose / AndroidX 行为 | ViewCompose 行为 | 状态 | 本地证据与验证说明 |
|---|---|---|---|---|
| Activity 根宿主 | ComponentActivity.setContent 把 Compose 内容安装到 Activity 中,并通过宿主管理 Composition。 | 中立 ComponentActivity.setUiContent 与具名 Material setMaterial3UiContent 都会替换 Activity 内容 View、同步渲染首帧、返回新的根 ViewGroup,并把 RenderSession 保存在内部注册表中,直到内容被替换或 Activity 销毁。 | Partially supported | AndroidHostBridge.kt、Material3AndroidHostBridge.kt及其可编译样例。同步首帧和内部持有的会话是 ViewCompose 特有语义。 |
| Fragment 宿主 | Fragment 中的 ComposeView 通常通过 DisposeOnViewTreeLifecycleDestroyed 随 Fragment View 树一起释放。 | 中立 Fragment.setUiContent 与具名 Material setMaterial3UiContent 为 onCreateView 返回 Root,在该 Root 的 viewLifecycleOwner 发布后启动 Session,把该 Owner 提供给内容,并在 onDestroyView 释放。 | Supported | AndroidHostBridge.kt与 FragmentHostLifecycleIntegrationTest.kt 验证 Owner Identity、View 重建、清理,以及独立保留的 Fragment Scope ViewModel/Saveable 所有权。 |
| 现有 View 层级 | ComposeView 提供 Composition 释放策略并发现 ViewTree owner。 | renderInto 渲染到指定的 ViewGroup;它不提供生命周期、ViewModel、保存状态、环境、主题或帧时钟 owner,并要求显式释放会话。 | Partially supported | RenderInto.kt以及 AndroidEntrySamples.kt中已编译的 renderIntoSample。 |
| 生命周期 owner 传播 | Compose 宿主集成从 Activity、Fragment View 或 ViewTree 解析 AndroidX owner。 | Activity 内容接收 Activity Owner,Fragment 内容接收当前 View Owner;自定义 renderInto 容器不会自动获得 Owner。 | Partially supported | AndroidHostBridge.kt、FragmentHostLifecycleIntegrationTest.kt 和 LifecycleHostGuards.kt。剩余差异是底层自定义宿主的显式所有权。 |
| ViewModel owner 传播 | Lifecycle 2.11 用 ViewModelStoreProvider 创建任意保留型子 UI Scope,继承父 Factory 与 CreationExtras,并用 Reference Token 保护退出动画等临时使用方。Compose 拥有的 Root 会发现 ViewTree owner。 | Activity Root 发现其 ViewTreeViewModelStoreOwner;Fragment 宿主显式保留 Fragment owner,而不是生命周期更短的 View owner;嵌套显式 Provider 优先。ViewModelScopeProvider 基于同一 AndroidX 原语,为任意子树、导航 Entry 和导航 Graph 提供具有稳定身份、终态清理和禁止复活语义的 Store。与 Compose 的位置便利形式不同,ViewCompose 始终要求调用方提供 Provider Key。 | Intentionally different | AndroidHostBridge.kt、ViewModelScopeProvider.kt、FragmentHostLifecycleIntegrationTest.kt,以及 NavEntryOwnerStoreTest.kt 中的导航配置重建覆盖。 |
| 保存状态 | Compose 宿主集成组合使用 SavedStateRegistryOwner、SavedStateHandle 与 saveable-state 设施。 | ViewCompose 宿主安装 ViewCompose SaveableStateRegistry;Activity、Fragment、Destination 与 Graph Owner 也支持 AndroidX 保存状态。UI 专属值使用 rememberSaveable,恢复型业务值只有一个 ViewModel 持有的 SavedStateHandle Flow。 | Intentionally different | SavedStateViewModelIntegrationTest.kt 与 NavHostPublicApiTest.kt 中的保存状态覆盖。 |
| 帧调度与显式渲染 | Compose 重组由 Recomposer 和帧时钟协调。 | 显式 render 是同步的。状态失效会合并到 Android 帧;处于 inactive 状态的会话会保留失效请求,直到再次激活。 | Intentionally different | AndroidFrameAlignedRenderSessionRuntime.kt和 AndroidFrameAlignedRenderSessionRuntimeTest.kt。 |
| Effect 所有权与终结性释放 | Effect 随其 Composition 作用域退出;释放 Composition 是终结操作。 | 一个 RenderSession 拥有 Composition 协程 Scope、渲染状态、Overlay、原生 View 和清理逻辑。Dispose 幂等;之后的公共 Render/Activation 工作快速失败,已排队的内部回调安全 no-op。 | Supported | RenderSession.kt、RenderSessionFailureTest.kt与 AndroidFrameAlignedRenderSessionRuntimeTest.kt。 |
| Android View factory 与 update | AndroidView 为一个实例创建一次 View,并在适用的重组中运行 update。 | 类型安全的 AndroidViewAdapter<V, S> 与 Callback 逃生路径会为一个构造身份创建 View,并在事务式原生树 Patch 内应用完整、可安全重放的状态。Adapter 类或 constructionKey 变化时先创建尚未挂载的候选节点;失败会保留已提交 View。 | Supported | AndroidViewAdapter.kt、ViewTreePatchPipeline.kt和 AndroidInteropRenderingUiTest.kt。 |
| Android View reset、commit 与 release | Compose 使用非空 onReset 选择加入可复用内容,并在内容永久离开 Composition 时调用 onRelease。它没有等价的事务 commit 回调。 | onReset(..., MountedTreeReuse) 只在主动允许的 Mounted Tree 跨逻辑 Key 复用时运行,普通更新或回滚绝不调用。onCommit 仅在完整 Composition 事务成功后运行;onRelease 为永久放弃执行一次性清理,其中包括失败候选与被替换的构造身份。 | Intentionally different | AndroidViewNodeProps.kt、ViewTreeDisposer.kt和 ViewTreeRenderTransactionTest.kt。 |
| ViewBinding 与树内 Fragment 互操作 | Compose 提供 AndroidViewBinding 和 AndroidFragment 集成。 | 可以在 Android View factory 中手动 inflate XML,但没有直接 ViewBinding 集成,也没有受支持的渲染树内 Fragment 对应能力。 | Unsupported | 在已审查模块中未找到对应的公共 API 或已编译样例。 |
选择宿主入口
当 Activity 或 Fragment 把宿主根内容交给 ViewCompose 管理时,使用中立 setUiContent 或具名
Material setMaterial3UiContent。只有在现有 Android View 层级必须继续拥有容器时,才使用
renderInto。后者是更底层的桥接,不是 ViewCompose 对 ComposeView 的另一种写法:
| 来源模式 | 目标模式 | 所有权变化 |
|---|---|---|
ComponentActivity.setContent | 中立 ComponentActivity.setUiContent 或 Material setMaterial3UiContent | ViewCompose 拥有内部会话;返回值是已安装的根 ViewGroup,不是会话句柄。 |
Fragment ComposeView | 从 onCreateView 返回中立 Fragment.setUiContent() 或 Material setMaterial3UiContent() | ViewCompose 在 View Owner 发布后启动,把该 Owner 提供给内容,并在 onDestroyView 释放内部 Session。 |
嵌入式 ComposeView | renderInto(existingViewGroup) | 调用方负责提供 owner 和执行释放。 |
所有宿主入口都必须针对仍处于 Active 状态的宿主调用,渲染属于 Android 主线程工作。Activity
setUiContent 与底层 renderInto 会在返回前提交首帧;Fragment setUiContent 从
onCreateView 返回 Root 后,等 Android 发布该 Root 的 View Lifecycle Owner 再提交首帧。
Activity 宿主
ComponentActivity.setUiContent 安装中立 ViewCompose 根;具名 setMaterial3UiContent 先解析
Material Context 与 token 快照,再委托相同宿主生命周期。两者都提供 Activity 生命周期与
ViewModel owner、宿主 saveable-state registry、动画上下文、帧时钟和环境。再次调用任一入口时,
都会替换并释放之前注册的 Activity 会话。
返回值是已安装的根 ViewGroup,而不是内部 RenderSession。因此,公共 Activity 宿主不会
暴露手动渲染、rendering-active 控制或提前释放会话。替换内容或销毁 Activity 时会释放已注册
会话。
Fragment 宿主
中立 Fragment.setUiContent 与具名 Material setMaterial3UiContent 都会创建并返回 Fragment
根 ViewGroup;请从 onCreateView 调用所选入口并返回该根节点。当前 viewLifecycleOwner
可用时,内部 Session Registry 会启动渲染并绑定释放,同一个 Owner 也会安装到内容中。Fragment
View 重建时会获得新的 Owner 与 Session,旧 Session 在 onDestroyView 恰好释放一次。ViewModel
与 Saveable State 所有权继续属于 Fragment,因此能跨这次仅 View 的重建保留。
渲染到现有 View 层级
renderInto 会向指定的 ViewGroup 同步执行首帧渲染。它有意不发现或安装生命周期、
ViewModel、保存状态、环境、主题或帧时钟 owner。之前依赖 ComposeView owner 发现机制的迁移
代码,必须围绕内容提供所需的 ViewCompose 局部值,并把释放绑定到所属 Android 生命周期。
调用方必须在永久放弃容器之前释放返回的会话,也不得让会话存活时间超过其拥有的 Android View。
renderInto Dispose 后,再由调用方发起 render 或 setRenderingActive 会抛出
IllegalStateException,而 Dispose 本身保持幂等。Session 内已经排队的失效或 Android 帧回调
会被取消或忽略,不能再发布一帧。
生命周期、ViewModel 与保存状态 owner
owner 迁移是语义迁移,不是类型名替换:
- Activity 宿主接收 Activity 作用域的 owner;
- Fragment 宿主把当前 View Lifecycle 用于内容和 Session 释放,同时保留 Fragment Scope 的 ViewModel 与 Saved State 所有权;
- 导航 entry 和 graph 分别拥有独立的生命周期、ViewModel 与保存状态作用域;
renderInto不会自动提供其中任何一种作用域。
Lifecycle 2.11 为任意 Compose UI 区域增加了通用 scoped ViewModel。ViewModelStoreProvider
可以让子 store 跨配置变更保留、在对应 UI 作用域永久离开时清理,并继承父级 factory 和
CreationExtras。ViewCompose 在 ViewModelScopeProvider 内使用同一个原语。普通 DSL 代码组合
rememberViewModelScopeProvider、rememberViewModelStoreOwner 与
ProvideViewModelStoreOwner;保留型容器引擎则直接获取并关闭 ViewModelStoreOwnerLease。
Lease 关闭表示临时释放,clear(key) 与 clearAll() 才是终态信号。活动 Lease 会延迟清理,并在
旧生命周期结束前阻止复活。父 Lifecycle 已销毁时会为配置重建保留共享状态,Provider 正常移除时
则执行清理。
ViewCompose 有意要求显式稳定 Provider/子 Key,不提供按位置派生的保留型 Provider Overload。 同一父级下相等的 Provider Key 共享状态,相等的子 Key 只在对应 Provider 内共享。导航 Entry 与 Graph Owner 现在也使用同一个 Provider:保存的 host-scope 身份会让它们的 Store 跨配置重建 保留,而 pop、graph 删除与宿主正常移除都是终态信号。
聚合宿主只在拥有 Android Root 的边界解析 owner。Activity 内容发现 Root 的
ViewTreeViewModelStoreOwner。Fragment 内容即使能从 Root ViewTree 看到生命周期更短的
FragmentViewLifecycleOwner,仍会显式使用 Fragment 作为 Store owner,从而让 Fragment Scope
ViewModel 跨 View 重建保留。嵌套 ProvideViewModelStoreOwner 仍对其子树优先。底层
renderInto 继续不提供 owner,也不执行 ViewTree 发现。
ViewModel Lookup 现在与 AndroidX Key 和 Creation 语义一致。只有 null 选择按 Class 派生的
默认 Key;空字符串和仅空白字符串都是显式 Key。此前用 Blank String 作为默认 Sentinel 的迁移
代码必须改传 null。Reified 与运行时 KClass Initializer Overload 会接收 Owner 的
CreationExtras,因此 Constructor Dependency 与 createSavedStateHandle() 不再需要一次性的
单 Class Factory。Owner 的 ViewModelStore 是唯一实例缓存,清理后会在下一次实际执行的
Composition 调用中被观察到。
独立 savedStateHandle() 函数与 SavedStateHandleHolderViewModel 已无别名删除。把旧 Helper
Key 迁移为真实业务 ViewModel Key,通过 Owner 默认 Factory 注入 Handle,或在 Initializer 内创建
它,并把每个恢复型业务值移入该 ViewModel。不要保留兼容 Holder,不要用 rememberSaveable 与
SavedStateHandle 双写同一个值,也不要只为复制 Compose API 外形而增加 Snapshot Adapter。
ViewCompose SaveableStateRegistry、AndroidX SavedStateRegistryOwner 和
SavedStateHandle 服务于不同层次。迁移时应明确每个值由哪一层拥有,并把进程重建与内存中
配置变更分别验证。rememberSaveable 继续持有 UI 专属状态;
SavedStateHandle.getMutableStateFlow() 继续作为业务状态路径。
会话、帧、Effect 与释放语义
RenderSession 拥有的不只是一个类似 Composition 的内容函数。它拥有 composition 协程
作用域、已挂载原生树、overlay 状态、帧调度和清理。成功的显式 render 会同步提交。状态驱动
的失效会与帧对齐并合并。禁用渲染会暂停交付这些帧,但不会丢弃待处理失效。
释放是终结且幂等的。它先取消 composition 作用域工作,再释放原生树和 overlay。导航是特殊 保留场景:隐藏目的地会话可在帧驱动渲染 inactive 时保持存活。仅仅隐藏目的地不会取消其 composition 作用域中的 Effect。生命周期感知迁移规则见 从 Compose Navigation 迁移。
Android View 互操作回调映射
ViewCompose Android View 回调参与渲染器的原生树事务:
可复用集成应实现 AndroidViewAdapter<V, S> 并传入完整状态快照。VNode 的 key 是逻辑内容
身份;Adapter 实现类与 constructionKey 共同组成物理构造身份。改变构造身份会原子替换 View,
无需伪装成逻辑条目变化。Adapter Scope 会暴露 VNode 的不可变 Environment,但不会公开其
Constructor、Renderer/Session 内部对象或可变 Transaction。
| 回调 | 必需的迁移解释 |
|---|---|
create / factory | 只创建新的构造身份。不要读取应放入 update 的变化状态。 |
update | 必须可安全重放。失败帧可以恢复此前已提交的树。 |
onReset | 必须可安全重放。只在 Resettable 节点跨逻辑 Key 时运行;普通更新与回滚绝不调用。 |
onCommit | 仅在完整原生树事务成功后运行。需要已提交树的不可逆工作应放在这里。 |
onRelease | 每当已创建节点被永久放弃时执行一次性清理,包括替换、删除、会话释放和未提交候选节点的回滚。 |
不支持的直接互操作
ViewCompose 0.1.0-alpha05 没有 Compose AndroidViewBinding 或 AndroidFragment 的直接
对应能力。factory 可以 inflate XML 布局,但 ViewBinding 生命周期管理和 Fragment 所有权
仍由应用负责。不要把 Fragment 直接放入 ViewCompose 渲染树,也不要因为能托管其根 View 就
推断已支持 Fragment。
迁移风险
- Fragment 内容会在
setUiContent返回后、Android 发布 View Owner 时开始;代码不能要求 Content 内工作在onCreateView本身返回前完成。 - 隐藏导航目的地在帧渲染 inactive 时仍保留 composition 作用域和 Effect。
- 保留型子 Scope 要求稳定的 Provider/子身份,并明确区分临时 Lease 释放与终态
clear;把位置或 可见性误作任一信号,都可能重建状态或过早清理。 NavHost缺少LocalViewModelStoreOwner时会直接失败;自定义renderInto宿主必须显式提供。renderInto不会自动发现 ViewTree owner,也没有 Composition 释放策略。- 不支持直接 ViewBinding 与渲染树内 Fragment 互操作。
迁移检查表
- 开始迁移内容前,先选择 Activity、Fragment 或现有容器宿主。
- 记录目标根节点的生命周期、ViewModel、保存状态、主题和帧 owner。
- 对
renderInto显式安装每个必需 owner,并绑定会话释放。 - 把可安全重放的 View 绑定放入
update或onReset;把依赖已提交树的不可逆工作放入onCommit。 - 让
onRelease同时安全处理回滚候选节点和已提交删除。 - 把 Session Dispose 视为终态;清除调用方引用,而不是捕获快速失败的误用。
- 分别测试 Fragment View 重建和 Fragment 销毁。
- 把配置变更、永久移除和进程重建作为三类不同的状态事件测试。
- 导航目的地被保留但隐藏时,生命周期感知工作仍必须遵循生命周期。
- 移除 Compose 宿主前,记录对任何不受支持的 Compose 互操作 API 的依赖。
重新核验要求
以下任一项发生变化时,都要重新核验本文:
- 宿主入口、owner 局部值、会话释放规则或 Android View 回调契约;
- Compose UI/Runtime、Activity、Lifecycle 或 SavedState 稳定版基线;
- 仓库的 Compose/AndroidX 可执行对比基线;
- 上文列出的任一保留验证缺口。
最低证据包括所属模块契约、引用的 JVM 测试、Android 互操作 instrumentation、已编译宿主 样例,以及对所链接 AndroidX 官方文档的重新复核。Fragment View 重建和渲染器事务行为需要 行为测试;只有 API 签名并不足够。
Phase 3 的干净运行通过了 151/151 项 Navigation Android 用例与 21/21 项 Android 聚合宿主 用例。相对 148 项导航基线,新增契约覆盖缺失 owner 失败、配置重建保留 Entry ViewModel、旧格式 恢复、Activity ViewTree 发现与 Fragment 显式 owner 优先级。结论:可执行 owner 选择与保留能力 improved。真机进程终止、内存和性能行为未测量,因此这些维度仍为 inconclusive。