一、类签名
这是用于异步填充 View 的帮助类。在主线程构建 AsyncLayoutInflater 实例,并调用方法 inflate(int, ViewGroup, OnInflateFinishedListener)。视图填充完毕后在主线程回调 OnInflateFinishedListener 通知调用者。
1
public final class AsyncLayoutInflater
这适用于懒创建或响应用户交互的UI部分。有利于主线程继续响应用户时,重量级填充操作继续执行。填充布局需要来自 ViewGroup.generateLayoutParams(AttributeSet) 的父布局,该方法线程安全。
所有视图在构建过程中,禁止创建任何 Handler 或调用 Looper.myLooper() 。若布局无法在子线程进行异步填充,则操作会回退到主线程上执行。
注意,视图填充完成后不会加入到父布局中。这相当于调用 LayoutInflater.inflate(int, ViewGroup, boolean) 时参数 attachToRoot 为 false。因为调用者很可能希望在 OnInflateFinishedListener 通过调用 ViewGroup.addView(View) 把视图放入父布局。
本填充器不支持设置 LayoutInflater.Factory 或 LayoutInflater.Factory2。类似,也不支持填充包含 fragment 的布局。源码版本Android 28
二、数据成员
1
2
3
4
5
6
7
8
// 负责填充的LayoutInflater实例
LayoutInflater mInflater;
// Handler
Handler mHandler;
// 线程
InflateThread mInflateThread;
如果请求在子线程填充的过程中出现异常,则任务会派发到主线程重试一次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private Callback mHandlerCallback = new Callback() {
@Override
public boolean handleMessage(Message msg) {
// 获取填充请求,进行视图的填充,以下操作都在主线程进行
InflateRequest request = (InflateRequest) msg.obj;
if (request.view == null) {
request.view = mInflater.inflate(
request.resid, request.parent, false);
}
// 填充成功,触发回调
request.callback.onInflateFinished(
request.view, request.resid, request.parent);
// 填充完成,回收请求以便以后复用
mInflateThread.releaseRequest(request);
return true;
}
};
三、构造方法
构造方法内初始化了实际负责填充工作的填充器 BasicInflater。
1
2
3
4
5
6
public AsyncLayoutInflater(@NonNull Context context) {
mInflater = new BasicInflater(context);
// 由于Handler在主线程创建,所以内部默认使用主线程
mHandler = new Handler(mHandlerCallback);
mInflateThread = InflateThread.getInstance();
}
四、成员方法
主线程通过此方法添加新填充任务。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@UiThread
public void inflate(@LayoutRes int resid, @Nullable ViewGroup parent,
@NonNull OnInflateFinishedListener callback) {
// 回调不能为空,以便把填充完成的布局返回给调用者
if (callback == null) {
throw new NullPointerException("callback argument may not be null!");
}
// 从缓存池中获取一个空的填充请求
InflateRequest request = mInflateThread.obtainRequest();
// 把填充需要的数据存入填充请求中
request.inflater = this;
request.resid = resid;
request.parent = parent;
request.callback = callback;
// 请求添加到子线程等待处理
mInflateThread.enqueue(request);
}
五、OnInflateFinishedListener
在子线程处理完毕后,通过此回调把填充完成的 View 返回给调用者。创建完成的 View 需要调用者自行添加到 ViewGroup 中,而不能像普通 LayoutInflater 那样直接加入到父布局。
1
2
3
4
public interface OnInflateFinishedListener {
void onInflateFinished(@NonNull View view, @LayoutRes int resid,
@Nullable ViewGroup parent);
}
六、InflateRequest
这个为构建任务的请求,请求包含参数:用于测绘的父布局、资源id、回调监听、已填充视图。初始化完成后的请求放入 InflateThread 的阻塞队列等待处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static class InflateRequest {
// AsyncLayoutInflater
AsyncLayoutInflater inflater;
// 父布局
ViewGroup parent;
// 需填充资源的id
int resid;
// 填充完成的视图,未填充完成为null
View view;
// 填充完成的回调
OnInflateFinishedListener callback;
InflateRequest() {
}
}
七、BasicInflater
此类继承父类 LayoutInflater 并重写父类方法 onCreateView(String name, AttributeSet attrs)。在此方法内,调用父类方法 createView 反射构建目标视图。
先尝试用 sClassPrefixList 的前缀构建视图,因为使用的视图多数位于这三个包路径底下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
private static class BasicInflater extends LayoutInflater {
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
BasicInflater(Context context) {
super(context);
}
@Override
public LayoutInflater cloneInContext(Context newContext) {
return new BasicInflater(newContext);
}
@Override
protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
for (String prefix : sClassPrefixList) {
try {
// 构建视图
View view = createView(name, prefix, attrs);
if (view != null) {
return view;
}
} catch (ClassNotFoundException e) {
// In this case we want to let the base class take a crack
// at it.
}
}
// 填充的视图不在以上三个包内,则调用父类方法构建
return super.onCreateView(name, attrs);
}
}
八、InflateThread
负责填充视图的线程。线程内包含任务阻塞队列,这个队列的空间为10。当放入任务数量超过10时,如果主线程继续放入新任务,则主线程会被阻塞直到队列出现空余位置。这个类是单例,所以多个 AsyncLayoutInflater 实例共享一个工作线程及内部线程池。
如果有非常多任务需要异步填充,由于每个填充的视图平均需要10ms的时间,所以会出现视图排队填充完成,造成长时间等待。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
private static class InflateThread extends Thread {
private static final InflateThread sInstance;
static {
sInstance = new InflateThread();
sInstance.start();
}
public static InflateThread getInstance() {
return sInstance;
}
// 等待处理InflateRequest实例的阻塞队列
private ArrayBlockingQueue<InflateRequest> mQueue = new ArrayBlockingQueue<>(10);
// 复用InflateRequest实例的缓存池
private SynchronizedPool<InflateRequest> mRequestPool = new SynchronizedPool<>(10);
// 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;
}
try {
// 从InflateRequest取出资源id、父布局样式作为参数,通过BasicInflater构建视图
request.view = request.inflater.mInflater.inflate(
request.resid, request.parent, false);
} catch (RuntimeException ex) {
// Probably a Looper failure, retry on the UI thread
Log.w(TAG, "Failed to inflate resource in the background! Retrying on the UI"
+ " thread", ex);
}
Message.obtain(request.inflater.mHandler, 0, request)
.sendToTarget();
}
// 循环从阻塞队列中获取任务
@Override
public void run() {
while (true) {
runInner();
}
}
// 从缓存池中获取一个有效InflateRequest用于构建任务参数
public InflateRequest obtainRequest() {
// 先尝试从缓存池中获取对象
InflateRequest obj = mRequestPool.acquire();
if (obj == null) {
// 若缓存池没有缓存对象才创建新对象
obj = new InflateRequest();
}
return obj;
}
// 填充任务完成后,释放InflateRequest并放回缓存池中等待复用
public void releaseRequest(InflateRequest obj) {
obj.callback = null;
obj.inflater = null;
obj.parent = null;
obj.resid = 0;
obj.view = null;
// 已重置对象放回缓存池
mRequestPool.release(obj);
}
// 初始化完成的任务通过此方法放入队列等待处理
public void enqueue(InflateRequest request) {
try {
mQueue.put(request);
} catch (InterruptedException e) {
throw new RuntimeException(
"Failed to enqueue async inflate request", e);
}
}
}
九、缺点
- 不支持多线程并发处理任务;
- 类修饰为 final,不能通过继承重写父类实现;
- 任务等待队列不能自定义初始化大小;
- 不支持设置 LayoutInflater.Factory 或 LayoutInflater.Factory2;