AI 年度盘点与2025发展趋势展望,50+案例解析亮相AICon 了解详情
写点什么

Facebook 构建高性能 Android 视频组件实践之路

  • 2018-02-28
  • 本文字数:4797 字

    阅读完需:约 16 分钟

随着移动设备视频消费的快速增长,为了更高效地呈现内容,Facebook 的移动工程师们面临着新的挑战。比起文本和图片这样的简单 UI 元素,视频需要更多的资源。视频解码器会消耗很多 CPU 资源,创建视频时还需要分配大量内存,同时他们会使用更多的网络带宽来从服务器下载视频数据。在可滚动的容器中播放视频其实是有一定难度的——对设备资源的压力可能会导致视频丢帧,这会使滚动效果变的很差。此外,我们不希望用户等待视频加载,我们也不希望它在播放中碰到缓冲,因此视频播放器需要快速启动并流畅运行。纵观市场上的各种设备,这些问题在 Android 上的挑战还是很大的。

我们最近完成了 Android 平台的新闻提要技术迁移,由我们的开源UI 渲染框架 Litho 提供技术支持。Litho 支持异步布局和细粒度的回收利用,这不仅有助于优化新闻提要使其更有效地渲染内容,而且可以使我们的代码更健壮、更易于扩展。我们希望将这些改进带到视频功能上来,以改善 Facebook 用户的播放体验,同时也为设计新用例的工程师们提供帮助。

构建一个视频 UI 元素比我们所做的其他 UI 组件更具挑战性。视频组件使用了 Litho 的一些其他组件不需要的高级特性,同时也促使 Litho 创建了一些新的特性。新的 Litho 视频组件引入了一些改进,从新闻提要的滚动性能到更灵活的设计,这些设计可以很容易地在不同的视频功能中重用。这篇文章描述了我们在 Android 应用程序中重写视频播放器时所面临的挑战,以及 Litho 如何帮助克服这些困难的。

视频新闻

新闻提要支持多种视频新闻类型。有些类型的行为和外观与其他的有所不同。视频附件是一种常见的新闻类型,其中有常规视频、Facebook Live 视频、360 度视频、gif 或其他类型的视频,它们会附在一个普通的文章上。

其他的视频新闻类型可以播放生成的视频,赞助商的信息,或者短动画。

想要支持所有这些变种会导致代码难以维护和测试。我们提供视频附件使用的主视频视图类叫VideoAttachmentView。对于一些新闻类型,我们必须扩展这个视图,以便重用和定制它来适应设计的需要。在某些情况下,我们不能在派生类中进行所有的更改,只能创建一个单独的视图类,这意味着需要将通用逻辑移到帮助类中。这进一步使代码复杂化了。

新闻提要的滚动性能也会受到影响。RecyclerView 仅从相同类的类型中回收视图,这意味着不能为常规视频附件新闻类型回收像SponsoredVideoAttachmentView 这样的视图,尽管SponsoredVideoAttachmentView 继承至VideoAttachmentView。由于这种低效率的原因,RecyclerView 需要分配更多的视图来容纳不同的新闻类型,从而导致了更多的内存占用。另外,在视频视图展现在屏幕上之前,分配行为就要被触发,这增加了它无法及时完成的可能性,甚至可能会导致丢帧。即使在一个新的布局中重用相同的VideoAttachmentView 来优化它的表现,我们也需要创建一个新的视图类型。因为我们另外添加了一层,这也使得视图层次结构变的更深了。视图层次越深,渲染的时间就越长。

设计视频组件

Litho 用一种叫做“组件”的单位来定义 UI。它支持两种主要类型的组件:MountSpec 和 LayoutSpec。 MountSpec 定义了一个 UI 构建块,例如一个文本或图像组件。 LayoutSpec 定义一个包含一个或多个组件的布局。在 Litho 中,使用组件结构来构造更复杂的组件是很常见的做法。LayoutSpec 可以定义一个包含其他 LayoutSpec 和 MountSpec 组件的布局,MountSpec 可以呈现一个特定的视图或绘制视图。

