跳到主要内容

从 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-android 0.1.0-alpha02,以及底层 viewcompose-host-android 0.1.0-alpha05 引擎。
  • 最后核验: 2026-08-28。
  • 重新核验负责人: viewcompose-android、viewcompose-host-android、 viewcompose-lifecycle-androidx、viewcompose-viewmodel-androidx 和 viewcompose-renderer-android 的维护者。

相关页面:迁移总览 · 从 Compose Navigation 迁移

验证模型​

本文的上游部分是对 AndroidX 稳定版文档和发布说明的语义复核:

本地可执行基线是 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 supportedAndroidHostBridge.kt、Material3AndroidHostBridge.kt及其可编译样例。同步首帧和内部持有的会话是 ViewCompose 特有语义。
Fragment 宿主Fragment 中的 ComposeView 通常通过 DisposeOnViewTreeLifecycleDestroyed 随 Fragment View 树一起释放。中立 Fragment.setUiContent 与具名 Material setMaterial3UiContent 为 onCreateView 返回 Root,在该 Root 的 viewLifecycleOwner 发布后启动 Session,把该 Owner 提供给内容,并在 onDestroyView 释放。SupportedAndroidHostBridge.kt与 FragmentHostLifecycleIntegrationTest.kt 验证 Owner Identity、View 重建、清理,以及独立保留的 Fragment Scope ViewModel/Saveable 所有权。
现有 View 层级ComposeView 提供 Composition 释放策略并发现 ViewTree owner。renderInto 渲染到指定的 ViewGroup;它不提供生命周期、ViewModel、保存状态、环境、主题或帧时钟 owner,并要求显式释放会话。Partially supportedRenderInto.kt以及 AndroidEntrySamples.kt中已编译的 renderIntoSample。
生命周期 owner 传播Compose 宿主集成从 Activity、Fragment View 或 ViewTree 解析 AndroidX owner。Activity 内容接收 Activity Owner,Fragment 内容接收当前 View Owner;自定义 renderInto 容器不会自动获得 Owner。Partially supportedAndroidHostBridge.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 differentAndroidHostBridge.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 differentSavedStateViewModelIntegrationTest.kt 与 NavHostPublicApiTest.kt 中的保存状态覆盖。
帧调度与显式渲染Compose 重组由 Recomposer 和帧时钟协调。显式 render 是同步的。状态失效会合并到 Android 帧;处于 inactive 状态的会话会保留失效请求,直到再次激活。Intentionally differentAndroidFrameAlignedRenderSessionRuntime.kt和 AndroidFrameAlignedRenderSessionRuntimeTest.kt。
Effect 所有权与终结性释放Effect 随其 Composition 作用域退出;释放 Composition 是终结操作。一个 RenderSession 拥有 Composition 协程 Scope、渲染状态、Overlay、原生 View 和清理逻辑。Dispose 幂等;之后的公共 Render/Activation 工作快速失败,已排队的内部回调安全 no-op。SupportedRenderSession.kt、RenderSessionFailureTest.kt与 AndroidFrameAlignedRenderSessionRuntimeTest.kt。
Android View factory 与 updateAndroidView 为一个实例创建一次 View,并在适用的重组中运行 update。类型安全的 AndroidViewAdapter<V, S> 与 Callback 逃生路径会为一个构造身份创建 View,并在事务式原生树 Patch 内应用完整、可安全重放的状态。Adapter 类或 constructionKey 变化时先创建尚未挂载的候选节点;失败会保留已提交 View。SupportedAndroidViewAdapter.kt、ViewTreePatchPipeline.kt和 AndroidInteropRenderingUiTest.kt。
Android View reset、commit 与 releaseCompose 使用非空 onReset 选择加入可复用内容,并在内容永久离开 Composition 时调用 onRelease。它没有等价的事务 commit 回调。onReset(..., MountedTreeReuse) 只在主动允许的 Mounted Tree 跨逻辑 Key 复用时运行,普通更新或回滚绝不调用。onCommit 仅在完整 Composition 事务成功后运行;onRelease 为永久放弃执行一次性清理,其中包括失败候选与被替换的构造身份。Intentionally differentAndroidViewNodeProps.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 setMaterial3UiContentViewCompose 拥有内部会话;返回值是已安装的根 ViewGroup,不是会话句柄。
Fragment ComposeView从 onCreateView 返回中立 Fragment.setUiContent() 或 Material setMaterial3UiContent()ViewCompose 在 View Owner 发布后启动,把该 Owner 提供给内容,并在 onDestroyView 释放内部 Session。
嵌入式 ComposeViewrenderInto(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 互操作。

迁移检查表​

  1. 开始迁移内容前,先选择 Activity、Fragment 或现有容器宿主。
  2. 记录目标根节点的生命周期、ViewModel、保存状态、主题和帧 owner。
  3. 对 renderInto 显式安装每个必需 owner,并绑定会话释放。
  4. 把可安全重放的 View 绑定放入 update 或 onReset;把依赖已提交树的不可逆工作放入 onCommit。
  5. 让 onRelease 同时安全处理回滚候选节点和已提交删除。
  6. 把 Session Dispose 视为终态;清除调用方引用,而不是捕获快速失败的误用。
  7. 分别测试 Fragment View 重建和 Fragment 销毁。
  8. 把配置变更、永久移除和进程重建作为三类不同的状态事件测试。
  9. 导航目的地被保留但隐藏时,生命周期感知工作仍必须遵循生命周期。
  10. 移除 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。