天天看點

Components for Android: 一個高效的聲明式UI架構

編輯推薦:稀土掘金,這是一個針對技術開發者的一個應用,你可以在掘金上擷取最新最優質的技術幹貨,不僅僅是Android知識、前端、後端以至于産品和設計都有涉獵,想成為全棧工程師的朋友不要錯過!

英文原文:Components for Android: A declarative framework for efficient UIs 。

滾動界面是移動端最常見的模式了。如果你做過app,那麼你很可能使用過RecyclerView來實作滾動清單。

在安卓上建立清單界面是相當簡單的:隻需建立item的布局,把它挂載到RecyclerView 的adapter上就完成了。雖然大多數app要比這稍微複雜點。

如果你要為清單添加無限滾動,你就需要考慮記憶體使用的問題了。如果你adapter的 view type比較多,你需要考慮如何高效的回收視圖。如果清單的item很複雜,你還很可能需要優化布局以避免滾動時丢幀。

在Facebook,我們要處理RecyclerView在各種資訊流中的極端使用情況。拿新聞資訊流來說,它的item非常複雜,有各種各樣的内容包括分享,富文本,視訊,廣告,圖檔等等。而且每天都有許多來自不同産品團隊的安卓工程師在做新聞資訊流的工作。

Components for Android: 一個高效的聲明式UI架構

這就帶來了一些有趣的技術挑戰。如何才能在内容如此複雜,團隊合作人數如此衆多的情況下做到讓RecyclerView具有流暢的滾動性能呢?如何才能在一個内容變化近乎無限的資訊流中提供一個高效的記憶體回收模式呢?

在這種規模之下,依靠一個團隊為自己的産品提出一次性的解決方案是不可持續的。我們需要一個封裝了實作這些資訊流複雜度的可擴充的公共架構,這樣産品團隊就能專注于為社群輸出驚歎的功能。

我們從React和ComponentKit得到靈感建立了一個強大的架構。它可以讓安卓開發者隻需通過一個簡單的聲明式API就能實作複雜且高性能的RecyclerView。我們把它叫做Components for Android (C4A)。

C4A采用了單向資料流的響應式程式設計模型。你隻需利用給定的不可變輸入聲明UI的不同狀态然後架構就會為你做剩餘的事情。

也許最好的描述方式是代碼:

@LayoutSpec
 public class HeaderComponentSpec {
      
   @OnCreateLayout
   static ComponentLayout onCreateLayout(
     ComponentContext c,
     @Prop Uri imageUri,
     @Prop(resType = STRING) String title,
     @Prop(resType = STRING, optional = true) String subtitle) {
      
     return Container.create(c)
         .direction(ROW)
         .alignItems(CENTER)
         .backgroundRes(R.drawable.header_bg)
         .paddingDip(ALL, 16)
         .child(
             FrescoImage.create(c)
                 .uri(imageUri)
                 .placeholderRes(R.drawable.avatar_placeholder)
                 .withLayout()
                 .widthRes(R.dimen.avatar_size)
                 .heightRes(R.dimen.avatar_size)
                 .marginDip(END, 16))
         .child(
             Container.create(c)
                 .direction(COLUMN)
                 .flexGrow(1)
                 .child(
                     Text.create(c)
                         .text(title)
                         .textColor(Color.DKGRAY)
                         .textSizeSp(16)
                         .textColorRes(R.color.user_name))
                 .child(
                     Text.create(c, R.style.SubtitleText)
                         .text(subtitle)
                         .withLayout()
                         .marginRes(TOP, R.dimen.subtitle_margin)))
         .build();
      }
    }
           

HeaderComponentSpec我們稱之為component spec。它隻是一個具有特殊注解的Java類。在編譯時,注解處理器将生成一個HeaderComponent類以及一個builder,builder帶有比對了component spec中使用的變量的方法。你可以這樣使用HeaderComponent:

HeaderComponent.create(c)
    .imageUri(Uri.parse("http://example.com/image"))
    .titleRes(R.string.title)
    .subtitle("And this is my subtitle.")
    .build();
           

C4A使用Flexbox,一個web上廣泛存在的強大的布局系統,由我們開源跨平台的實作css-layout支援。HeaderComponent在螢幕上的效果如下:

Components for Android: 一個高效的聲明式UI架構

HeaderComponentSpec用起來感覺非常簡單,隻需處理純函數。你隻要傳入一些參數然後就傳回一個布局樹。這和安卓UI上狀态化,指令式的代碼形成鮮明的對比。

