热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

说一说Facebook开源的Litho

Facebook总是能给业界带来一些惊喜,最近开源的Litho是一个高效构建AndroidUI的声名式框架(declarativeframeworkforbuildingeffic

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框架。

Litho最初的目的是为了解决复杂列表的高效渲染和内存使用问题。之前我也写过相关的文章Android ListView中复杂数据流的高效渲染,Android复杂数据流的“高效”渲染。之前的思路是把列表中的逻辑Item拆分为可复用的更小单元,然后利用ListView或者RecyclerView自带的缓存策略达到节约内存的目的。Litho采用了更激进的方式,放弃使用原生的View,使用了自定义的View和布局,通过极高的View复用率节约了内存使用,同时采用了非常高效的布局策略,使得绘制更加迅速,滑动更加流畅。Litho的使用对于复杂数据流展示优化可以说是颠覆式的,非常佩服他们的思路和实现。当然个人认为Litho的目的不仅仅是解决上述问题,作为一个UI渲染框架完全可以代替目前Android中的渲染实现。但是就目前Litho的情况来看,离完全替代还有很长的距离,之后我会说明自己的想法。

Litho 概述

先来看下官方上对于Litho高效渲染的介绍,主要介绍了4个特征:

  • 声名式组件
    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,也非常棒。

  • ** 异步布局**
    Litho可以异步进行measure和layout,不需要在UI线程中。

  • 扁平化的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,这会让试图渲染更快速。如图:

    《说一说Facebook开源的Litho》
    当我们使用开发者工具中的显示布局时,可以看到图中的所有元素是渲染在一个View上的。

  • 细粒度的复用
    所有组件包括text和image等可以被回收并在UI的所有位置进行复用。
    Litho组件的全局复用,可以极大地提高内存使用率,在展示复杂列表时,内存使用会有明显的区别。

看完Litho的四个特征,相信每个Android开发者都是非常惊喜的。

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的开发者工具对界面进行调试:

《说一说Facebook开源的Litho》

其实相比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 mBuilderPool = new Pools.SynchronizedPool(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 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 {
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 build() {
if (mRequired != null && mRequired.nextClearBit(0) List missingProps = new ArrayList();
for (int i = 0; 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提供的可用组件:

《说一说Facebook开源的Litho》

所以如果完全使用Litho来开发一款应用,需要自己实现的控件会非常多。个人认为虽然Litho有诸多好处,对于一般的应用来讲,常规的优化手段已经完全可以满足需求。Litho还是更适用于对性能优化有强烈需求的应用。

3. Litho组件化的思考

Litho使用了类似React的设计思路,而React社区非常的活跃。如果Litho的未来发展的比较良好,可以支撑常规应用开发时,React社区的很多经验就可以借鉴过来,如Redux等工具的实现等。

最后

对于Litho的使用还是一个比较初级的体验,文中如有错误的地方,烦请指出,非常感谢。

推荐阅读:

Android 组件化开发原理和配置

Android组件化开发实践


推荐阅读
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 本文介绍了在Mac上安装Xamarin并使用Windows上的VS开发iOS app的方法,包括所需的安装环境和软件,以及使用Xamarin.iOS进行开发的步骤。通过这种方法,即使没有Mac或者安装苹果系统,程序员们也能轻松开发iOS app。 ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • GreenDAO快速入门
    前言之前在自己做项目的时候,用到了GreenDAO数据库,其实对于数据库辅助工具库从OrmLite,到litePal再到GreenDAO,总是在不停的切换,但是没有真正去了解他们的 ... [详细]
  • 本文介绍了使用kotlin实现动画效果的方法,包括上下移动、放大缩小、旋转等功能。通过代码示例演示了如何使用ObjectAnimator和AnimatorSet来实现动画效果,并提供了实现抖动效果的代码。同时还介绍了如何使用translationY和translationX来实现上下和左右移动的效果。最后还提供了一个anim_small.xml文件的代码示例,可以用来实现放大缩小的效果。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • MyBatis错题分析解析及注意事项
    本文对MyBatis的错题进行了分析和解析,同时介绍了使用MyBatis时需要注意的一些事项,如resultMap的使用、SqlSession和SqlSessionFactory的获取方式、动态SQL中的else元素和when元素的使用、resource属性和url属性的配置方式、typeAliases的使用方法等。同时还指出了在属性名与查询字段名不一致时需要使用resultMap进行结果映射,而不能使用resultType。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 本文介绍了RxJava在Android开发中的广泛应用以及其在事件总线(Event Bus)实现中的使用方法。RxJava是一种基于观察者模式的异步java库,可以提高开发效率、降低维护成本。通过RxJava,开发者可以实现事件的异步处理和链式操作。对于已经具备RxJava基础的开发者来说,本文将详细介绍如何利用RxJava实现事件总线,并提供了使用建议。 ... [详细]
author-avatar
背着单反看世界
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有