說一說 Facebook 開源的 Litho

Facebook老是能給業界帶來一些驚喜,最近開源的Litho是一個高效構建Android UI的聲名式框架(declarative framework for building efficient UIs on Android)。Litho的出現能夠追溯到Facebook去年的一篇博文Components for Android: A declarative framework for efficient UIs,中文譯文:Components for Android: 一個高效的聲明式UI框架javascript

Litho最初的目的是爲了解決複雜列表的高效渲染和內存使用問題。以前我也寫過相關的文章Android ListView中複雜數據流的高效渲染Android複雜數據流的「高效」渲染。以前的思路是把列表中的邏輯Item拆分爲可複用的更小單元,而後利用ListView或者RecyclerView自帶的緩存策略達到節約內存的目的。Litho採用了更激進的方式,放棄使用原生的View,使用了自定義的View和佈局,經過極高的View複用率節約了內存使用,同時採用了很是高效的佈局策略,使得繪製更加迅速,滑動更加流暢。Litho的使用對於複雜數據流展現優化能夠說是顛覆式的,很是佩服他們的思路和實現。固然我的認爲Litho的目的不只僅是解決上述問題,做爲一個UI渲染框架徹底能夠代替目前Android中的渲染實現。可是就目前Litho的狀況來看,離徹底替代還有很長的距離,以後我會說明本身的想法。css

Litho 概述

先來看下官方上對於Litho高效渲染的介紹,主要介紹了4個特徵:html

  • 聲名式組件
    Litho採用聲名式的Api來定義UI組件,咱們只須要基於一組不可變輸入( immutable inputs)描述UI的佈局,剩下的事情就能夠交給Litho了。
    聲名式佈局讓咱們用一種描述式的方式構建組件:前端

    @LayoutSpec
    public class FeedItemComponentSpec {
    
    @OnCreateLayout
    static ComponentLayout onCreateLayout(
        ComponentContext c,
        @Prop final Artist artist,
        @Prop final RecyclerBinder binder) {
      return Column.create(c)
          .child(
              Column.create(c)
                  .child(artist.images.length == 1 ?
                      SingleImageComponent.create(c)
                          .image(artist.images[0])
                          .aspectRatio(2)
                          .withLayout() :
                      Recycler.create(c)
                          .binder(binder)
                          .withLayout().flexShrink(0)
                          .aspectRatio(2))
                  .child(
                      TitleComponent.create(c)
                          .title(artist.name))
                  .child(
                      ActionsComponent.create(c)))
          .child(
              FooterComponent.create(c)
                  .text(artist.biography))
          .build();
    }
    }複製代碼

    看代碼很是簡單易懂,並且Litho使用Flexbox 對組件進行佈局,有前端經驗的同窗知道Flexbox佈局很是的方便。Litho提供的Image使用了fresco,也很是棒。java

  • 異步佈局
    Litho能夠異步進行measure和layout,不須要在UI線程中。android

  • 扁平化的View
    Litho 使用了Yoga 來進行佈局,能夠減小UI中繪製ViewGroup的數量。
    在Android中,爲了不界面錯亂,全部的UI繪製和操做都是在UI線程中,對於比較複雜的界面,繪製過程過長就會引發界面卡頓,掉幀,以前的優化基本都是經過減小布局層級、避免過分繪製等手段進行優化。Litho使用異步佈局就避免了在UI線程中執行繁重的measure和layout過程。Litho使用Yoga能夠進一步優化佈局,咱們在生命式的UI佈局中只是指定了佈局的樣子,並非實際的佈局,Litho能夠進一步優化,咱們知道展現UI可使用View或者更加輕量級的Drawable,Litho能夠根據須要裝載View或者Drawable,相比Android原生的佈局,Litho使用了更多的drawable,這會讓試圖渲染更快速。如圖:
    git


    當咱們使用開發者工具中的顯示佈局時,能夠看到圖中的全部元素是渲染在一個View上的。

  • 細粒度的複用
    全部組件包括text和image等能夠被回收並在UI的全部位置進行復用。
    Litho組件的全局複用,能夠極大地提升內存使用率,在展現複雜列表時,內存使用會有明顯的區別。github

看完Litho的四個特徵,相信每一個Android開發者都是很是驚喜的。chrome

Litho的思路

本文不會深刻到Litho的代碼細節,主要介紹本身對於Litho的分析與想法。緩存

