项目地址:github.com/didi/booste…java
作过开发的同窗都深有体会,用 XML 来撸 UI 的效率简直是吊打手写代码,在 Anko (Kotlin 库) 尚未流行以前,你们对 XML 仍是亲睐有加,虽然性能上偶尔会有卡顿,可是整体来讲,仍是勉强能接受,可是 Anko 的流行,也让咱们开始思考,有没有办法既能享受 XML 撸 UI 的快感,又能享受像 Anko 同样的性能呢?android
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
手写代码的思路api
在解析 XML 后,根据 XML 元素的属性去调用对应的 API性能优化
RelativeLayout root = new RelativeLayout(context);
TextView txt = new TextView(context);
txt.setText(...);
txt.setTextColor(...);
txt.setTextSize(...);
root.addView(txt);
复制代码
运行时解析 XML 的思路工具
在解析 XML 后,根据 XML 元素的属性去构造 AttributeSet布局
AttributeSet attr = new AttributeSet(...);
RelativeLayout root = new RelativeLayout(context, attr);
TextView txt = new TextView(context, attr);
复制代码
从以上两种方案,咱们能够发现:性能
所以,Booster 选择了第 2 种方案,至于其中的细节和原理,请参考此示例工程。
Android SDK 提供了 LayoutInflator 用于将 XML 转换成 View,读过 AOSP 源码的同窗可能会发现 LayoutInflator 是一个系统服务,为何要将 LayoutInflator 做为一个系统服务而不是一个工具类呢?我认为,最主要的缘由在于提高性能。
「What? 调用系统服务会涉及到跨进程调用,怎么可能会是为了提高性能呢?」
这得从 XML 解析提及,由于 XML 布局中会引用三类资源:
因此,对于系统资源来讲
所以,基于以上缘由,须要有一个更高层次的资源管理,所以做为一个系统服务合情合理。
为了分析 LayoutInflator 构造 View 的过程,咱们想到了 Android Studio 的布局可视化设计器,就是这个示例工程的由来,这样就能够在 IDE 中去单步调试 XML 的渲染过程。
Layout Library 由两部分组成:
之因此这么设计,主要仍是为了考虑向后兼容。
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);
}
}
复制代码
有兴趣的同窗能够用单步调式示例工程。
了解了 LayoutInflator 和 Layout Library 的实现原理后,对于 Booster 如何去转换 XML 成为字节码,相信你们已经有了思路了,实现原理以下:
LayoutInflater.inflate(...)
Activity.setContentView(int)
Dialog.setContentView(int)