一、Litho简介
Litho是Facebook推出的一套高效构建Android UI的框架,主要目的是提升RecycleView复杂列表的滑动性能和内存占用。官网原文介绍如下:
当我们在使用recycleview 或者listview来承载一些列表页的时候,如果遇到页面中有很多类型不同的item或者item的布局比较复杂,会遇到滑动过程帧率骤降的过程(需要重新inflate 对应的xml来生成对应的控件,并进行装载),并且虽然listview或者recycleview已经提供了复用的机制,但是否能够高效回收还取决于每个开发者的功力。因此,Litho应运而生。
使用
1、依赖导入
按照自己的相关诉求进行导入,前面几个是必选的,另外的看使用情况。
// Litho implementation 'com.facebook.litho:litho-core:0.32.0' implementation 'com.facebook.litho:litho-widget:0.32.0' annotationProcessor 'com.facebook.litho:litho-processor:0.32.0' // SoLoader implementation 'com.facebook.soloader:soloader:0.5.1' // For integration with Fresco implementation 'com.facebook.litho:litho-fresco:0.32.0' // For testing testImplementation 'com.facebook.litho:litho-testing:0.32.0' // Sections implementation 'com.facebook.litho:litho-sections-core:0.32.0' implementation 'com.facebook.litho:litho-sections-widget:0.32.0' compileOnly 'com.facebook.litho:litho-sections-annotations:0.32.0' annotationProcessor 'com.facebook.litho:litho-sections-processor:0.32.0'
2、第一个例子
final ComponentContext context = new ComponentContext(this); final Component component = Text.create(context) .text("Hello World") .textSizeDip(50) .textColor(Color.RED) .build(); LithoView lithoView = LithoView.create(context, component);
通过上面的代码,就可以创建一个TextView并显示在Activity上面。
3、列表开发
Litho底层也是使用了RecycleView,在上层做了一层封装,如果需要使用到litho的来显示列表数据,可以使用下面的方式,先定义一个Group,然后再实现自己的Item对象。
@GroupSectionSpec public class ListSectionSpec {
@OnCreateChildren static Children onCreateChildren(final SectionContext c) {
Children.Builder builder = Children.create(); for (int i = 0; i < 32; i++) {
builder.child( SingleComponentSection.create(c) .key(String.valueOf(i)) .component(ListItem.create(c).build())); } return builder.build(); } } @LayoutSpec public class ListItemSpec {
@OnCreateLayout static Component onCreateLayout(ComponentContext c) {
return Column.create(c) .paddingDip(ALL, 16) .backgroundColor(Color.WHITE) .child(Text.create(c).text("Hello world").textSizeSp(40)) .child(Text.create(c).text("Litho tutorial").textSizeSp(20)) .build(); } }
三、特性
1、异步布局
Litho可以提前异步测量和布局UI,而不是阻塞主线程。按照常规的实现,Android上对于页面所有的View操作,包括
measure、layout、draw的操作都是在UI线程执行的,这样子主要是为了保证页面不会因为多线程出现错乱。如果我们的页面比较复杂,那么measure、layout的时间就会很长,导致页面滑动的时候出现卡顿。Litho提供了异步布局的方案来优化解决这种问题。对于不同线程操作可能会存在的错乱问题,Litho是使用Immutability(不变性)来解决的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QYTnsHBg-51)(media/340.jpg)]
实现原理
- 为什么会出现错乱?
在了解异步的实现之前,我们先来看看,为啥会出现错乱的问题?
public class SomeExampleClass {
private int mCounter; public String getThisOrThat() {
if (mCounter > 10) {
return "this"; } else {
mCounter++; return "that"; } } }
如上,如果有多个线程要在SomeExampleClass的同一实例上调用getThisOrThat,等到第二个线程调用getThisOrThat,尝试读取mCounter时,第一个线程可能正在执行mCounter ++,我们将无法确定第二个线程实际从mCounter读取的值是什么。
因此通常存在的问题是,代码中存在一个可变状态(mCounter),并且有多个线程试图写入和读取它。当编写尝试在多个线程上分配工作的应用程序时,就会出现最常见的竞争问题。
这个就是为啥在传统实现上,多个线程操作UI相关的逻辑会很复杂,因为Android的view 是有状态和多变的,例如TextView的为文案设置提供的setText方法,如果要在其他线程也调用这个方法来改变文案,那么意味着,必须解决用户在布局计算发生时从另一个线程调用setText并更改当前文本的问题(谁先谁后)。
- 如何实现异步?
看回上面的问题,之所以会出现多线程问题,是因为TextView的状态是可变的,有没有另外一种写法,可以实现等价的功能,并且是不依赖于这种可变状态的呢?如果一个对象在创建之后,就不再可变了,是否是就可以解决这种问题?
public static class Result {
public final int mCounter; public final String mValue; public Result(int counter, int value) {
mCounter = counter; mValue = value; } } public class SomeExampleClass {
public static Result getThisOrThat(int counterValue) {
if (counterValue > 10) {
return new Result(counterValue, "this"); } else {
return new Result(counterValue + 1, "that"); } } }
如上,同样是对应 getThisOrThat,无论是哪个线程进行调用,只要输入正确,就能返回对应的结果。Litho在布局的时候就是使用这种方式来实现的。每一个Component都是一个不可变的对象,以@Prop和@State相关的数据来作为布局功能的所有输入。
这也说明了为啥litho要求 @Prop 和 @State是不可变的,要不然,也就没有了布局仅是纯功能的属性了。
可以看到,这种实现方式会带来额外的对象分配(内存占用),这种额外的占用是必须的,但Litho 通过使用池和代码生成来最小化额外的对象内存占用。
Litho 提供了 setRoot 和 setRootSync 两个方法来实现同步和异步的效果。异步的实现,会使用Litho的layout线程
来进行布局计算,异步也就意味着布局结果不会立马得出,因此这种方式只在 RecyclerBinder上使用。
- 源码分析
final ComponentContext context = new ComponentContext(this); final Component component = Text.create(context) .text("Hello World") .textSizeDip(50) .textColor(Color.RED) .build(); LithoView lithoView = LithoView.create(context, component);
以创建一个最简单的Hello World为例,相关时序图如下:
在创建view显示的过程中,最终会调用到LayoutState做测量和布局的操作,并以一个LayoutOunt的对象进行输出。ComponentTree里面通过方法了getDefaultLayoutThreadLooper,获得排版线程的Looper(有单线程模型,也有多线程模型),最终会post 一个CalculateLayoutRunnable进行布局和测量的相关工作。相关的输出和状态会存放在LayoutState。当需要绘制的时候,拿出里面的LayoutOutput进行draw 展示。
ps:如果还是不能理解,可以按照上面的流程,下对应的断点,整个流程debug 看一次,就能够理解了。
2、视图扁平化
Litho使用yoga进行布局,并且会自动减少UI包含的ViewGroup数量,去除不必要的结构,进行UI拍平。
- 如何实现?

