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

时间:2022-05-07
本文章向大家介绍Facebook构建高性能Android视频组件实践之路,主要内容包括性能改进、预分配、生命周期、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

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

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

@MountSpecpublic 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。所有新闻提要中的视频都是在自动播放管理器上注册的,但并不是所有的视频都需要自动播放功能(例如,全屏视频播放器中的视频)。

@LayoutSpecpublic class AutoplayVideoComponentSpec {
      
  @OnCreateLayout  static Component onCreateLayout(
      ComponentContext c,
      @Prop VideoParams videoParams) {
    registerVideoForAutoplay(videoParams);
      
    return CoreVideoComponent.create(c)
        .videoParams(videoParams)
        .build();
  }
  ...}

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

@LayoutSpecpublic 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以最优的渲染性能优化了布局树,构建出了扁平的视图结构。

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

@LayoutSpecpublic 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;}