本文译自「Remote Compose in Android Automotive OS: Embed rich UI without the usual pain」,原文链接https://proandroiddev.com/remote-compose-in-android-automotive-os-embed-rich-ui-without-the-usual-pain-90b857d59d8c,由Daniel Georg发布于2026年5月31日。
在 Android Automotive OS 上构建信息娱乐系统时,一个问题会反复出现:你需要将一个应用程序的用户界面显示在另一个应用程序的界面中。例如,在启动器中添加手机小部件,在仪表盘中添加媒体卡片,或者在 OEM 系统界面中嵌入第三方应用程序的内容。听起来很简单——直到你真正尝试实现它。
Android 提供了多种跨应用 UI 共享机制,每种机制都有其优缺点。让我们来看看目前有哪些机制:
内部组件/视图——这是显而易见的首选:所有内容都直接构建在宿主应用内部(例如启动器)。没有跨进程开销。缺点是需要掌握相关知识,并且存在耦合问题。在汽车领域,要构建一个媒体卡片,你的团队需要学习并理解完整的 MediaSession/MediaBrowser 堆栈。要构建一个电话组件,他们需要深入了解 Telephony API。再加上汽车 API、OEM 内部 API 以及每个领域特有的特性——你的团队就变成了十几个互不相关领域的专家。每个组件都与其依赖的 API 紧密耦合,而当 API 发生变化时,没有人记得当初为何如此设计。
TaskView / CarTaskView — 在系统级别运行,将另一个应用的完整 Activity 嵌入到你的窗口层级结构中。它仅限于系统应用,需要特权权限,会在主机和客户端之间建立紧密的生命周期耦合,并添加一个专用的 SurfaceFlinger 层——会迅速消耗 Hardware Composer 的叠加层预算,尤其是在多用户多显示器 (MUMD) 设置中,例如主机、乘客显示屏和后排屏幕/用户同时运行的情况下。
SurfaceControlViewHost — 避免了特权权限,并共享相同的层开销,但引入了脆弱的进程间通信 (IPC) 边界。虽然基本渲染功能正常,但同步结构更改、处理客户端进程崩溃以及管理 AAOS 旋转焦点或无缝触摸手势切换都迫使主机采用复杂的手动变通方案。
RemoteViews — 任何应用均可使用,无需特殊权限。但它的组件库过于原始,无法构建任何严肃的用户界面:只有寥寥几个 TextView、ImageView 和 Button。除此之外,其他功能都无法实现。
Remote Compose方案
从本质上讲,RemoteCompose 文档是 Canvas 绘制调用的序列化记录——与 Android 用于渲染任何 UI 的原语(绘制矩形、设置颜色、剪辑区域)相同。提供程序不会立即执行这些调用,而是将它们打包成紧凑的、独立的二进制格式。它包含构建 UI 所需的一切:形状、带有字体和样式的文本、图像,甚至动画表达式。
当主机收到此文档时,它只需根据其本地 Canvas 迭代指令即可。主机不需要了解数据来自何处的领域知识——它只需回放数据即可。虽然这就像浏览器呈现静态页面一样,但提供者可以以 IPC 允许的速度推送更新的文档。这将流程转变为高频流,从而实现流畅、状态驱动的 UI 更新,完全无需 XML 膨胀的开销。
动画通过在记录时间嵌入到文档中的表达式来工作。提供程序不是对值进行硬编码,而是编写一个类似于 ContinuousSec() * 360 的表达式 - 主机在每一帧上本地对其进行计算,从而生成流畅的动画,而无需与提供程序进程进行任何往返。
PoC:将理论付诸实践
我想看看 RemoteCompose 作为跨应用程序 UI 机制可以发展到什么程度。在 AAOS 堆栈中工作多年后,我看到 OEM 编写了数千行样板文件只是为了共享一个简单的小部件。汽车发射器是完美的试验场。
模拟环境
目标还在于证明 RemoteCompose 可以与现有 AAOS 渲染技术共存。新的三列启动器布局完美地证明了这一点。

