一、背景
当谈到移动应用程序的体验时,页面启动速度是其中至关重要的一点,更快的页面展示速度确保应用程序可以迅速加载并响应用户的操作, 从而提高用户使用 App 时的满意度。在页面启动的整个流程中,随着 UI 复杂度的上升,布局的 Inflate 耗时占据了相当一部分关键的比例,本文分享得物自身在页面布局构建耗时优化方案上的探索历程。
二、现有方案
在布局构建耗时优化上,开源社区上有一些现成的方案可供参考,我们首先看下目前一些已知的技术方案。
掌阅X2C
掌阅的 X2C 方案开源于 2018 年,其通过 APT 在编译期间对目标 XML 文件进行解析,并翻译成 XML View 树结构对应的 Java 文件。比如以下的布局 XML 文件。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="10dp">
<include
android:id="@+id/head"
layout="@layout/head"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true" />
<ImageView
android:id="@+id/ccc"
style="@style/bb"
android:layout_below="@id/head" />
</RelativeLayout>
转换成 Java 文件:
public class X2C_2131296281_Activity_Main implements IViewCreator {
@Override
public View createView(Context ctx, int layoutId) {
Resources res = ctx.getResources();
RelativeLayout relativeLayout0 = new RelativeLayout(ctx);
relativeLayout0.setPadding((int)(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,10,res.getDisplayMetrics())),0,0,0);
View view1 =(View) new X2C_2131296283_Head().createView(ctx,0);
RelativeLayout.LayoutParams layoutParam1 = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);
view1.setLayoutParams(layoutParam1);
relativeLayout0.addView(view1);
view1.setId(R.id.head);
layoutParam1.addRule(RelativeLayout.CENTER_HORIZONTAL,RelativeLayout.TRUE);
ImageView imageView2 = new ImageView(ctx);
RelativeLayout.LayoutParams layoutParam2 = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,(int)(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,1,res.getDisplayMetrics())));
imageView2.setLayoutParams(layoutParam2);
relativeLayout0.addView(imageView2);
imageView2.setId(R.id.ccc);
layoutParam2.addRule(RelativeLayout.BELOW,R.id.head);
return relativeLayout0;
}
}

优点:
- 性能高,没有了加载 XML 的 IO 和递归解析过程。
- 避免了类反射构建的耗时。
- 基于 APT 直接生成 Java 文件。
缺点:
- View 兼容性差,适配成本高,自定义 View 需要配置属性对应的方法。
- 功能不完整,不支持 Merge 标签,无法查询系统 style,所以只支持应用内 style。
- 由于 APT 本身的特性,在 XML 发生变化时,对应注解处理器生成的 Java 构建文件不会同步发生变, 对于不熟悉的同学来说容易踩坑。
AsyncLayoutInflater
AsyncLayoutInflater 是由 Android Google 官方提供的异步 Inflate API,其主要思路是将 Inflate 操作放在异步线程并行操作,从而让主线程可以继续执行一些其他的初始化操作,通过异步回调在相应的 Layout View 创建完成后,再设置到页面上。
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new AsyncLayoutInflater(this).inflate(
R.layout.async_layout,
null,
new AsyncLayoutInflater.OnInflateFinishedListener() {
@Override
public void onInflateFinished(View view, int resid, ViewGroup parent) {
setContentView(view);
}
}
);
}
优点:
- 将 UI 加载过程迁移到了子线程,保证了 UI 线程的高响应。
- 不存在 View 兼容性问题。
缺点:
-
有一定改造成本,在原有的页面直接引入 AsyncLayoutInflater 进行改造时,由于从同步调用改成异步回调调用导致的逻辑结构变化容易引入 NPE 之类的风险。
-
内部依然存在部分 View 的反射需要创建的开销。
ViewCompiler
Google 加入了一个 ViewCompiler,从原理来看是系统在安装 APK 的时候自动对布局文件做的编译优化,ViewCompiler 会将可优化的 XML 布局转化为代码构建的代码,并编译成 Dex 文件。

之后在程序运行时,首次使用 Infalter 类时,就会提前加载该 Dex 文件。

之后在调用 Infalte 函数 Inflate相应布局资源时,会尝试调用优化后的 pacakgeme.CompileView 类的 Infalte 函数,直接生成对应的 View。


ViewCompiler 编译 Layout 的原理其实和现有的 XML To Code 方案是类似的,都是解析 Layout XML 文件,再根据 XML 节点信息生产组装 Vi

本文分享得物在页面布局构建耗时优化方案的探索。先介绍掌阅X2C、AsyncLayoutInflater、ViewCompiler等现有方案及优缺点,接着阐述自研X2C框架实践,包括View构建流程、插件选型、预加载、线程调优等,最后展示线上性能收益,强调数据驱动优化。
最低0.47元/天 解锁文章
437

被折叠的 条评论
为什么被折叠?