视频组件是一个 UI 构建块,这意味着我们需要一个 MountSpec 来定义它。一种方法是创建一个 MountSpec 来呈现 VideoAttachmentView,但我们还需要为其它扩展视图创建另一个 MountSpec, 如 SponsoredVideoAttachmentView。虽然这做也是可行的,但最终我们还会遇到相同的重用性和可维护性的问题。另一种方法是创建一个 MountSpec 只呈现简单的视频视图而不是 VideoAttachmentView,并将其添加到一个特定的新闻类型的 LayoutSpec 中。下图演示了新组件之间的关系:

CoreVideoComponent 是一个有着最简特性的任何视频新闻都需要的 MountSpec。

复制代码
@MountSpec
public class CoreVideoComponentSpec {
@OnCreateMountContent
static SimpleVideoView onCreateMountContent(ComponentContext context) {
return new SimpleVideoView(context);
}
@OnPrepare
static void onPrepare(
ComponentContext context,
@Prop VideoParams videoParams) {
prefetchVideo(videoParams);
}
@OnMount
static void onMount(
ComponentContext context,
SimpleVideoView videoView,
@Prop VideoParams videoParams) {
initVideoPlayback(videoView, videoParams);
}
@OnUnmount
static void onUnmount(
ComponentContext context,
SimpleVideoView videoView,
@Prop VideoParams videoParams) {
cleanupVideoPlayback(videoView, videoParams);
}
...
}

CoreVideoComponent 是 AutoplayVideoComponent 的子类,该组件是一个用于在新闻提要中注册视频的 LayoutSpec。所有新闻提要中的视频都是在自动播放管理器上注册的,但并不是所有的视频都需要自动播放功能 (例如,全屏视频播放器中的视频)。

复制代码
@LayoutSpec
public class AutoplayVideoComponentSpec {
@OnCreateLayout
static Component onCreateLayout(
ComponentContext c,
@Prop VideoParams videoParams) {
registerVideoForAutoplay(videoParams);
return CoreVideoComponent.create(c)
.videoParams(videoParams)
.build();
}
...
}

最后, 我们将自动播放组件作为子类添加到 VideoAttachmentComponent 中。这个组件将一个视频附件数据结构转换为一个通用的视频组件都能理解的属性。

复制代码
@LayoutSpec
public class VideoAttachmentComponentSpec {
@OnCreateLayout
static Component onCreateLayout(
ComponentContext c,
@Prop VideoAttachmentInfo videoAttachmentInfo) {
final VideoParams videoParams = createVideoParams(videoAttachmentInfo);
return AutoplayVideoComponent.create(c)
.videoParams(videoParams)
.build();
}
...
}

与 VideoAttachmentView 相比,这个设计提供了更多的灵活性。这些组件中的任何一个都可以添加到另一个 LayoutSpec 中,创建一个更复杂的组件并扩展它的功能或 UI 设计。Litho 鼓励使用嵌套组件,以及组件组合,以构建更强大的功能。Litho 以最优的渲染性能优化了布局树,构建出了扁平的视图结构。

下面是一个创建视频附件组件的示例,该组件显示底部的水印:

复制代码
@LayoutSpec
public class WatermarkVideoAttachmentComponentSpec {
@OnCreateLayout
static Component onCreateLayout(
ComponentContext c,
@Prop VideoAttachmentInfo videoAttachmentInfo) {
return Column.create(c)
.flexShrink(0)
.alignContent(YogaAlign.FLEX_START)
.child(
VideoAttachmentComponent.create(c)
.videoAttachmentInfo(videoAttachmentInfo))
.child(
Text.create(c)
.text("Powered by Litho")
.textColorRes(android.R.color.holo_green_light)
.textSizeDip(25)
.paddingDip(YogaEdge.LEFT, 5)
.positionType(YogaPositionType.ABSOLUTE)
.positionDip(YogaEdge.BOTTOM, 0)
.positionDip(YogaEdge.START, 0))
.build();
}
}

