AsyncLayoutInflater
AsyncLayoutInflater
AsyncLayoutInflater
AsyncLayoutInflater 用于异步布局加载
介绍
Android 在 View 的使用中,过多的布局文件 inflate 影响性能,尤其在一些滚动列表、样式种类很丰富的场景下,inflate 次数相对较多,整体 inflate 耗时就会增加,导致滚动过程卡顿。
所以,需要 View 的异步 inflate,甚至 View 的全局缓存,通过这些方式,去减少 UI 线程 inflate 的耗时及次数,以便减少卡顿,提升性能。
AsyncLayoutInflater 使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class AsyncLayoutInflaterActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val pb = ProgressBar(this)
setContentView(pb)
val s = System.currentTimeMillis()
thread {
LogUtils.logi("inflate", "thread.sleep", "模拟耗时的inflate。。。")
SystemClock.sleep(2000)
runOnUiThread {
// AsyncLayoutInflater不能直接在非UI线程使用
AsyncLayoutInflater(this@AsyncLayoutInflaterActivity)
.inflate(R.layout.activity_async_inflater_demo, null) { view, resid, parent ->
setContentView(view)
LogUtils.logi("inflate", "onInflateFinished", "耗时${System.currentTimeMillis() - s}ms")
}
}
}
}
}
AndroidX AsyncLayoutInflater 的限制
- 单一的线程
- 如果超过 10 个 items,主线程会 delay
- 不支持设置一个
LayoutInflater.Factory
和LayoutInflater.Factory2
- 没有方式取消正在进行的 inflate 操作
官方 AsyncLayoutInflater 不足
布局属性限制
- 不支持父容器属性 异步转换出来的 View 并没有被加到 parent 中,必须手动添加,AsyncLayoutInflater 是调用了
LayoutInflater.inflate(int, ViewGroup, false)
;parent
参数仅用于计算LayoutParams
,实际添加到父容器需手动操作,可能导致布局属性失效(如merge
标签)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void runInner() {
InflateRequest request;
try {
request = (InflateRequest)this.mQueue.take();
} catch (InterruptedException var4) {
Log.w("AsyncLayoutInflater", var4);
return;
}
try {
request.view = request.inflater.mInflater.inflate(request.resid, request.parent, false);
} catch (RuntimeException var3) {
Log.w("AsyncLayoutInflater", "Failed to inflate resource in the background! Retrying on the UI thread", var3);
}
Message.obtain(request.inflater.mHandler, 0, request).sendToTarget();
}
解决:异步转换出来的 View 自动将其添加到 parent 中
线程模型简单
单线程瓶颈: 使用单线程来做全部的 inflate 工作,如果一个界面中 layout 很多不一定能满足需求;高并发场景下可能排队。 解决:引入线程池,减少单线程等待
无优先级控制 无法优先处理紧急布局请求。
不是 Looper 线程 所构建的 View 中不能直接使用 Handler 或者调用 Looper.myLooper(),因为异步线程默认没有调用 Looper.prepare(); 是在一个
InflateThread
线程中调用的,该线程不是 Looper 线程非主线程不能使用 AsyncLayoutInflater 不能在非主线程中调用 AsyncLayoutInflater,因为异步线程默认没有调用 Looper.prepare();AsyncLayoutInflater 初始化的时候 new 了 Handler,如果该线程不是 Looper 线程,会报错
1
2
3
4
5
public AsyncLayoutInflater(Context context) {
this.mInflater = new BasicInflater(context);
this.mHandler = new Handler(this.mHandlerCallback);
this.mInflateThread = AsyncLayoutInflater.InflateThread.getInstance();
}
- 异常处理缺失: 后台线程的异常(如无效布局 ID)未捕获,直接导致应用崩溃。
其他
- AsyncLayoutInflater 不支持设置 LayoutInflater.Factory 或者 LayoutInflater.Factory2;
- 不能取消进行的任务,对生命周期无感知
- 缓存队列默认 10 的大小限制如果超过了 10 个则会导致主线程的等待
- 如果在异步加载 view 还没有添加到 parent 时,做一些 View 操作或者依赖 context 的操作,容易出问题,mashi 项目就出现大量这样的问题
- 不支持 fragment
- layoutinflater 布局的时候,可以统计该 view 加载时的耗时
AsyncLayoutInflater 改造
LZWAsyncLayoutInflater
https://github.com/AweiLoveAndroid/LZWAsyncLayoutInflater
OkLayoutInflater
https://medium.com/okcredit/oklayoutinflater-3c5cd93c6ebc
Ref
- Android AsyncLayoutInflater 限制及改进https://www.jianshu.com/p/f0c0eda06ae4
本文由作者按照 CC BY 4.0 进行授权