1. 組件化

這裏所說的組件化不是工程上的組件化,而是佈局上的組件化。Litho的靈感應該是來源於React,以組件的方式組織布局。

傳統的Android使用xml進行佈局,名義上是mvc中的view,可是在功能上很是弱,幾乎沒有邏輯處理,以後推出的data binding使得功能上稍有增強,可是功能依然比較弱。固然不能否認這種界面佈局與邏輯代碼分離的設計思路也是很是棒的。在傳統開發中,把界面佈局和邏輯分離是最合理的方案,可是有些時候也稍顯笨重。litho的設計思路是放棄了xml佈局,而是使用java代碼來構建界面組件並進行佈局,使用組件的方式鏈接了邏輯和界面佈局,與React在前端上的設計有相同的思路。Litho包含兩種組件:

Mount spec: 能夠獨立渲染一個view或者drawable,擁有本身的生命週期
Layout spec:能夠組織其餘組件構成一個佈局,相似於Android中的ViewGroup。

使用litho後每個界面都是組件化的,合理設計組件,能夠增長組件的複用性,同時組件自己props、state的設計是的自身功能比較完整,比傳統意義上的xml中定義佈局要強大不少。

2. 扁平化與事件處理

咱們知道,Android中的View不止能夠展現,還能夠與用戶進行交互,如點擊、滑動等等。Litho使用yoga佈局,能夠節約內存佔用和繪製時間,可是這種狀況下不能與用戶進行交互了。Litho單獨對Event進行處理,能夠處理點擊、長按、碰觸(touch)事件,與View元素對事件處理略有不一樣,但能夠知足基本的需求。

關於Litho的一些想法

1. 關於界面調試

Android開發中咱們在xml中定義佈局,Android studio有強大的預覽功能,所見即所得的體驗很棒。Litho提供了對於Stetho 對支持,能夠利用chrome的開發者工具對界面進行調試:

其實相比xml,這種方式並不方便,在chrome只是輔助調試,最終仍是根據調試狀況手動在代碼中更新。

2. 開發體驗

在寫界面時,咱們要合理地對界面進行拆分,使用多個組件組合成爲一個完整對界面。一個組件定義以下:

@LayoutSpec
public class FeedItemComponentSpec {

  @OnCreateLayout
  static ComponentLayout onCreateLayout(
      ComponentContext c,
      @Prop final Artist artist,
      @Prop final RecyclerBinder binder) {
    return Column.create(c)
        .child(
            Column.create(c)
                .child(artist.images.length == 1 ?
                    SingleImageComponent.create(c)
                        .image(artist.images[0])
                        .aspectRatio(2)
                        .withLayout() :
                    Recycler.create(c)
                        .binder(binder)
                        .withLayout().flexShrink(0)
                        .aspectRatio(2))
                .child(
                    TitleComponent.create(c)
                        .title(artist.name))
                .child(
                    ActionsComponent.create(c)))
        .child(
            FooterComponent.create(c)
                .text(artist.biography))
        .build();
  }
}複製代碼

例子中咱們定義了一個組件,可是咱們在邏輯代碼中並不會引用到這段代碼。Litho會根據componentSpec生的生成真正的component代碼:

public final class FeedItemComponent extends ComponentLifecycle {
  private static FeedItemComponent sInstance = null;

  private static final Pools.SynchronizedPool<Builder> mBuilderPool = new Pools.SynchronizedPool<Builder>(2);

  private FeedItemComponentSpec mSpec = new FeedItemComponentSpec();

  private FeedItemComponent() {
  }

  public static synchronized FeedItemComponent get() {
    if (sInstance == null) {
      sInstance = new FeedItemComponent();
    }
    return sInstance;
  }

  @Override
  protected ComponentLayout onCreateLayout(ComponentContext c, Component _abstractImpl) {
    FeedItemComponentImpl _impl = (FeedItemComponentImpl) _abstractImpl;
    ComponentLayout _result = (ComponentLayout) mSpec.onCreateLayout(
      (ComponentContext) c,
      (Artist) _impl.artist,
      (RecyclerBinder) _impl.binder);
    return _result;
  }

  private static Builder newBuilder(ComponentContext context, int defStyleAttr, int defStyleRes,
      FeedItemComponentImpl feedItemComponentImpl) {
    Builder builder = mBuilderPool.acquire();
    if (builder == null) {
      builder = new Builder();
    }
    builder.init(context, defStyleAttr, defStyleRes, feedItemComponentImpl);
    return builder;
  }

