在可滾動的容器中播放視頻其實是有一定難度的——對設(shè)備資源的壓力可能會導致視頻丟幀,這會使?jié)L動效果變的很差。此外,我們不希望用戶等待視頻加載,我們也不希望它在播放中碰到緩沖,因此視頻播放器需要快速啟動并流暢運行??v觀市場上的各種設(shè)備,這些問題在 Android 上的挑戰(zhàn)還是很大的。
我們最近完成了 Android 平臺的新聞提要技術(shù)遷移,由我們的開源 UI 渲染框架 Litho 提供技術(shù)支持。Litho 支持異步布局和細粒度的回收利用,這不僅有助于優(yōu)化新聞提要使其更有效地渲染內(nèi)容,而且可以使我們的代碼更健壯、更易于擴展。我們希望將這些改進帶到視頻功能上來,以改善 Facebook 用戶的播放體驗,同時也為設(shè)計新用例的工程師們提供幫助。
構(gòu)建一個視頻 UI 元素比我們所做的其他 UI 組件更具挑戰(zhàn)性。視頻組件使用了 Litho 的一些其他組件不需要的高級特性,同時也促使 Litho 創(chuàng)建了一些新的特性。新的 Litho 視頻組件引入了一些改進,從新聞提要的滾動性能到更靈活的設(shè)計,這些設(shè)計可以很容易地在不同的視頻功能中重用。這篇文章描述了我們在 Android 應(yīng)用程序中重寫視頻播放器時所面臨的挑戰(zhàn),以及 Litho 如何幫助克服這些困難的。
視頻新聞
新聞提要支持多種視頻新聞類型。有些類型的行為和外觀與其他的有所不同。視頻附件是一種常見的新聞類型,其中有常規(guī)視頻、Facebook Live 視頻、360 度視頻、gif 或其他類型的視頻,它們會附在一個普通的文章上。
其他的視頻新聞類型可以播放生成的視頻,贊助商的信息,或者短動畫。
想要支持所有這些變種會導致代碼難以維護和測試。我們提供視頻附件使用的主視頻視圖類叫 VideoAttachmentView。對于一些新聞類型,我們必須擴展這個視圖,以便重用和定制它來適應(yīng)設(shè)計的需要。在某些情況下,我們不能在派生類中進行所有的更改,只能創(chuàng)建一個單獨的視圖類,這意味著需要將通用邏輯移到幫助類中。這進一步使代碼復雜化了。
新聞提要的滾動性能也會受到影響。RecyclerView 僅從相同類的類型中回收視圖,這意味著不能為常規(guī)視頻附件新聞類型回收像 SponsoredVideoAttachmentView 這樣的視圖,盡管 SponsoredVideoAttachmentView 繼承至 VideoAttachmentView。由于這種低效率的原因,RecyclerView 需要分配更多的視圖來容納不同的新聞類型,從而導致了更多的內(nèi)存占用。另外,在視頻視圖展現(xiàn)在屏幕上之前,分配行為就要被觸發(fā),這增加了它無法及時完成的可能性,甚至可能會導致丟幀。即使在一個新的布局中重用相同的 VideoAttachmentView 來優(yōu)化它的表現(xiàn),我們也需要創(chuàng)建一個新的視圖類型。因為我們另外添加了一層,這也使得視圖層次結(jié)構(gòu)變的更深了。視圖層次越深,渲染的時間就越長。
設(shè)計視頻組件
Litho 用一種叫做“組件”的單位來定義 UI。它支持兩種主要類型的組件:MountSpec 和 LayoutSpec。MountSpec 定義了一個 UI 構(gòu)建塊,例如一個文本或圖像組件。LayoutSpec 定義一個包含一個或多個組件的布局。在 Litho 中,使用組件結(jié)構(gòu)來構(gòu)造更復雜的組件是很常見的做法。LayoutSpec 可以定義一個包含其他 LayoutSpec 和 MountSpec 組件的布局,MountSpec 可以呈現(xiàn)一個特定的視圖或繪制視圖。
視頻組件是一個 UI 構(gòu)建塊,這意味著我們需要一個 MountSpec 來定義它。一種方法是創(chuàng)建一個 MountSpec 來呈現(xiàn) VideoAttachmentView,但我們還需要為其它擴展視圖創(chuàng)建另一個 MountSpec, 如 SponsoredVideoAttachmentView。雖然這做也是可行的,但最終我們還會遇到相同的重用性和可維護性的問題。另一種方法是創(chuàng)建一個 MountSpec 只呈現(xiàn)簡單的視頻視圖而不是 VideoAttachmentView,并將其添加到一個特定的新聞類型的 LayoutSpec 中。下圖演示了新組件之間的關(guān)系:
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 中。這個組件將一個視頻附件數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換為一個通用的視頻組件都能理解的屬性。
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 相比,這個設(shè)計提供了更多的靈活性。這些組件中的任何一個都可以添加到另一個 LayoutSpec 中,創(chuàng)建一個更復雜的組件并擴展它的功能或 UI 設(shè)計。Litho 鼓勵使用嵌套組件,以及組件組合,以構(gòu)建更強大的功能。Litho 以最優(yōu)的渲染性能優(yōu)化了布局樹,構(gòu)建出了扁平的視圖結(jié)構(gòu)。
下面是一個創(chuàng)建視頻附件組件的示例,該組件顯示底部的水印:
@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。
性能改進
除了支持更加靈活的設(shè)計之外,Litho 還提供了一些屬性和特性,幫助我們優(yōu)化新聞提要中的視頻播放和整個應(yīng)用的整體性能。
資源回收利用
Android 內(nèi)置的 RecyclerView 可以基于視圖的類型將其保存在不同的緩存池中,這對于創(chuàng)建了很多不同類型視圖的用戶界面來說可能會是一個問題。
相比之下,Litho 的回收系統(tǒng)復用了更小的用戶界面構(gòu)建模塊,比如文本或圖片,而不是整個視圖。通過使用一個核心視頻組件,同樣的視圖可以被循環(huán)使用于所有的視頻新聞類型。更有效的回收利用減少了對象的分配,進而提高了滾動性能。
預分配
新聞提要的第一個視頻新聞不能循環(huán)使用預先存在的視頻視圖,因為之前沒有視圖。當兩個視頻新聞同時出現(xiàn)在屏幕上時也需要注意:一個視頻視圖可以從以前的新聞中回收,但是第二個視圖需要新建。當 RecyclerView 需要分配一個新的視圖對象,特別是像視頻視圖那樣的復雜視圖時,會帶來丟幀的風險。我們希望優(yōu)化這種情況,因此我們在 Litho 中創(chuàng)建了預分配功能。
通過向 MountSpec 注解中添加一些屬性,我們可以讓 Litho 提前創(chuàng)建一些實例。當滾動瀏覽新聞提要中的第一個視頻新聞時,預分配的視頻視圖可以極大地提高滾動性能。
@MountSpec(poolSize = 3, canPreallocate = true) public class CoreVideoComponentSpec { ... }生命周期
MountSpec 有一些實用且簡單的生命周期回調(diào)方法。這些足以讓我們將大部分視頻播放邏輯封裝在組件中。在 Litho 之前,這個邏輯會被分散到不同的類中,由一個單獨的控制器觸發(fā)。視頻組件中的主要回調(diào)方法包括:
onPrepare- 開始預取視頻。在視頻組件出現(xiàn)之前,在后臺線程上觸發(fā)。
onMount- 初始化視頻播放器。組件首次配置其視圖屬性時觸發(fā)。
onUnmount- 清除視頻播放器,為下一次使用做準備。當視頻滾動走時被觸發(fā)。
LayoutSpec 有一個主要的回調(diào):onCreateLayout()。它的主要目的是構(gòu)造 LayoutSpec 的布局,但是它也可以為其子組件準備資源。例如,封面照片 LayoutSpec 可以在上面創(chuàng)建一個帶有視頻和封面照片的布局,同時還可以觸發(fā)封面照片的預抓取,所有這些都是在同一個回調(diào)方法中進行的。
MountSpec 還支持另一個實用的回調(diào):shouldUpdate()。當 RecyclerView 的適配器被更新時,它可以重新綁定所有的子視圖,并獲得所有可見的組件并重新加載 (觸發(fā) onUnmount 和 onMount)。對于簡單的組件,這沒有明顯的影響,但是重新配置一個視頻播放器就會是一個比較繁重的操作。這個回調(diào)是在 Litho 重新加載組件之前調(diào)用的,如果你覺得它沒有必要的話 (例如,加載相同的視頻),我們可以選擇跳過它。
@ShouldUpdate(onMount = true) static boolean shouldUpdate(@Prop Diff可測試性videoParamsDiff) { return videoParamsDiff.getNext().videoId != videoParamsDiff.getPrevious().videoId; }
新組件的模塊化有助于更輕松地測試視頻播放邏輯。Litho 提供了一個測試框架,可以在單元測試中模擬組件的生命周期。通過將更多的視頻播放邏輯封裝在這些組件中,我們可以測試和驗證我們以前無法使用的復雜場景。此外,通過使用組合而不是繼承來擴展功能,會更安全、更容易維護。
結(jié) 論
通過使用傳統(tǒng)的 Android 視圖,Litho 幫助我們通過一次構(gòu)建就可以提升視頻實現(xiàn)的性能、效率、可擴展性和可維護性。該視頻組件在整個 Facebook 的 Android 應(yīng)用程序中提升了 20% 的滾動性能,同時也改善了我們的冷啟動時間,使我們的內(nèi)存崩潰率降低了 2.5%,這都是更有效的內(nèi)存管理的結(jié)果。代碼的改進不僅改善了 Facebook 的體驗,還讓工程師有了更多在 Facebook 應(yīng)用中創(chuàng)建新的視頻體驗的余地。
Litho 允許我們在 Android 上編寫高度優(yōu)化的 UI。Litho 于 2017 年 4 月開源,此后一直在成長。在去年年底,我們啟動了 Sections 項目,它是基于 Litho 構(gòu)建的用于編寫高度優(yōu)化的列表界面的 API。轉(zhuǎn)換到 Litho 和 Sections 后,通常會有大幅度的性能提升,比如滾動性能提升可能高達 42%。使用 Litho 很簡單并且文檔豐富??梢圆榭?Litho 網(wǎng)站和 GitHub 頁面上的代碼示例、教程和高級指南。
-
Android
+關(guān)注
關(guān)注
12文章
3939瀏覽量
127641
原文標題:Facebook構(gòu)建高性能Android視頻組件實踐之路
文章出處:【微信號:livevideostack,微信公眾號:LiveVideoStack】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論