Litho学习--Litho 简介
背景介绍
Litho 是 FaceBook 2017年上半年开源的声明式UI渲染框架。
为什么 Facebook 要开发 Litho ?
APP中最常见的UI表现形式就是各种内容丰富的Feed流,比如各种新闻流,图片流。对于这些滚动列表,如何能够保证流畅地滑动?
为了保证页面滚动流畅,需要程序在1秒内渲染60帧的数据才能保证页面不卡顿,也就是在每16ms内把需要显示的帧画在屏幕上,否则就可能引起掉帧。
Android 系统是如何解决这个问题的?
Android 系统中提供的滚动列表就是我们常见的 RecyclerView , RecyclerView 用于动态显示大量数据集的列表,仅仅在屏幕中显示有限的几个条目,对于滑出屏幕的条目进行复用。
当某个条目第一次被显示在屏幕上时,RecyclerVeiw 会让 Adapter 新填充一个 View 显示在屏幕上。当用户开始滚动列表的时候,RecyclerView 会检查是否有 Item 滚出屏幕,滚出屏幕的 View 会被放在pool中备用。当随后需要再显示这个 View 的时候,再从 pool 中取出来,让 Adapter 用新数据对这个 View 显示的数据进行更新。
RecyclerView 在一帧内都做了什么?
当需要在屏幕上显示一个新的条目时,如果 pool 中没有,就新填充一个 View,然后更新 View 的数据,之后是 measure,layout , draw,所有这些都需要在 16ms 内完成。
那么问题来了:
- 如果列表中 Item 的类型很多,该怎么处理?
- 如果单个 Item 比较复杂,既有图片,又有视频,还有各种文字链接的情况?
对于多种数据类型,RecylerView 中采用 ViewType,每一种ViewType 会分配一个 pool 。当 ViewType 大量增加,滚动列表的时候,每一帧的数据里就可能更多地涉及到 View 的填充,View 的分配操作是个重操作,比较耗费资源,也就是说 ViewType越多,就越有可能发生掉帧。另外在内存上,那些使用频率低的 ViewType 对应的 pool ,会一直存在于内存中,内存使用效率变低。
如果 Item 本身比较复杂,想要提升性能,就要对 itemview 进行优化,比如静态的 layout 优化,减少布局层数,或者 自定义layout ,但同时又带来难以维护的问题。
Litho 是如何解决上述问题的?
1. Async Layout 异步布局
采用 Yoga 这个布局引擎 把 measure 和 layout 放到后台线程。在ReactNative中也是用的Yoga,Yoga 是一种脱离于Android 系统的高效布局引擎,实现了 FlexBox 布局规范。
异步布局的意思就是提前在后台线程渲染画面,要在16ms内进行measure,layout ,draw 三个必须又耗时的操作,Android 传统的做法是把所有这些操作放在 MainThread ,Litho 没有这个限制,Litho 使用 Yoga 渲染组件,把 measure 和 layout 放在后台线程,在新一帧到来之前提前在后台线程中计算出组件的大小和位置,除了 Yoga 之外,如何保证线程安全,Litho 这里采用了不可变的声明式 API ,使组件自然而然地保持了线程安全,使得后台渲染变得可行。
Android Threading Model Litho Threading Model 1st
除此之外,现在我们已经节省了主线程的时间,还可以进一步进行优化。如果一帧有时间空余的话,可以提前 draw 下一帧的内容。这个是用 DisplayList 实现的, DisplayList 在Android系统中用来保存 openGL commands ,提前创建和编译 Displaylists,需要显示这个组件的时候,只需要执行里面的命令就可,不需要再进行编译。(DisplayList is a group of openGL commands(已被保存,编译) for later execution)
Litho Threading Model 2nd
2. View Flattering 铺平View层次
好处:既减少了内存使用,也减少了GC的频率。
在不改变显示内容的基础上,铺平 View 结构可以减少 View 的个数
View Flatterning
a. 容器处理的优化:
Android 里面有很多容器,如果下图中的 Row 和 Column 不需要画背景的话,真正需要显示在屏幕上的组件只有一个图片加上两个文本,那么就可以把 Row 和 Column 这两个容器删掉。Litho 会去检测容器是否影响组件的绘制,如果不影响就删掉。由于使用了 Yoga,因为 Litho 允许 layout 过程中不使用 Android Views,可以提前得知在绘制组件的时候是否需要容器。
Container
b. View层面处理的优化:
如果 Row 和 Column 这两个容器消失了,那对于这个图片和两个文本要如何处理?
标准的Android系统,图片使用 ImageView,文本使用 TextView
Litho 中的实现使用 Drawables,轻量级,不会有 View 的内存和性能负担,使用 Drawables 让Litho进一步减少 View 的层数。
View
3. Incremental Recycling 增量复用
Android ReyclerView 的复用是基于 ViewType 的,假设有上百个ViewType,各种文章文本视频图片组合在一起,每个 Type 分配一个Pool,那将会有上百个 pool ,会对内存造成负担。
RecylerView 多类型复用
Litho中的细粒度复用
每个Item都是由一些核心组件构成的,文本,图片,视频等,只是不同的 Item 只是对这些核心组件进行不同的组合。不把每个 Item 的内容看成一个整体,而是把 Item 拆分成各个核心组件。
当一个 Item 滚出屏幕,就把这个 Item 分解成各个核心组件,每种核心组件放到一个 Pool ,当显示一个新的 Item 的时候,再把这些组件进行重新组合。
更进一步的优化:
因为我们不把一个 Item 当成一个整体,所以当这个 Item 滚出屏幕的时候,也不用等整个 Item 滚出屏幕的时候再回收,只需要当每一个核心组件滚出屏幕就可以进行回收。
下面这个例子,当上面 Item 中的一部分移出屏幕,头像和标题部分已经被回收了,不需要整个 Item 滚出屏幕,这些组件可以更快地回收和复用,以减少核心组件的数量。