Booster 系列之——布局:XML vs 纯代码

项目地址:github.com/didi/booste…java

背景

作过开发的同窗都深有体会,用 XML 来撸 UI 的效率简直是吊打手写代码,在 Anko (Kotlin 库) 尚未流行以前,你们对 XML 仍是亲睐有加,虽然性能上偶尔会有卡顿,可是整体来讲,仍是勉强能接受,可是 Anko 的流行,也让咱们开始思考,有没有办法既能享受 XML 撸 UI 的快感,又能享受像 Anko 同样的性能呢?android

XML vs 纯代码

XML 布局是在运行时解析的,由 XmlPullParser 一边解析二进制的 XML 文件一边反射构造 View 节点,像 APP 首页通常都是由不少 XML 组成,这样会致使屡次「加载-解析」。都说手写代码性能更好,到底有多好呢?如下是 Anko 与 XML 的性能对比数据:git

机型 规格 Anko XML 差别
阿尔卡特
One Touch
Mediatek MT6572
Dual-core 1.3GHz Cortex-A7
512MB RAM
169 ms 608 ms 359%
华为 Y300 Qualcomm MSM 8225
Dual-core 1.0GHz Cortex-A5
512MB RAM
593 ms 3435 ms 578%
华为 Y330 Mediatek MT6572
Dual-core 1.3GHz Cortex-A7
512MB RAM
162 ms 984 ms 606%
三星 Galaxy S2 Exynos 4210
Dual-core 1.2GHz Cortex-A9
1GB RAM
207 ms 753 ms 363%

以上数据来源于 android.jlelse.eu/400-faster-…github

鱼和熊掌兼得

既然 Booster 是作专门用来作性能优化的,看到这里,你们可能想到了解决方案——将 XML 转译成字节码。没错,就是这样,你们可能会问:转译成字节码会有兼容性问题么?——这得看实现方式。目前来看,有如下两种实现:canvas

  1. 手写代码的思路api

    在解析 XML 后,根据 XML 元素的属性去调用对应的 API性能优化

    RelativeLayout root = new RelativeLayout(context);
    TextView txt = new TextView(context);
    txt.setText(...);
    txt.setTextColor(...);
    txt.setTextSize(...);
    root.addView(txt);
    复制代码
  2. 运行时解析 XML 的思路工具

    在解析 XML 后,根据 XML 元素的属性去构造 AttributeSet布局

    AttributeSet attr = new AttributeSet(...);
    RelativeLayout root = new RelativeLayout(context, attr);
    TextView txt = new TextView(context, attr);
    复制代码

从以上两种方案,咱们能够发现:性能

  • 方案 1 须要适配全部的 Layout ,对于自定义的 Layout 或者 View,生成的代码可能与 XML 的呈现不一致
  • 方案 2 只要保证 AttributeSet 正确性,就能保证最终呈现的 UI 跟 XML 渲染出来的是一致的

所以,Booster 选择了第 2 种方案,至于其中的细节和原理,请参考此示例工程

LayoutInflator

Android SDK 提供了 LayoutInflator 用于将 XML 转换成 View,读过 AOSP 源码的同窗可能会发现 LayoutInflator 是一个系统服务,为何要将 LayoutInflator 做为一个系统服务而不是一个工具类呢?我认为,最主要的缘由在于提高性能。

「What? 调用系统服务会涉及到跨进程调用,怎么可能会是为了提高性能呢?」

这得从 XML 解析提及,由于 XML 布局中会引用三类资源:

  1. 系统资源
  2. 本 APP 的资源
  3. 其它 APP 的资源

因此,对于系统资源来讲

  • 若是每次 inflate 时都要去加载一次系统资源,不 ANR 才怪
  • 若是引用了其它 package 的资源,对于当前 APP 来讲,正常状况是没法访问的

所以,基于以上缘由,须要有一个更高层次的资源管理,所以做为一个系统服务合情合理。

Layoutlib

为了分析 LayoutInflator 构造 View 的过程,咱们想到了 Android Studio 的布局可视化设计器,就是这个示例工程的由来,这样就能够在 IDE 中去单步调试 XML 的渲染过程。

Layout Library 由两部分组成:

  • layoutlib-api:layoutlib 的接口层,随着 Android Studio 一块儿发布
  • layoutlib:layoutlib-api 的实现层,随着 Android SDK 一块儿发布

之因此这么设计,主要仍是为了考虑向后兼容。

Layout Library 的另外一大亮点在于它参用了相似 Robolectric Shadow 的方式,对 Android SDK 中的 API 进行了替换,这样,本来调用 native 的 API 都在 Layout Library 中用 Java 实现了:

@LayoutlibDelegate
static void native_drawBitmap(Canvas thisCanvas, long nativeCanvas, Bitmap bitmap, float left, float top, long nativePaintOrZero, int canvasDensity, int screenDensity, int bitmapDensity) {
    Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
    if (bitmapDelegate != null) {
        BufferedImage image = bitmapDelegate.getImage();
        float right = left + (float)image.getWidth();
        float bottom = top + (float)image.getHeight();
        drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, 0, 0, image.getWidth(), image.getHeight(), (int)left, (int)top, (int)right, (int)bottom);
    }
}
复制代码

有兴趣的同窗能够用单步调式示例工程

总结

了解了 LayoutInflatorLayout Library 的实现原理后,对于 Booster 如何去转换 XML 成为字节码,相信你们已经有了思路了,实现原理以下:

  1. 根据 compileSdk 加载 Android SDK 中对应 platform 中的系统资源(第2步会用到)
  2. mergeRes 任务以后,加载 APP 资源
  3. 解析项目中的布局 xml,并根据解析结果生成对应的代码
  4. compile 的过程当中,将生成的代码一块儿参与编译
  5. transform 的过程当中,扫描 class 中的如下方法调用替换成调用第3步生成的代码
    • LayoutInflater.inflate(...)
    • Activity.setContentView(int)
    • Dialog.setContentView(int)
    • ......
相关文章
相关标签/搜索