譬如想要实现上面的效果,Android 上一般的实现,需要增加一个FrameLayout作为容器,左边是一个ImageView,右边是一个LinearLayout包含两个TextView(当然,有更高优的实现,这里只是说很多人脑海浮现的实现方式)。对应的布局边界如下:
降级处理
上面的例子,是以一张图的形式来展示我们想要的布局,但是,如果这个样式的某块区域需要进行事件响应的时候(点击、长按或者触摸),就没法处理为一个Drawable了,需要还是以View控件的形式挂载到LithoView(ViewGroup)上。也就是如果有点击事件绑定,需要降级到更Android 普通的实现一样的view。
3、更细粒度的回收机制
对于长列表的内容展示,我们一般都是使用
ListView或者RecycleView来进行展示。因为ListView或者RecycleView都提供了回收机制,让我们在滚动过程中可以重用已经创建出来的view,而不是重新创建、测量和布局,能够减少UI线程的工作量,达到一个流畅的效果。这种在我们的列表比较简单(都是同种类型的item)的情况下是没有问题的。但如果对于复杂的场景,譬如,每次滚动,要展示的类型都不一样,这势必会为UI线程带来更大的工作量。因此,除了上面提供的异步排版能够减少这种新建UI类型带来的损耗之外,Litho 提供了一个更加可扩展和更高效率的回收机制。
因为在litho中,布局的表示和即将用在屏幕上渲染的视图是完全分离的,这就意味着,可以以更小的维度来定义每个item。譬如每个简单的TEXT、Image,都可以是单独的一个item,这种子,回收的粒度就细化到每个小控件维度了,而不是我们通用的一大快。
实现原理
- 如何实现?
复用原理如下:每个item,在会受到的时候,会进行解绑,拆分成每个绘制单元,由Litho的缓存进行分类回收和复用。
4、声明式
Litho使用声明式API来定义UI组件,使用者只需要基于一组不可变的输入来描述UI布局,其他的都交由框架来处理即可。框架层使用了注解和代码生成的方式来创建你声明的组件。
参考链接
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/200487.html原文链接:https://javaforall.net