从左到右查看主显示屏。这是一个实时共存测试:
- 第 1 列:Native Widget。 直接在 Launcher 进程中实现的标准视图。在传统设置中,每个小部件都会像这样,迫使启动器管理电话 API 和复杂的观察者。
- 第 2 列:RemoteCompose。 我们的 PoC 电话小部件。通过将其转移到 RemoteCompose,启动器在设计上仍然是愚蠢的。它仅管理布局槽并回放传入的 UI 描述。 Phone 团队可以独立更新其 UI,而无需触及任何 Launcher 代码。
- 第 3 列:CarTaskView。 嵌入在启动器表面中的另一个应用程序的完整映射活动。
此次集成确认 RemoteCompose 可以与传统的基于 View 的系统和复杂的 CarTaskView 环境共存,而不会产生副作用。这验证了无中断的采用路径:原始设备制造商可以将现代的远程驱动组件逐步引入其生产堆栈,从而避免整个系统重写的风险和成本。
PoC 架构
RemoteCompose 没有提及如何传送二进制流。它仅定义格式,这意味着传输完全取决于你。以下是一些在 AAOS 环境中有意义的选项:
- AIDL: 低延迟,直接进程间通信,非常适合频繁更新。这就是 PoC 使用的内容。
- ContentProvider: 如果小部件数据已经建模为内容(如媒体元数据或联系人),那么这是一个自然的选择。它使用基于拉动的模型,非常适合静态或缓慢变化的 UI。
- BroadcastReceiver: 提供简单的推送模型。然而,缺乏背压和严格的有效负载大小限制使得除了微小更新之外的任何事情都变得尴尬。
- WebSocket: 对于服务器驱动的 UI 场景来说,这是一个有趣的选项,其中文档直接来自后端而不是本地进程。
对于 PoC,AIDL 是显而易见的选择。它提供低开销、基于回调的推送传递,并直接控制提供者何时将新文档推送到主机。
我之前提到过,这两个应用程序之间的契约非常小。这是证据。我们来看看实际的实现。
PoC 中的 AIDL 合约
提供者使用熟悉的 Compose 语法编写 UI。它使用远程等效项,而不是列、行或文本。结果永远不会呈现到屏幕上。相反,它使用 captureSingleRemoteDocument 直接打包到字节数组中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | |
语法几乎与标准 Compose 相同。唯一的区别是 Remote 前缀以及输出是 ByteArray 而不是屏幕上的像素这一事实。
主持人:渲染
现代 UI 框架的一个常见问题是采用成本。对于此 PoC,我使用标准 AAOS CarLauncher 作为主机。与大部分核心 Automotive 堆栈一样,它构建在传统 Android 视图系统上:XML 布局、片段和自定义视图。对于 OEM 而言,仅仅为了支持一些“小部件”而将生产级系统 shell 迁移到 Jetpack Compose 很少是现实的选择。
幸运的是,RemoteCompose 就是针对这一现实而设计的。它附带两个不同的玩家工件:
- remote-player-compose: 对于已在 Jetpack Compose 上运行的主机。
- remote-player-view: 对于传统的基于视图的应用程序。
由于参考 CarLauncher 是基于视图的,因此我使用了远程播放器视图工件。 RemoteDocumentPlayer 是一个普通的 FrameLayout,你可以将其直接放入任何 XML 布局中。你只需调用 setDocument(bytes),它就会通过 Canvas 呈现提供者的 UI。
主机端不需要 Compose 依赖项,不需要 ComposeView 包装器,并且迁移工作量为零。提供者端保持完全相同:无论谁渲染它,它都会推送相同的字节数组。这证明了行业采用的关键点:一个提供商可以以零耦合的方式为现代和传统主机提供服务。
关于 Android 框架 (API 35) 的说明 如果你仔细查看最新的 Android 开源项目 (AOSP) 更新,你会发现 Android 实际上从 API 35 开始将其自己的本机 RemoteCompose 播放器直接烘焙到框架中。它为标准系统小部件的新 DrawInstructions 提供支持。然而,依赖框架播放器意味着将你的主机与操作系统更新周期联系起来。通过使用上述 AndroidX 播放器工件,第三方启动器和 OEM 主机现在就可以采用这种下一代架构,即使在较旧的 Android 版本上也是如此,同时支持比基本系统更新的操作。
局限性和悬而未决的问题