在我們探索這個framework的特性之前,讓我們先快速浏覽一遍它是如何在螢幕上顯示控件的。

在C4A中,布局和渲染是分為兩個獨立的步驟實作的:布局(layout)和裝載(mount)。

Components for Android: 一個高效的聲明式UI架構

布局這一步和安卓視圖系統是完全無關的,因為C4A有自己的布局系統(css-layout)。架構的使用者隻需建立一個布局樹( @OnCreateLayout方法),其餘的事情都由架構來處理。布局的結果是一個控件清單以及它們各自的大小和位置。

然後布局的結果被用到裝載這一步,以建立實際的view結構,一旦控件可見就立即渲染到螢幕上。

我們利用這個架構去實作一些激動人心的功能:異步布局,平鋪視圖結構,逐漸裝載以及精細的回收。

異步布局

Component是帶不可變輸入的純函數是以它們在設計上是線程安全的。架構不受安卓視圖系統限制,是以可以在背景線程執行布局,免受複雜的多線程的困擾。

比如, C4A可以提前計算好RecyclerView中item的布局,而不要阻塞UI線程。等到使用者能看到item的時候,絕大多數繁重的工作都已經完成了。

平鋪視圖結構

 @OnCreateLayout布局傳回的布局結構隻是一個UI的輪廓,和安卓視圖沒有直接的關系。這允許架構在component被裝載之前對布局樹做性能優化。我們通過兩種方法來做這件事情。

首先,C4A在完成布局的計算之後可以完全跳過容器,因為它們對于裝載步驟無關緊要。在前面的例子中,當HeaderComponent被裝載的時候,不會出現ViewGroup包裹标題和副标題的情況。

其次,component可以裝載一個View或者一個drawable。實際上這個架構的絕大多數核心部件都是裝載drawables的,而不是View,比如Text 和 Image。

經過這些優化之後,HeaderComponent就可以作為一個單獨的、完全扁平的視圖被渲染。你可以從下面啟用了開發者選項的Show layout bound的截圖中看出來。

Components for Android: 一個高效的聲明式UI架構

雖然平鋪視圖對于記憶體使用和繪制時間都有很大益處,但是它們并不是銀彈。當我們想學習安卓視圖中的重要功能比如觸摸事件處理,沖突限制時,C4A具有一個非常通用的系統可以自動讓mounted components的視圖結構“去扁平化”。比如,如果HeaderComponent的标題文字設定了點選事件處理,架構會自動把它包裹在一個View中。

逐漸裝載

因為RecyclerView中布局提前完成,我們就知道每個控件在顯示之前的尺寸與邊界。控件利用這點,一旦它們在螢幕上就可見逐漸裝載各自的内部内容。

Components for Android: 一個高效的聲明式UI架構

Incremental mount distributes the work of rendering components transparently across multiple frames making them less likely to cause jank while scrolling. This is especially relevant forRecyclerViews with complex items that would be very challenging to implement efficiently without a lot of custom optimizations.

精細的回收

RecyclerView提供了一種基于View type概念的回收機制,不同類型的item從不同的池子(pool)回收。絕大多數情況下這都能很好的工作,但是對于view類型非常多的清單就不理想了,因為RecyclerView需要不斷的為每種類型inflating新的view,導緻記憶體過度開銷和滾動性能問題。

在C4A中,所有葉子控件如Text和Image在底層都是單獨回收的。這可以讓我們為RecyclerView帶來更精細的回收方案。一旦一個組合控件的内部部分移出螢幕,該架構立即讓它們可以被adapter中的其它item複用。

Components for Android: 一個高效的聲明式UI架構

This means C4A dynamically cross-pollinates things like text and images across all composite items in a RecyclerView and doesn't require the use of multiple view types.

更好的性能

C4A讓開發者可以在背景提前執行布局,渲染更平鋪的視圖結構,使用更精細的回收。所有這些都通過更簡單的程式設計模式獲得。

我們從使用了C4A的安卓app中看到了滾動性能的提升。現在Facebook Lite(我們為新興市場做的app)的最新版本在Jelly Bean及以上的裝置上就是完全基于Components。即便在低端手機上,滾動性能也獲得了很大的提升。

同時,我們也正在把Facebook for Android應用轉換成使用Components,并且把傳統安卓視圖轉換成新架構之後也看到了類似的性能提升。

原文位址:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/1031/6721.html

Good luck!

Reprinted by Jimmy.li

繼續閱讀