使用 Jetpack Compose 提升 Play 商店的用户体验

为了让 Jetpack Compose 的使用体验更上一层楼,以及了解大家对 Compose 开发、学习方面的内容需求,这里诚邀您参与 Jetpack Compose 使用情况调研, 点击这里 即刻参与调研。

作者 / Google Play 技术负责人 Andrew Flynn 和 Jon Boekenoogen

2020 年,Google Play 商店开发团队管理层做出了一个重大决定: 改造整个 Play 商店技术栈。因为现有代码的历史已经长达 10 多年,在无数的 Android 平台版本发布和功能更新的过程中产生了巨大的技术负债。我们需要新的框架,在不影响开发者的工作效率、用户体验或 Play 商店自身性能的同时,能够支撑数百名工程师同时开展工作。

我们为此制定了一个长期路线图,来更新商店内从网络层一直到像素渲染的所有内容。在这之中,我们还想要采用现代的声明式界面框架,以实现我们围绕交互性和用户满意度的产品目标。在分析了各种选择后,我们做出了 (在当时) 一个大胆的决定——使用当时还处于 Alpha 预览阶段的 Jetpack Compose

从那时起,Google Play 商店与 Jetpack Compose 团队密切合作,发布并完善了满足我们特定需求的 Jetpack Compose 版本。本文将为您介绍我们的迁移方法以及在此过程中发现的挑战和优势,并分享一些对于有众多贡献者的应用选择 Compose 的洞察。

优先考虑

当我们对新的界面渲染层使用 Jetpack Compose 时,需要优先考虑以下两点:

  1. 开发者的工作效率 : Play 商店团队有数百个工程师改进代码,因此开发起来应该很容易 (也很有趣)。
  2. 性能 : Play 商店会渲染大量媒体密集型内容,其中很多业务指标对延迟和卡顿十分敏感,所以我们需要确保它在所有设备上表现良好,尤其是低内存硬件和 Android (Go 版本) 设备。

开发者的工作效率

一年多来 ,我们一直在使用 Jetpack Compose 编写用户界面代码,也得益于 Jetpack Compose 让界面开发变得更加简单。

我们倾向于 编写界面时使用更少的代码,有时甚至可以减少 50%。此项改进的实现得益于 Compose 是一个利用了 Kotlin 简洁性的声明式界面框架。自定义绘图和布局现在是简单的函数调用,而不用再通过对视图子类进行各种复写。

以评分表格为例:

使用视图类编写,此表格包含:

  • 总共 3 个视图类,其中 2 个需要自定义绘制圆角矩形和星形
  • 约 350 行 Java 代码,55 行 XML

使用 Compose 编写,此表格包含:

  • 所有的 @Composable 函数都包含在同一文件和语言中!
  • 约 210 行 Kotlin 代码

动画

动画因其简单、富有表现力而成为 Compose 备受赞誉的一项功能。我们的团队正在使用 Compose 构建动效功能,极大地提高了 Play 商店用户的满意度。借助 Compose 的声明性和动画 API,编写连续或并行动画从未如此简单。我们的团队不再担心关于动画取消和回调链的所有极端情况。Lottie 是一个流行的动画库,已经提供了易于使用的 Compose API。

△ 动画成为 Compose 备受赞誉的一项功能

现在您可能会想: 这一切听起来都很棒,但提供视图的库依赖项呢?确实,并非所有的库开发者都实现了基于 Compose 的 API,尤其是在我们首次迁移时。但是,Compose 通过其 ComposeView 和 AndroidView API 提供了 简单的视图互操作性。我们以这种方式成功地与 ExoPlayerYouTube Player 等流行库集成。

性能

Play 商店和 Jetpack Compose 团队密切合作,以确保 Compose 可以像视图框架一样快速运行并且没有卡顿。由于需要把 Compose 打包在应用中 (而不是作为 Android 框架的一部分),这是一项艰巨的任务。在屏幕上渲染单个界面组件很快,但是将整个 Compose 框架加载到应用内存中所用的端到端时间却很长。

Play 商店采用 Compose 后最大的性能改进之一来自 基准配置文件 的开发。虽然已经推出了一段时间的 云配置文件 可以帮助改善应用启动时间,但是它们只适用于 API 28+,且对于更新节奏频繁 (每周) 的应用效果不佳。为了解决这一问题,Play 商店和 Android 团队合作开发了基准配置文件 (Baseline Profiles): 开发者预定义打包好的、应用可以指定的一个配置文件,它们随您的应用提供,与云配置文件完全兼容,并且可以在具体应用级别和库级别进行定义 (适配 Compose 的开发者可免费使用此功能!)。通过推出基准配置文件,Play 商店发现其搜索结果页的 初始页面渲染时间减少了 40%。这是巨大的进步!

重复使用界面组件 是使 Compose 在渲染方面表现出色的 核心机制,尤其是在滚动情况下。Compose 会尽可能跳过已知可以跳过的可组合项的重组 (例如,它们是不可变的),但是如果所有参数满足 @Stable 注释要求,开发者也可以强制将可组合项设置为可跳过。Compose 编译器还提供了一份 便捷指南,说明防止特定函数被跳过的原理。当在 Play 商店中创建在滚动情况下频繁使用的大量重复使用界面组件时,我们发现不必要的重组会增加丢失的帧时间,从而导致卡顿。我们建立了一个 修饰符 (Modifier),以便在我们的调试设置中轻松发现这些重组。通过将这些技术应用于我们的界面组件,我们能够将卡顿减少 10-15%

△ 实际操作中的重组可视化修饰符 (Modifiers)蓝色 (无重组),绿色 (1 次重组)

△ 实际操作中的重组可视化修饰符 (Modifiers)蓝色 (无重组),绿色 (1 次重组)

为 Play 商店应用优化 Compose 的另一个关键是 为整个应用制定详细的端到端的迁移策略。在最初的集成实验中,我们遇到了双栈问题: 在单个用户会话中同时运行 Compose 和视图类渲染非常占用内存,尤其是在低端设备上。当代码在同一页面上运行时就会出现这种情况,当两个不同的页面 (例如,Play 商店主页和搜索结果页) 各自位于不同的堆栈上时,也会出现这种情况。为了改善这种启动延迟,我们 为页面迁移到 Compose 的顺序和时间安排 制定一个具体计划,这是非常重要的。同时我们发现,在应用迁移到完全使用 Compose 进行渲染使用之前,对一些通用类进行一定的 “预热” 是有助于提高内存性能的。

将 Compose 从 Android 框架中分离出来减少了我们团队直接为 Jetpack Compose 做出贡献的开销,从而缩短了改进工作的周转时间,使所有开发者受益。我们与 Jetpack Compose 团队合作,推出 LazyList 项目类型缓存 等功能,并快速进行轻量级修复,如 额外的对象分配

展望未来

Play 商店采用 Compose 后,提升了我们团队开发者的幸福感,并 大大提高了代码质量和健康度。所有的全新 Play 商店功能都建立在此框架之上,且 Compose 有助于为应用实现更快的速度和更顺畅的访问。由于我们 Compose 迁移策略的性质,我们无法准确衡量 APK 大小 变化或构建速度等,但是我们看到的所有迹象都非常积极!

Compose 是 Android 界面开发的未来,也帮助 Play 商店实现了进一步的优化。欢迎您持续关注 我们了解最新内容。

1 个赞