一、Litho简介

Litho是Facebook推出的一套高效构建Android UI的框架,主要目的是提升RecycleView复杂列表的滑动性能和内存占用。官网原文介绍如下:

“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.”

当我们在使用recycleview 或者listview来承载一些列表页的时候,如果遇到页面中有很多类型不同的item或者item的布局比较复杂,会遇到滑动过程帧率骤降的过程(需要重新inflate 对应的xml来生成对应的控件,并进行装载),并且虽然listview或者recycleview已经提供了复用的机制,但是否能够高效回收还取决于每个开发者的功力。因此,Litho应运而生。

使用

1、依赖导入

按照自己的相关诉求进行导入,前面几个是必选的,另外的看使用情况。

    // Lithoimplementation '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'// SoLoaderimplementation 'com.facebook.soloader:soloader:0.5.1'// For integration with Frescoimplementation 'com.facebook.litho:litho-fresco:0.32.0'// For testingtestImplementation 'com.facebook.litho:litho-testing:0.32.0'// Sectionsimplementation '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 {@OnCreateChildrenstatic 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 {@OnCreateLayoutstatic 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操作,包括measurelayoutdraw的操作都是在UI线程执行的,这样子主要是为了保证页面不会因为多线程出现错乱。如果我们的页面比较复杂,那么measurelayout的时间就会很长,导致页面滑动的时候出现卡顿。Litho提供了异步布局的方案来优化解决这种问题。对于不同线程操作可能会存在的错乱问题,Litho是使用Immutability(不变性)来解决的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QYTnsHBg-1576508743151)(media/15763987989340.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 提供了 setRootsetRootSync 两个方法来实现同步和异步的效果。异步的实现,会使用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(当然,有更高优的实现,这里只是说很多人脑海浮现的实现方式)。对应的布局边界如下:

如果用Litho来实现呢?可能就只需一层。

为什么呢?
首先,Litho在布局计算完成之后,可以完全跳过容器层,因为在显示的过程中,并不需要用到父容器。这个第一步层级的优化,譬如上面,就可以少去包裹着两个textView 的 LinearLayout。
另外,Litho跟Android不同,他进一步支持了一个控件按照 Drawable 或者 View 来展示,我们知道,Android开发过程,一般都是用 View 来展示对应的内容,相比于Drawable,如果使用View来展示,除了会增加measurelayout这两步的时间。还会占用更大的内存。而使用Drawable的方式,会减少过渡绘制的场景,并且省去measurelayout,直接draw即可。
通过上面这两个步骤,基本上就只需要用一个没有层级的Drawable 来展示就行了。这个优化的流程如下:

降级处理

上面的例子,是以一张图的形式来展示我们想要的布局,但是,如果这个样式的某块区域需要进行事件响应的时候(点击、长按或者触摸),就没法处理为一个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://tech.meituan.com/2019/03/14/litho-use-and-principle-analysis.html
https://juejin.im/post/5922359944d904006cd0355a
https://fblitho.com/

Litho介绍和原理分析相关推荐

  1. AbstractQueuedSynchronizer的介绍和原理分析

    简介 提供了一个基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架.该同步器(以下简称同步器)利用了一个int来表示状态,期望它能够成为实现大部分同步需求的基础.使用的方法是继承,子类通过 ...

  2. arthas 运维工具介绍与原理分析

    目录 arthas是什么?他能帮我们做什么? arthas使用举例 arthas 实现这些功能的原理分析 arthas是什么?他能帮我们做什么? Arthas 是一款线上监控诊断产品,通过全局视角实时 ...

  3. 转:AbstractQueuedSynchronizer的介绍和原理分析

    引自:http://ifeve.com/introduce-abstractqueuedsynchronizer/ 简介 提供了一个基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架.该同 ...

  4. Java框架tk_TKmybatis的框架介绍和原理分析及Mybatis新特性

    tkmybatis是在mybatis框架的基础上提供了很多工具,让开发更加高效,下面来看看这个框架的基本使用,后面会对相关源码进行分析,感兴趣的同学可以看一下,挺不错的一个工具 实现对员工表的增删改查 ...

  5. ION基本概念介绍和原理分析[转]

    转载前的话: ION将内核态形形色色的内存分配纳入统一的管理接口之中,更重要的设计意图是为内存在不同用户态进程之间传递和访问提供了支持. 每个ion_buffer与一个struct file关联,其h ...

  6. ION基本概念介绍和原理分析

    转载前的话: ION将内核态形形色色的内存分配纳入统一的管理接口之中,更重要的设计意图是为内存在不同用户态进程之间传递和访问提供了支持. 每个ion_buffer与一个struct file关联,其h ...

  7. Servlet过滤器介绍之原理分析(转)

    http://zhangjunhd.blog.51cto.com/113473/20629 本文主要介绍Servlet过滤器的基本原理 author: ZJ 2007-2-21 Blog: [url] ...

  8. Select函数实现原理分析

    转载自 http://blog.chinaunix.net/uid-20643761-id-1594860.html select需要驱动程序的支持,驱动程序实现fops内的poll函数.select ...

  9. spring ioc原理分析

    spring ioc原理分析 spring ioc 的概念 简单工厂方法 spirng ioc实现原理 spring ioc的概念 ioc: 控制反转 将对象的创建由spring管理.比如,我们以前用 ...

最新文章

  1. Swing组件集合的事件处理(二)
  2. 计算机操作系统(3):操作系统的基本特征
  3. python join函数_Python join()函数
  4. txt形式进行传输WebShell图文演示!
  5. PAT1124:Raffle for Weibo Followers
  6. Android开发——联系人中几种常见的mimetype、几张常见表的Uri
  7. layout_weight
  8. plsql查询不显示结果_管理NVivo的查询结果
  9. ESP8266 MQTT
  10. 滞后问题_富锂正极材料的电压滞后问题
  11. 算法图解 PDF 原文内容分享
  12. 源码编译 Qt 6.2
  13. Unity 游戏中近战攻击判定检测——射线检测
  14. 值得一看的Spring实战 (第5版)上!!笔者强力推荐!!
  15. 勾股定理的毕达哥拉斯证明
  16. Ubuntu版本及对应的代号(4.10-22.04) 共18年整理
  17. 先有鸡还是先有蛋?先有操作系统,还是先有汇编器?(对计算机本质的探讨)
  18. Excel收纳箱:VBA一键删除当前工作表的条件格式
  19. java 游标 查数据_Java中的大量数据查询
  20. 用计算机谈狂狼,抖音狂狼原唱是谁 抖音狂狼完整版歌词

热门文章

  1. 使用方便的多功能远程桌面软件-mRemote
  2. 【JZOJ A组】海明距离
  3. ubuntu搭建android开发环境
  4. 欢迎您来到谷歌搜索在中国的新家 Google.com.hk
  5. linux安装程序是什么意思,linux – 全新安装后要安装到Windows的程序列表是什么?...
  6. Javaweb学习笔记(一)
  7. 三、【服务器】服务器入门·服务器分类
  8. 显示unc路径服务器根目录,IIS虚拟目录与UNC路径权限初探
  9. 思维导图怎么画?怎么画出简单又漂亮的思维导图
  10. Delphi之东进模拟语音卡(D160A)可复用源码(转)