Android Layout Inflation 优化
减少布局的嵌套层级
例如,使用 ConstraintLayout
取代 LinearLayout
。
异步加载
为ViewGroup
动态添加子View
时,我们往往使用一个 layout 的 XML 来 inflate 一个 view,然后将其 add 到父容器。
inflate 包含对 XML 文件的读取和解析(IO 操作),并通过反射创建View
树。当XML文件过大或页面层级过深,布局的加载就会较为耗时。
由于这一步并非 UI 操作,可以转移到非主线程执行,为此,官方在扩展包提供了AsyncLayoutInflater
。
以 inflate 一个简单的 layout 10次为例:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context=".jank.JankActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="hello" />
</FrameLayout>
for (int i = 0; i < 10; i++) {
layoutInflater.inflate(R.layout.view_jank, container, false);
}
同步 inflate 约需耗时 12 ms。
AsyncLayoutInflater asyncLayoutInflater = new AsyncLayoutInflater(this);
AsyncLayoutInflater.OnInflateFinishedListener onInflateFinishedListener = new AsyncLayoutInflater.OnInflateFinishedListener() {
@Override
public void onInflateFinished(@NonNull View view, int resid, @Nullable ViewGroup parent) {
if (parent != null) {
parent.addView(view);
}
}
};
for (int i = 0; i < 10; i++) {
asyncLayoutInflater.inflate(R.layout.view_jank, container, onInflateFinishedListener);
}
使用AsyncLayoutInflater
异步 inflate 后,主线程就不再有 inflate 的耗时了。
适用场景
动态加载布局较复杂的View
懒加载
使用ViewStub
占位,需要显示时才加载真正的View
。
<ViewStub
android:id="@+id/stub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout="@layout/real_view" />
ViewStub stub = findViewById(R.id.stub);
if (stub != null) {
stub.inflate(); // inflate一次以后,view树中就不再包含这个ViewStub了
}
适用场景
根据条件决定是否显示的View
,例如:
- 网络请求失败的提示
- 列表为空的提示
- 新内容、新功能的引导,因为引导基本上只显示一次
延迟加载
在主线程注册IdleHandler
回调,当主线程"空闲"时才执行回调中的逻辑。
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
// 当该Looper线程没有message要处理时才执行
return false;
}
});
适用场景
次要页面元素的加载和渲染,比如未读信息的红点、新手引导等。