Android源码系列(19) -- AsyncLayoutInflater

January 18, 2019

一、类签名

这是用于异步填充 View 的帮助类。在主线程构建 AsyncLayoutInflater 实例,并调用方法 inflate(int, ViewGroup, OnInflateFinishedListener)。视图填充完毕后在主线程回调 OnInflateFinishedListener 通知调用者。

1
public final class AsyncLayoutInflater

这适用于懒创建或响应用户交互的UI部分。有利于主线程继续响应用户时,重量级填充操作继续执行。填充布局需要来自 ViewGroup.generateLayoutParams(AttributeSet) 的父布局,该方法线程安全。

所有视图在构建过程中,禁止创建任何 Handler 或调用 Looper.myLooper() 。若布局无法在子线程进行异步填充,则操作会回退到主线程上执行。

注意,视图填充完成后不会加入到父布局中。这相当于调用 LayoutInflater.inflate(int, ViewGroup, boolean) 时参数 attachToRootfalse。因为调用者很可能希望在 OnInflateFinishedListener 通过调用 ViewGroup.addView(View) 把视图放入父布局。

本填充器不支持设置 LayoutInflater.FactoryLayoutInflater.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);
        }
    }
}

九、缺点

  1. 不支持多线程并发处理任务;
  2. 类修饰为 final,不能通过继承重写父类实现;
  3. 任务等待队列不能自定义初始化大小;
  4. 不支持设置 LayoutInflater.FactoryLayoutInflater.Factory2