Litho是Facebook推出的一套高效构建Android UI的声明式框架,主要目的是提高RecyclerView复杂列表的滑动性能和下降内存占用。下面是Litho官网的介绍:php
Litho is a declarative framework for building efficient user interfaces (UI) on Android. It allows you to write highly-optimized Android views through a simple functional API based on Java annotations. It was primarily built to implement complex scrollable UIs based on RecyclerView. With Litho, you build your UI in terms of components instead of interacting directly with traditional Android views. A component is essentially a function that takes immutable inputs, called props, and returns a component hierarchy describing your user interface.java
Litho是高效构建Android UI的声明式框架,经过注解API建立高优的Android视图,很是适用于基于Recyclerview的复杂滚动列表。Litho使用一系列组件构建视图,代替了Android传统视图交互方式。组件本质上是一个函数,它接受名为Props的不可变输入,并返回描述用户界面的组件层次结构。react
Litho是一套彻底不一样于传统Android的UI框架,它继承了Facebook一贯大胆创新的风格,突破性地在Android上实现了React风格的UI框架。架构图以下:android
应用层:上层Android应用接入层。git
规范层(API):容许用户使用声明式的API(注解)来构建符合Flexbox规范的布局。github
布局层:Litho使用可挂载组件、布局组件和Flexbox组件来构建布局,其中可挂载组件和布局组件容许用户使用规范来定义,各个组件的具体用法下面的组件规范中会详细介绍。在Litho中每个组件都是一个独立的功能模块。Litho的组件和React的组件相相似,也具备属性和状态的概念,经过状态的变动来控制组件的展现样式。缓存
布局测量:Litho使用Yoga来完成组件布局的异步或同步(可根据场景定制)测量和计算,实现了布局的扁平化。性能优化
布局渲染:Litho不只支持使用View来渲染视图,还可使用更轻量的Drawable来渲染视图。Litho实现了大量使用Drawable来渲染的基础组件,能够进一步拍平布局。架构
除了上面提到的扁平化布局,Litho还实现了布局的细粒度复用和异步计算布局的能力,对于这些功能的实如今Litho的特性及原理剖析中详细介绍。下面先介绍一下你们比较关心的Litho使用方法。框架
Litho的使用方式相比于传统的Android来讲有些另类,它抛弃了经过XML定义布局的方式,采用声明式的组件在Java中构建布局。
Android传统布局:首先在资源文件res/layout目录下定义布局文件xx.xml,而后在Activity或Fragment中引用布局文件生成视图,示例以下:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World" android:textAlignment="center" android:textColor="#666666" android:textSize="40dp" />
复制代码
public class MainActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.helloworld);
}
}
复制代码
Litho布局:Litho抛弃了Android原生的布局方式,经过组件方式构建布局生成视图,示例以下:
public class MainActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ComponentContext context = new ComponentContext(this);
final Text.Builder builder = Text.create(context);
final Component = builder.text("Hello World")
.textSizeDip(40)
.textColor(Color.parseColor("#666666"))
.textAlignment(Layout.Alignment.ALIGN_CENTER)
.build();
LithoView view = LithoView.create(context, component);
setContentView(view);
}
}
复制代码
Litho中的视图单元叫作Component,能够直观的翻译为“组件”,它的设计理念来自于React组件化的思想。每一个组件持有描述一个视图单元所必须的属性和状态,用于视图布局的计算工做。视图最终的绘制工做是由组件指定的绘制单元(View或者Drawable)来完成的。
Litho组件的建立方式也和原生View的建立方式有着很大的区别。Litho使用注解定义了一系列的规范,咱们须要使用Litho的注解来定义本身的组件生成规则,最终由Litho在编译期自动编译生成真正的组件。
2.2.1 组件规范
Litho提供了两种类型的组件规范,分别是Layout Spec规范和Mount Spec规范。下面分别介绍两种规范的使用方式:
Layout Spec规范:用于生成布局类型组件的规范,布局组件在逻辑上等同于Android中的ViewGroup,用于组织其余组件构成一个布局。它要求咱们必须使用@LayoutSpec注解来注明,并实现一个标注了@OnCreateLayout注解的方法。示例以下:
@LayoutSpec
class HelloComponentSpec {
@OnCreateLayout
static Component onCreateLayout(ComponentContext c, @Prop String name) {
return Column.create(c)
.child(Text.create(c)
.text("Hello, " + name)
.textSizeRes(R.dimen.my_text_size)
.textColor(Color.BLACK)
.paddingDip(ALL, 10)
.build())
.child(Image.create(c)
.drawableRes(R.drawable.welcome)
.scaleType(ImageView.ScaleType.CENTER_CROP)
.build())
.build();
}
}
复制代码
最终Litho会在编译时生成一个名为HelloComponent的组件。
public final class HelloComponent extends Component {
@Prop(resType = ResType.NONE,optional = false) String name;
private HelloComponent() {
super();
}
@Override
protected Component onCreateLayout(ComponentContext c) {
return (Component) HelloComponentSpec.onCreateLayout((ComponentContext) c, (String) name);
}
...
public static Builder create(ComponentContext context, int defStyleAttr, int defStyleRes) {
Builder builder = sBuilderPool.acquire();
if (builder == null) {
builder = new Builder();
}
HelloComponent instance = new HelloComponent();
builder.init(context, defStyleAttr, defStyleRes, instance);
return builder;
}
public static class Builder extends Component.Builder<Builder> {
private static final String[] REQUIRED_PROPS_NAMES = new String[] {"name"};
private static final int REQUIRED_PROPS_COUNT = 1;
HelloComponent mHelloComponent;
...
public Builder name(String name) {
this.mHelloComponent.name = name;
mRequired.set(0);
return this;
}
@Override
public HelloComponent build() {
checkArgs(REQUIRED_PROPS_COUNT, mRequired, REQUIRED_PROPS_NAMES);
HelloComponent helloComponentRef = mHelloComponent;
release();
return helloComponentRef;
}
}
}
复制代码
Mount Spec规范:用来生成可挂载类型组件的规范,用来生成渲染具体View或者Drawable的组件。一样,它必须使用@MountSpec注解来标注,并至少实现一个标注了@onCreateMountContent的方法。Mount Spec相比于Layout Spec更复杂一些,它拥有本身的生命周期:
除了上述两种组件类型,Litho中还有一种特殊的组件——Layout,它不能使用规范来生成。Layout是Litho中的容器组件,相似于Android中的ViewGroup,可是只能使用Flexbox的规范。它能够包含子组件节点,是Litho各组件链接的纽带。Layout组件只是Yoga在Litho中的代理,组件的全部布局相关的属性都会直接设置给Yoga,并由Yoga完成布局的计算。Litho实现了两个Layout组件Row和Column,分别对应Flexbox中的行和列。
2.2.2 Litho的属性
在Litho中属性分为两种,不可变属性称为Props,可变属性称为State,下面分别介绍一下两种属性:
Props属性:组件中使用@Prop注解标注的参数集合,具备单向性和不可变性。下面经过一个简单的例子了解一下如何在组件中定义和使用Props属性:
@MountSpec
class MyComponentSpec {
@OnPrepare
static void onPrepare( ComponentContext c, @Prop(optional = true) String prop1) {
...
}
@OnMount
static void onMount( ComponentContext c, SomeDrawable convertDrawable, @Prop(optional = true) String prop1, @Prop int prop2) {
if (prop1 != null) {
...
}
}
}
复制代码
在上面的代码中,共使用了三次Prop注解,分别标注prop1和prop2两个变量,即定义了prop1和prop2两个属性。Litho会在自动编译生成的MyComponent类的Builder类中生成这两个属性的同名方法。按照以下代码,即可以去使用上面定义的属性:
MyComponent.create(c)
.prop1("My prop 1")
.prop2(256)
.build();
复制代码
State属性:意为“状态”属性,State属性虽然可变,可是其变化由组件内部控制,例如:输入框、Checkbox等都是由组件内部去感知用户行为,并更新组件的State属性。因此一个组件一旦建立,咱们便没法经过任何外部设置去更改它的属性。组件的State属性虽然不容许像Props属性那样去显式设置,可是咱们能够定义一个单独的Props属性来当作某个State属性的初始值。
Litho官网首页经过4个段落重点介绍了Litho的4个特性。
Litho采用声明式的API来定义UI组件,组件经过一组不可变的属性来描述UI。这种组件化的思想灵感来源于React,关于声明式组件的用法上面已经详细介绍过了。
传统Android布局由于UI与逻辑分离,因此开发工具都有强大的预览功能,方便开发者调整布局。而Litho采用React组件化的思想,经过组件链接了逻辑与布局UI,虽然Litho也提供了对Stetho的支持,借助于Chrome开发者工具对界面进行调试,不过使用起来并无那么方便。
Android系统在绘制时为了防止页面错乱,页面全部View的测量(Measure)、布局(Layout)以及绘制(Draw)都是在UI线程中完成的。当页面UI很是复杂、视图层级较深时,不免Measure和Layout的时间会过长,从而致使页面渲染时候丢帧出现卡顿状况。Litho为解决该问题,提出了异步布局的思想,利用CPU的闲置时间提早在异步线程中完成Measure和Layout的过程,仅在UI线程中完成绘制工做。固然,Litho只是提供了异步布局的能力,它主要使用在RecyclerView等能够提早知道下一个视图长什么样子的场景。
3.2.1 异步布局原理剖析
针对RecyclerView等滑动列表,因为能够提早知道接下来要展现的一个甚至多个条目的视图样式,因此只要提早建立好下一个或多个条目的视图,就能够提早完成视图的布局工做。
那么Android原生为何不支持异步布局呢?主要有如下两个缘由:
View的属性是可变的,只要属性发生变化就可能致使布局变化,所以须要从新计算布局,那么提早计算布局的意义就不大了。而Litho组件的属性是不可变的,因此对于一个组件来讲,它的布局计算结果是惟一且不变的。
提早异步布局就意味着要提早建立好接下来要用到的一个或者多个条目的视图,而Android原生的View做为视图单元,不只包含一个视图的全部属性,并且还负责视图的绘制工做。若是要在绘制前提早去计算布局,就须要预先去持有大量未展现的View实例,大大增长内存占用。反观Litho的组件则没有这个问题,Litho的组件只是视图属性的一个集合,仅负责计算布局,绘制工做由指定的绘制单元来完成,相比与传统的View显然Litho的组件要轻量的多。因此在Litho中,提早建立好接下来要用到的多个条目的组件,并不会带来性能问题,甚至还能够直接把组件当成滑动列表的数据源。以下图所示:
使用Litho布局,咱们能够获得一个极致扁平的视图效果。它能够减小渲染时的递归调用,加快渲染速度。
下面是同一个视图在Android和Litho实现下的视图层级效果对比。能够看到,一样的样式,使用Litho实现的布局要比使用Android原生实现的布局更加扁平。
3.3.1 扁平化视图原理剖析
Litho使用Flexbox来建立布局,最终生成带有层级结构的组件树。而后Litho对布局层级进行了两次优化。
使用了Yoga来进行布局计算,Yoga会将Flexbox的相对布局转成绝对布局。通过Yoga处理后的布局没有了原来的布局层级,变成了只有一层。虽然不能解决过分绘制的问题,可是能够有效地减小渲染时的递归调用。
前面介绍过Litho的视图渲染由绘制单元来完成,绘制单元能够是View或者更加轻量的Drawable,Litho本身实现了一系列挂载Drawable的基本视图组件。经过使用Drawable能够减小内存占用,同时相比于View,Android没法检查出Drawable的视图层级,这样可使视图效果看起来更加扁平。
原理以下图所示,Litho会先把组件树拍平成没有层级的列表,而后使用Drawable来绘制对应的视图单元。
Litho使用Drawable代替View能带来多少好处呢?Drawable和View的区别在于前者不能和用户交互,只能展现,所以Drawable不会像View那样持有不少变量和引用,因此Drawable比View从内存上看要轻量不少。举个例子:50个一样展现“Hello world”的TextView和TextDrawable在内存占比上,前者几乎是后者的8倍。对比图以下,Shallow Size表示对象自身占用的内存大小。
3.3.2 绘制单元的降级策略
因为Drawable不具备交互能力,因此对于使用Drawable没法实现的交互场景,Litho会自动降级成View。主要有如下几种场景:
对于以上场景的使用请仔细考虑,过多的使用会致使Litho的层级优化效果变差。
3.3.3 对比Android的约束布局
为了解决布局嵌套问题,Android推出了约束布局(ConstraintLayout),使用约束布局也能够达到扁平化视图的目的,那么使用Litho的好处是什么呢?
Litho能够更好地实现复杂布局。约束布局虽然能够实现扁平效果,可是它使用了大量的约束来固定视图的位置。随着布局复杂程度的增长,约束条件变得愈来愈多,可读性也变得愈来愈差。而Litho则是对Flexbox布局进行的扁平化处理,因此实际使用的仍是Flexbox布局,对于复杂的布局Flexbox布局可读性更高。
Litho中的全部组件均可以被回收,并在任何位置进行复用。这种细粒度的复用方式能够极大地提升内存使用率,尤为适用于复杂滑动列表,内存优化很是明显。
3.4.1 原生RecyclerView复用原理剖析
原生的RecyclerView视图按模板类型进行存储并复用,也就是说模板类型越多,所需存储的模板种类也就越多,致使内存占用愈来愈大。原理以下图。滑出屏幕的itemType1和itemType2都会在Recycler缓存池保存,等待后面滑进屏幕的条目的复用。
3.4.2 细粒度复用优化内存原理剖析
在Litho中,item在回收前,会把LithoView中挂载的各个绘制单元拆分出来(解绑),由Litho本身的缓存池去分类回收,在展现前由LithoView按照组件树的样式组装(挂载)各个绘制单元,这样就达到了细粒度复用的目的。原理以下图。滑出屏幕的itemType1会被拆分红一个个的视图单元。LithoView容器由Recycler缓存池回收,其余视图单元由Litho的缓存池分类回收。
使用细粒度复用的RecyclerView的缓存池再也不须要区分模板类型来缓存大量的视图模板,只须要缓存LithoView容器。细粒度回收的视图单元数量要远远小于原来缓存在各个视图模板中的视图单元数量。
美团对Litho进行了二次开发,在美团的MTFlexbox动态化实现方案(简称动态布局)中把Litho做为底层UI渲染引擎来使用。经过动态布局的预览工具,为Litho提供实时预览能力,同时能够有效发挥Litho的性能优化效果。
目前Litho+动态布局的实现方案已经应用在了美团App中,给美团App带来了不错的性能提高。后续博主会详细介绍Litho+动态布局在美团性能优化的实践方案。
因为Litho中使用了大量Drawable替换View,而且实现了视图单元的细粒度复用,所以复杂列表滑动时内存优化比较明显。美团首页内存占用随滑动页数变化走势图以下。随着一页一页地滑动,内存优化了30M以上。(数据采集自Vivo x20手机内存占用状况)
FPS的提高主要得益于Litho的异步布局能力,提早计算布局能够减小滑动时的帧率波动,因此滑动过程较平稳,不会有高低起伏的卡顿感。(数据采集自魅蓝2手机一段时间内连续fps的波动状况)
Litho相对于传统Android是颠覆式的,它采用了React的思路,使用声明式的API来编写UI。相比于传统Android,确实在性能优化上有很大的进步,可是若是彻底使用Litho开发一款应用,须要本身实现不少组件,而Litho的组件须要在编译时生成,实时预览方面也有所欠缺。相对于直接使用Litho的高成本,把Litho封装成Flexbox布局的底层渲染引擎是个不错的选择。