基本功---Litho的使用及原理剖析

Layout Spec规范:用于生成布局类型组件的规范,布局组件在逻辑上等同于Android中的ViewGroup,用于组织其他组件构成一个布局。它要求我们必须使用@LayoutSpec注解来注明,并实现一个标注了@OnCreateLayout注解的方法。示例如下:

@LayoutSpec
class HelloComponentSpec {
@OnCreateLayout
static Component onCreateLayout(ComponentContext c, @Prop String name) {
return Column.create©
.child(Text.create©
.text("Hello, " + name)
.textSizeRes(R.dimen.my_text_size)
.textColor(Color.BLACK)
.paddingDip(ALL, 10)
.build())
.child(Image.create©
.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 {
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更复杂一些,它拥有自己的生命周期:

  • @OnPrepare,准备阶段,进行一些初始化操作。
  • @OnMeasure,负责布局的计算。
  • @OnBoundsDefined,在布局计算完成后挂载视图前做一些操作。
  • @OnCreateMountContent,创建需要挂载的视图。
  • @OnMount,挂载视图,完成布局相关的设置。
  • @OnBind,绑定视图,完成数据和视图的绑定。
  • @OnUnBind,解绑视图,主要用于重置视图的数据相关的属性,防止出现复用问题。
  • @OnUnmount,卸载视图,主要用于重置视图的布局相关的属性,防止出现复用问题。

image

除了上述两种组件类型,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©
.prop1(“My prop 1”)
.prop2(256)
.build();

State属性:意为“状态”属性,State属性虽然可变,但是其变化由组件内部控制,例如:输入框、Checkbox等都是由组件内部去感知用户行为,并更新组件的State属性。所以一个组件一旦创建,我们便无法通过任何外部设置去更改它的属性。组件的State属性虽然不允许像Props属性那样去显式设置,但是我们可以定义一个单独的Props属性来当做某个State属性的初始值。

3. Litho的特性及原理剖析

Litho官网首页通过4个段落重点介绍了Litho的4个特性。

3.1 声明式组件

Litho采用声明式的API来定义UI组件,组件通过一组不可变的属性来描述UI。这种组件化的思想灵感来源于React,关于声明式组件的用法上面已经详细介绍过了。

传统Android布局因为UI与逻辑分离,所以开发工具都有强大的预览功能,方便开发者调整布局。而Litho采用React组件化的思想,通过组件连接了逻辑与布局UI,虽然Litho也提供了对Stetho的支持,借助于Chrome开发者工具对界面进行调试,不过使用起来并没有那么方便。

3.2 异步布局

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中,提前创建好接下来要用到的多个条目的组件,并不会带来性能问题,甚至还可以直接把组件当成滑动列表的数据源。如下图所示:

image

3.3 扁平化的视图

使用Litho布局,我们可以得到一个极致扁平的视图效果。它可以减少渲染时的递归调用,加快渲染速度。

下面是同一个视图在Android和Litho实现下的视图层级效果对比。可以看到,同样的样式,使用Litho实现的布局要比使用Android原生实现的布局更加扁平。

image

3.3.1 扁平化视图原理剖析

Litho使用Flexbox来创建布局,最终生成带有层级结构的组件树。然后Litho对布局层级进行了两次优化。

  • 使用了Yoga来进行布局计算,Yoga会将Flexbox的相对布局转成绝对布局。经过Yoga处理后的布局没有了原来的布局层级,变成了只有一层。虽然不能解决过度绘制的问题,但是可以有效地减少渲染时的递归调用。

  • 前面介绍过Litho的视图渲染由绘制单元来完成,绘制单元可以是View或者更加轻量的Drawable,Litho自己实现了一系列挂载Drawable的基本视图组件。通过使用Drawable可以减少内存占用,同时相比于View,Android无法检查出Drawable的视图层级,这样可以使视图效果看起来更加扁平。

原理如下图所示,Litho会先把组件树拍平成没有层级的列表,然后使用Drawable来绘制对应的视图单元。

image

Litho使用Drawable代替View能带来多少好处呢?Drawable和View的区别在于前者不能和用户交互,只能展示,因此Drawable不会像View那样持有很多变量和引用,所以Drawable比View从内存上看要轻量很多。举个例子:50个同样展示“Hello world”的TextView和TextDrawable在内存占比上,前者几乎是后者的8倍。对比图如下,Shallow Size表示对象自身占用的内存大小。

image

3.3.2 绘制单元的降级策略

由于Drawable不具有交互能力,所以对于使用Drawable无法实现的交互场景,Litho会自动降级成View。主要有以下几种场景:

  • 有监听点击事件。
  • 限制子视图绘出父布局。
  • 有监听焦点变化。
  • 有设置Tag。
  • 有监听触摸事件。
  • 有光影效果。

对于以上场景的使用请仔细考虑,过多的使用会导致Litho的层级优化效果变差。

3.3.3 对比Android的约束布局

为了解决布局嵌套问题,Android推出了约束布局(ConstraintLayout),使用约束布局也可以达到扁平化视图的目的,那么使用Litho的好处是什么呢?

Litho可以更好地实现复杂布局。约束布局虽然可以实现扁平效果,但是它使用了大量的约束来固定视图的位置。随着布局复杂程度的增加,约束条件变得越来越多,可读性也变得越来越差。而Litho则是对Flexbox布局进行的扁平化处理,所以实际使用的还是Flexbox布局,对于复杂的布局Flexbox布局可读性更高。

3.4 细粒度的复用

Litho中的所有组件都可以被回收,并在任何位置进行复用。这种细粒度的复用方式可以极大地提高内存使用率,尤其适用于复杂滑动列表,内存优化非常明显。

3.4.1 原生RecyclerView复用原理剖析

原生的RecyclerView视图按模板类型进行存储并复用,也就是说模板类型越多,所需存储的模板种类也就越多,导致内存占用越来越大。原理如下图。滑出屏幕的itemType1和itemType2都会在Recycler缓存池保存,等待后面滑进屏幕的条目的复用。

image

3.4.2 细粒度复用优化内存原理剖析

在Litho中,item在回收前,会把LithoView中挂载的各个绘制单元拆分出来(解绑),由Litho自己的缓存池去分类回收,在展示前由LithoView按照组件树的样式组装(挂载)各个绘制单元,这样就达到了细粒度复用的目的。原理如下图。滑出屏幕的itemType1会被拆分成一个个的视图单元。LithoView容器由Recycler缓存池回收,其他视图单元由Litho的缓存池分类回收。

image

使用细粒度复用的RecyclerView的缓存池不再需要区分模板类型来缓存大量的视图模板,只需要缓存LithoView容器。细粒度回收的视图单元数量要远远小于原来缓存在各个视图模板中的视图单元数量。

4. 实践

美团对Litho进行了二次开发,在美团的MTFlexbox动态化实现方案(简称动态布局)中把Litho作为底层UI渲染引擎来使用。通过动态布局的预览工具,为Litho提供实时预览能力,同时可以有效发挥Litho的性能优化效果。

目前Litho+动态布局的实现方案已经应用在了美团App中,给美团App带来了不错的性能提升。后续博主会详细介绍Litho+动态布局在美团性能优化的实践方案。

使用Litho+动态布局实现的部分卡片

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

分享读者

作者2013年java转到Android开发,在小厂待过,也去过华为,OPPO等大厂待过,18年四月份进了阿里一直到现在。

被人面试过,也面试过很多人。深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长,而且极易碰到天花板技术停滞不前!

我们整理了一份阿里P7级别的Android架构师全套学习资料,特别适合有3-5年以上经验的小伙伴深入学习提升。

主要包括阿里,以及字节跳动,腾讯,华为,小米,等一线互联网公司主流架构技术。如果你有需要,尽管拿走好了。

35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

包括阿里,以及字节跳动,腾讯,华为,小米,等一线互联网公司主流架构技术。如果你有需要,尽管拿走好了。

[外链图片转存中…(img-ryqpZiNI-1712389241295)]

35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 23
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值