写点什么

基本功 ---Litho 的使用及原理剖析,10 年阿里开发架构师经验分享

用户头像
Android架构
关注
发布于: 刚刚

android:layout_height="wrap_content"android:text="Hello World"android:textAlignment="center"android:textColor="#666666"android:textSize="40dp" />


public class MainActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.helloworld);}}


Litho 布局:Litho 抛弃了 Android 原生的布局方式,通过组件方式构建布局生成视图,示例如下:


public class MainActivity {@Overrideprotected 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);}}

2.2 Litho 自定义视图

Litho 中的视图单元叫做 Component,可以直观的翻译为“组件”,它的设计理念来自于 React 组件化的思想。每个组件持有描述一个视图单元所必须的属性和状态,用于视图布局的计算工作。视图最终的绘制工作是由组件指定的绘制单元(View 或者 Drawable)来完成的。


Litho 组件的创建方式也和原生 View 的创建方式有着很大的区别。Litho 使用注解定义了一系列的规范,我们需要使用 Litho 的注解来定义自己的组件生成规则,最终由 Litho 在编译期自动编译生成真正的组件。


2.2.1 组件规范


Litho 提供了两种类型的组件规范,分别是 Layout Spec 规范和 Mount Spec 规范。下面分别介绍两种规范的使用方式:


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


@LayoutSpecclass HelloComponentSpec {@OnCreateLayoutstatic Component onCreateLayout(ComponentContext c,


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


@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();}


@Overrideprotected 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;}


@Overridepublic 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,卸载视图,主要用于重置视图的布局相关的属性,防止出现复用问题。



除了上述两种组件类型,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 属性:


@MountSpecclass MyComponentSpec {


@OnPreparestatic void onPrepare(ComponentContext c,@Prop(optional = true) String prop1) {...}


@OnMountstatic 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 属性的初始值。

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



用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
基本功---Litho的使用及原理剖析,10年阿里开发架构师经验分享