新组件通过将其添加为子组件来重新使用视频附件组件的所有代码和 UI。

性能改进

除了支持更加灵活的设计之外,Litho 还提供了一些属性和特性,帮助我们优化新闻提要中的视频播放和整个应用的整体性能。

资源回收利用

Android 内置的 RecyclerView 可以基于视图的类型将其保存在不同的缓存池中,这对于创建了很多不同类型视图的用户界面来说可能会是一个问题。

相比之下,Litho 的回收系统复用了更小的用户界面构建模块,比如文本或图片,而不是整个视图。通过使用一个核心视频组件,同样的视图可以被循环使用于所有的视频新闻类型。更有效的回收利用减少了对象的分配,进而提高了滚动性能。

预分配

新闻提要的第一个视频新闻不能循环使用预先存在的视频视图,因为之前没有视图。当两个视频新闻同时出现在屏幕上时也需要注意:一个视频视图可以从以前的新闻中回收,但是第二个视图需要新建。当RecyclerView 需要分配一个新的视图对象,特别是像视频视图那样的复杂视图时,会带来丢帧的风险。我们希望优化这种情况,因此我们在Litho 中创建了预分配功能

通过向MountSpec 注解中添加一些属性,我们可以让Litho 提前创建一些实例。当滚动浏览新闻提要中的第一个视频新闻时,预分配的视频视图可以极大地提高滚动性能。

复制代码
@MountSpec(poolSize = 3, canPreallocate = true)
public class CoreVideoComponentSpec {
...
}

生命周期

MountSpec 有一些实用且简单的生命周期回调方法。这些足以让我们将大部分视频播放逻辑封装在组件中。在 Litho 之前,这个逻辑会被分散到不同的类中,由一个单独的控制器触发。视频组件中的主要回调方法包括:

  • onPrepare- 开始预取视频。在视频组件出现之前,在后台线程上触发。
  • onMount- 初始化视频播放器。组件首次配置其视图属性时触发。
  • onUnmount- 清除视频播放器,为下一次使用做准备。当视频滚动走时被触发。

LayoutSpec 有一个主要的回调: onCreateLayout() 。它的主要目的是构造 LayoutSpec 的布局,但是它也可以为其子组件准备资源。例如,封面照片 LayoutSpec 可以在上面创建一个带有视频和封面照片的布局,同时还可以触发封面照片的预抓取,所有这些都是在同一个回调方法中进行的。

MountSpec 还支持另一个实用的回调: shouldUpdate() 。当 RecyclerView 的适配器被更新时,它可以重新绑定所有的子视图,并获得所有可见的组件并重新加载 (触发 onUnmount 和 onMount)。对于简单的组件,这没有明显的影响,但是重新配置一个视频播放器就会是一个比较繁重的操作。这个回调是在 Litho 重新加载组件之前调用的,如果你觉得它没有必要的话 (例如,加载相同的视频),我们可以选择跳过它。

复制代码
@ShouldUpdate(onMount = true)
static boolean shouldUpdate(@Prop Diff<VideoParams> videoParamsDiff) {
return videoParamsDiff.getNext().videoId != videoParamsDiff.getPrevious().videoId;
}

可测试性

新组件的模块化有助于更轻松地测试视频播放逻辑。Litho 提供了一个测试框架,可以在单元测试中模拟组件的生命周期。通过将更多的视频播放逻辑封装在这些组件中,我们可以测试和验证我们以前无法使用的复杂场景。此外,通过使用组合而不是继承来扩展功能,会更安全、更容易维护。

结论

通过使用传统的Android 视图,Litho 帮助我们通过一次构建就可以提升视频实现的性能、效率、可扩展性和可维护性。该视频组件在整个Facebook 的Android 应用程序中提升了20% 的滚动性能,同时也改善了我们的冷启动时间,使我们的内存崩溃率降低了2.5%,这都是更有效的内存管理的结果。代码的改进不仅改善了Facebook 的体验,还让工程师有了更多在Facebook 应用中创建新的视频体验的余地。

