2020年初,小红书主页 UI 的复杂度显着提高,在优化布局 xml 和运用一些 stub 方法的一起,咱们也在寻找一些本钱更低、功能更好的方法。
X2C 是其时业界熟知的一种优化方法,其原理是编译期将 xml 翻译成代码,可以有效防止反射以及读取资源文件的损耗。因为小红书 APP 中存在着很多自定义 View 的场景,X2C 一起也会带来较高的保护本钱。
经过对 LayoutInflater 耗时的深入分析,咱们找到了可以兼容各种 View 场景的 APT 计划。这一计划既防止了反射所带来的损耗,也不会增加额定的保护本钱,成为了一个开箱即用的东西。
1. 计划的探究
咱们的探究灵感来自于 ViewCompiler 。作为 Google 的一个实验性东西,ViewCompiler 可以手动地将 xml 布局转化为 java 文件或者 dex 文件,但它并不支撑 merge 和 include 标签。
ViewCompiler 在 Android Q(Android 10)的时分被引进,目前来说也仍是一个实验性质的东西,因而咱们平时并没有方法运用它。下图为Android S(Android 12)中的源码,咱们可以看到这项功能未被开启。
其原理也很简单,先生成一个模板代码片段,然后再生成遍历 xml 的逻辑代码。
这样做的主要优点是可以节省掉反射带来的时刻消耗,官方在 AppCompatViewInflater 中现已处理了原生 View 的创立,经过直接匹配称号 new 目标,防止了运用反射造成的功能开支。
在日常运用中,反射功能开支主要集中在自定义 View 这部分,咱们的 App 本身便是一个自定义 View 十分多的场景,所以天然适合这种 VIewCompiler 的这种方法。一起,因为在遍历 xml 的时分,每一个 attrs 都会遍历到,所以它在保护性上也有着巨大的优势,咱们不需求对自定义的 attrs 做任何处理。
基于对 X2C 和 ViewCompiler 的源码和生成代码的阅读,咱们决定做一个可以生成 Kotlin 代码,一起也处理 ViewCompiler 不支撑的 include 和 merge 两个标签。咱们用到的东西比较惯例,有 kapt 和 kotlinpoet,全体的思路是经过 Resources.getLayout 取到 XmlResourceParser,然后经过 parser 的不断 next 来遍历每一个 xml 中的 tag,生成的代码示意如下:
在遇到 merge 和 include 时,咱们需求特别处理递归调用的逻辑,以便可以将父子布局连在一起。
用这种新的方法替换掉主页中一些布局的实现后,咱们发现,线上主页部分 p90 的布局时刻减少了 200ms+,时长、CES、留存等目标均得到了显着提高。
2. 深入分析
LayoutInflater 的工作进程
LayoutInflater 的工作进程可以用下图来简易表明:
本文所阐述的计划便是利用 apt 在编译期间生成代码,在便利解析 layout 文件之后,咱们运用生成的代码直接创立实例,其功率与射中 AppCompat 基础组件逻辑之后的功率在理论上是一致的。
AppCompat 基础组件可以检查 AppCompatViewInflater.java 源码(上文也有部分展示),其中包含了比如 TextView、Button 等十几个常用的基础组件。
就一个详细的布局而言,可以经过 Layout2Code 的运用得以提高的功能只要除了基础组件之外的其他组件,尤其是当布局运用了大量自定义组件时,效果尤为显着。
这也给了咱们另一个提示。如在 xml 中写 TextView / TextViewCompat,在 AppCmpatViewInflater 的效果下终究创立的实例都是 TextViewCompat。但在不运用 Layout2Code 或类 X2C 计划时,它们的功率是不同的,前者射中上图的直接创立逻辑,而后者则会经过反射创立。
X2C的缺乏
X2C 除了做了以上优化,还将 layout 文件的读取和解析也同时移到了编译阶段,以此来降低 IO 开支。但编译期解析 xml 最大的困难在于咱们需求逐条翻译 View 的属性,原因是编译期间并没有 SDK 的依赖,因而无法生成 AtrributeSet 目标直接供以 View 的构造器消费。
这样一来,需求人工保护翻译规矩,将一条条 xml 属性转换成设置 View 属性的代码,这带来了几个问题:
-
生成的代码量指数级增加
-
需求极高的保护本钱来支撑自定义 View 的属性
-
某些 xml 属性并没有相对应的方法或不是一一对应的。
总而言之,在此基础上要维持强健完备的功能是十分困难的。而咱们所探究的 Layout2Code 的新计划与之比较,兼容性和保护本钱都有着巨大优势,仅有需求权衡考虑的便是运行时读取 layout 文件的优化空间有多少,是否值得这样的投入。
layout 文件的特别性
说到 xml 文件,条件反射般地就会想到是 IO 操作,功能差,这没错,但 layout 文件却比较特别。在 Andorid 使用打包进程中,AAPT 会对资源进行打包,会将除了 asset 文件夹下的 xml 文件经过字符串池复用、二进制转换等方法进行紧缩,终究生成紧缩后的资源文件和资源文件索引 resources.arsc 还有 R 文件。而在运用 AssetManager 对资源文件进行加载时,咱们也会运用 mmap 来降低 IO 本钱。
经过分析以上种种手段的利害,咱们在实际使用场景中测验后发现读取 layout 文件的耗时通常不超越 1ms。因而,考虑到将 layout 文件的读取和解析移到编译阶段所带来的保护本钱,权衡之下咱们终究挑选了直接放弃这一部分的优化。
3. 总结
在当下的开发环境中,Layout2Code 这一计划在功能提高方面仍然可以发挥很大的效果,当然有效运用这一计划的前提是开发者足够了解计划原理,以及知晓其详细的适用范围(非 AppComapt 组件)。
比较于传统的 X2C 计划,Layout2Code 的适用范围更广,保护本钱也更低。目前,该计划现已在小红书 APP 中得到了广泛的使用,并为咱们带来了良好的收益和效果。咱们对 Layout2Code 的研究由 kotlin 实现,运用 kapt,在未来咱们也计划接入 ksp,来减少编译期耗时,继续优化这一计划。
4. 作者简介
殇不患 小红书商业技能 Android 工程师
绫 人 小红书商业技能 Android 工程师