  public static Builder create(ComponentContext context, int defStyleAttr, int defStyleRes) {
    return newBuilder(context, defStyleAttr, defStyleRes, new FeedItemComponentImpl());
  }

  public static Builder create(ComponentContext context) {
    return create(context, 0, 0);
  }

  private static class FeedItemComponentImpl extends Component<FeedItemComponent> implements Cloneable {
    @Prop
    Artist artist;

    @Prop
    RecyclerBinder binder;

    private FeedItemComponentImpl() {
      super(get());
    }

    @Override
    public String getSimpleName() {
      return "FeedItemComponent";
    }

    @Override
    public boolean equals(Object other) {
      if (this == other) {
        return true;
      }
      if (other == null || getClass() != other.getClass()) {
        return false;
      }
      FeedItemComponentImpl feedItemComponentImpl = (FeedItemComponentImpl) other;
      if (this.getId() == feedItemComponentImpl.getId()) {
        return true;
      }
      if (artist != null ? !artist.equals(feedItemComponentImpl.artist) : feedItemComponentImpl.artist != null) {
        return false;
      }
      if (binder != null ? !binder.equals(feedItemComponentImpl.binder) : feedItemComponentImpl.binder != null) {
        return false;
      }
      return true;
    }
  }

  public static class Builder extends Component.Builder<FeedItemComponent> {
    private static final String[] REQUIRED_PROPS_NAMES = new String[] {"artist", "binder"};

    private static final int REQUIRED_PROPS_COUNT = 2;

    FeedItemComponentImpl mFeedItemComponentImpl;

    ComponentContext mContext;

    private BitSet mRequired = new BitSet(REQUIRED_PROPS_COUNT);

    private void init(ComponentContext context, int defStyleAttr, int defStyleRes,
        FeedItemComponentImpl feedItemComponentImpl) {
      super.init(context, defStyleAttr, defStyleRes, feedItemComponentImpl);
      mFeedItemComponentImpl = feedItemComponentImpl;
      mContext = context;
      mRequired.clear();
    }

    public Builder artist(Artist artist) {
      this.mFeedItemComponentImpl.artist = artist;
      mRequired.set(0);
      return this;
    }

    public Builder binder(RecyclerBinder binder) {
      this.mFeedItemComponentImpl.binder = binder;
      mRequired.set(1);
      return this;
    }

    public Builder key(String key) {
      super.setKey(key);
      return this;
    }

    @Override
    public Component<FeedItemComponent> build() {
      if (mRequired != null && mRequired.nextClearBit(0) < REQUIRED_PROPS_COUNT) {
        List<String> missingProps = new ArrayList<String>();
        for (int i = 0; i < REQUIRED_PROPS_COUNT; i++) {
          if (!mRequired.get(i)) {
            missingProps.add(REQUIRED_PROPS_NAMES[i]);
          }
        }
        throw new IllegalStateException("The following props are not marked as optional and were not supplied: " + Arrays.toString(missingProps.toArray()));
      }
      FeedItemComponentImpl feedItemComponentImpl = mFeedItemComponentImpl;
      release();
      return feedItemComponentImpl;
    }

    @Override
    protected void release() {
      super.release();
      mFeedItemComponentImpl = null;
      mContext = null;
      mBuilderPool.release(this);
    }
  }
}複製代碼

因此有個弊端是咱們每次修改一個component文件都須要build一次生成可用的代碼。對於開發來講體驗並不友好。

另外咱們能夠看下Litho提供的可用組件:

因此若是徹底使用Litho來開發一款應用,須要本身實現的控件會很是多。我的認爲雖然Litho有諸多好處,對於通常的應用來說,常規的優化手段已經徹底能夠知足需求。Litho仍是更適用於對性能優化有強烈需求的應用。

3. Litho組件化的思考

Litho使用了相似React的設計思路,而React社區很是的活躍。若是Litho的將來發展的比較良好,能夠支撐常規應用開發時,React社區的不少經驗就能夠借鑑過來,如Redux等工具的實現等。

最後

對於Litho的使用仍是一個比較初級的體驗,文中若有錯誤的地方,煩請指出,很是感謝。

推薦閱讀:

Android 組件化開發原理和配置

Android組件化開發實踐

相關文章
相關標籤/搜索