Litho 允许我们在 Android 上编写高度优化的 UI。Litho 于 2017 年 4 月开源,此后一直在成长。在去年年底,我们启动了 Sections 项目,它是基于 Litho 构建的用于编写高度优化的列表界面的 API。转换到 Litho 和 Sections 后,通常会有大幅度的性能提升,比如滚动性能提升可能高达42% 。使用Litho 很简单并且文档丰富。可以查看Litho网站 GitHub 页面上的代码示例、教程和高级指南。

英文原文链接: Improving Android video on News Feed with Litho

感谢冬雨对本文的审校。

2018-02-28 16:473116

评论

发布
暂无评论
发现更多内容

恒源云(Gpushare)_炼丹萌新指南,这次错不了!

恒源云

深度学习 GPU算力 算法训练

2022年4月中国数据库排行榜:华为GaussDB 挺进前四,榜单前八得分扶摇直上

墨天轮

数据库 国产数据库 达梦 人大金仓 gbase8a

测试权限

石子头

SVGIcon 组件的构建与使用

全象云低代码

前端 低代码 SVG 低代码平台 图标库

数据挖掘:针对小样本与不均衡样本的机器学习算法实践

鲸品堂

数据挖掘

腾讯二面:Linux操作系统里一个进程最多可以创建多少个线程?

Java全栈架构师

Linux 程序员 架构 面试 操作系统

TiDB源码系列之沉浸式编译TiDB

TiDB 社区干货传送门

ironSource 发行解决方案 Supersonic 两周年,游戏全球下载量突破 20 亿

Geek_2d6073

多方安全计算升级数据治理技术体系需考虑数据源合规性等

易观分析

多方安全计算

开源分布式图数据库的思考和实践

NebulaGraph

图数据库 知识图谱

科创中国开源创新榜单发布,EMQX 获评“年度优秀开源产品”

EMQ映云科技

开源 物联网 IoT emq emqx

百度工程师教你快速提升研发效率小技巧

百度Geek说

前端

ArduBee|开源技术背后的创新

科技热闻

使用ORM与原始SQL的性能对比

杨彦星

Python MySQL sanic

如何成为更好的AI专业人员?请查收这7条实战经验

Baihai IDP

人工智能 算法 数据科学

四川数字经济发展分析:四川21市州数字经济发展活跃度解密

易观分析

数字化转型 数字化经济

hash,bloomfilter,分布式一致性hash

Linux服务器开发

分布式 hash 后端开发 Linux服务器开发 C++后台开发

搭建一个可视化看板,仅需4步

阿里云云效

云计算 阿里云 看板 研发团队 可视化看板

VNC中文是什么意思?全称是什么?

行云管家

运维 服务器 vnc

百度程序员开发避坑指南(3)

百度Geek说

前端

STI即将登录Gate.io,我们有哪些期待?

小哈区块

借品牌升级之际,谈一谈技术开发者为什么选择 InfoQ 写作社区

宇宙之一粟

4月月更 InfoQ写作社区2周年

百度程序员开发避坑指南(移动端篇)

百度Geek说

移动端

详解离线数仓和实时数仓的区别

五分钟学大数据

4月月更

公司产品手册的编写方法

小炮

企业 产品宣传手册

Pulsar Summit Asia 2021|Pulsar在移动云智能运维平台的实践

移动云大数据

pulsar

【技术加油站】浅谈百度智能测试的三个阶段

百度Geek说

测试

netty系列之:netty中的frame解码器

程序那些事

Netty 程序那些事 java 4月月更

问题来了!拔掉网线几秒,再插回去,原本的 TCP 连接还存在吗?

Java全栈架构师

程序员 架构 面试 计算机网络 底层知识

多个私有云设施管理用什么云管理软件好?

行云管家

云计算 私有云 云管理 多有云

【分享汇总】AIoT开源科技节暨OpenHarmony技术论坛(附链接)

OpenHarmony开发者

OpenHarmony AIoT开源科技节

Facebook构建高性能Android视频组件实践之路_Meta_Udi Cohen_InfoQ精选文章