异步加载View,AsyncLayoutInflater原理
AsyncLayoutInflater是谷歌提供的一个异步加载UI方案,其可以异步加载控件并回调给UI,以此减少主线程消耗。
使用
在应用的build.gradle中添加如下依赖
...
dependencies {
...
implementation 'androidx.asynclayoutinflater:asynclayoutinflater:1.0.0'
}
依赖后,可以在任何能获取context的场景调用如下方法进行异步加载
/* KOTLIN */
/**
* 示例方法
*
* @param ctx 上下文实例
* @param layoutRes 目标布局文件id
* @param parent 父布局
*/
fun sample(ctx: Context, layoutRes: Int, parent: ViewGroup) {
AsyncLayoutInflater(ctx).inflate(layoutRes, parent) {
view, id, parentView ->
Log.d("RESULT_INTRO", "view: 目标加载布局")
Log.d("RESULT_INTRO", "id: 目标加载布局资源id")
Log.d("RESULT_INTRO", "view: 父布局")
// 该方法仅加载布局,父布局仅用来计算尺寸,不会将目标布局放入父布局
parentView?.addView(view)
}
}
/* JAVA */
/**
* 示例方法
*
* @param ctx 上下文实例
* @param layoutRes 目标布局文件id
* @param parent 父布局
*/
void sample(Context ctx, int layoutRes, ViewGroup parent) {
new AsyncLayoutInflater(ctx)
.inflate(layoutRes, parent, (view, resid, parentView) -> {
Log.d("RESULT_INTRO", "view: 目标加载布局");
Log.d("RESULT_INTRO", "resid: 目标加载布局资源id");
Log.d("RESULT_INTRO", "view: 父布局");
// 该方法仅加载布局,父布局仅用来计算尺寸,不会将目标布局放入父布局
if (parentView != null) parentView.addView(view);
});
}
该异步加载仅相当于LayoutInflater#inflate(int, ViewGroup, boolean)
方法中attachToRoot参数为false的结果。由于不知道异步加载结束后父布局会不会已被回收,所以加载结束后不会直接加入父布局,仅做尺寸计算
原理
打开AsyncLayoutInflater类,可以看到该类维护了一个LayoutInflater、一个用于将在加载完成后将事件回调给原线程(创建AsyncLayoutInflater的线程)的Handler,一个用来加载layout的线程。
初始化
// AsyncLayoutInflater.java
...
public AsyncLayoutInflater(@NonNull Context context) {
mInflater = new BasicInflater(context);
mHandler = new Handler(mHandlerCallback);
mInflateThread = InflateThread.getInstance();
}
...
在其构造函数中将前面提到的三个变量进行了初始化。可以看出其加载线程是一个静态内部类模式的单例。该线程维护了一个存放请求的队列,并在线程中死循环去从队列取加载请求并进行加载。
// AsyncLayoutInflater.java
// AsyncLayoutInflater.InflateThread.class
...
@Override
public void run() {
while (true) {
runInner();
}
}
// Extracted to its own method to ensure locals have a constrained liveness
// scope by the GC. This is needed to avoid keeping previous request references
// alive for an indeterminate amount of time, see b/33158143 for details
public void runInner() {
InflateRequest request;
try {
request = mQueue.take();
} catch (InterruptedException ex) {
// Odd, just continue
Log.w(TAG, ex);
return