AsyncLayoutInflater的简单介绍和源码分析

AsyncLayoutInflater是Google在Support v4包中提供的异步布局加载工具,用于避免因XML布局复杂导致的UI线程卡顿。它在非UI线程加载布局,然后通过回调将结果同步到UI线程。使用时需要遵守特定限制,例如不支持包含Fragments的布局。文章还深入源码,解析其工作原理,包括ArrayBlockingQueue实现的“生产者-消费者”模型和优化的LayoutInflater子类。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

我们知道setContentView()、layoutinflater.inflate()等传统的布局加载方式都是在UI线程中同步加载布局的。当layout.xml过于复杂繁重,加载就会造成UI卡顿甚至ANR。在Google最近发布的Supportv4包中,给我们提供了一个异步加载布局的帮助类:AsyncLayoutInflater。这个类可以帮助你在非UI线程中加载layout,然后将加载好的布局通过接口回调的形式同步给UI线程。这个帮助类将会允许你的UI线程在执行繁重的inflate时继续保持响应。

使用方式

new AsyncLayoutInflater(this).inflate(
                                     R.layout.activity_main,
                                     null,
                                     new AsyncLayoutInflater.OnInflateFinishedListener() {
            @Override
            public void onInflateFinished(View view, int resid, ViewGroup parent) {
                //Do something with view
            }});

对比一般的加载方式

View  result = LayoutInflater.from(this).inflate(R.layout.activity_main, null);

可以看出,二者在用法上几乎唯一的区别就是AsyncLayoutInflater比LayoutInflater多了一个用于通知加载完成的回调。

限制条件

1、 parent的 generateLayoutParams() 函数必须是线程安全的。

2、 所有正在构建的views一定不能创建任何 Handlers 或者调用 Looper.myLooper 函数。

3、 不支持设置LayoutInflater.Factory也不支持LayoutInflater.Factory2

4、 不支持包含Fragments的inflatinglayouts

如果我们尝试异步的方式去inflate的layout不支持这种方式,那么inflation处理将会自动回退到主线程中。

使用场景

繁重复杂的布局文件

源码分析

我们先从AsyncLayoutInflater的成员变量分析,AsyncLayoutInflater的成员变量比较简单,只有三个类:LayoutInflater、Handler、InflateThread,如下:
 private LayoutInflater mInflater;
 private Handler mHandler;
 private InflateThread mInflateThread;

mInflater便是最终真正执行加载布局的类,Handler的唯一作用是从非UI线程切换到UI线程。mInflateThread继承至一个Thread,真正的异步加载便是在它里面发生的,这是一个一new出来就开始run的线程。一句话概括就是layout布局在mInflateThread中交由mInflater进行加载,加载完成之后再由mHandler抛给UI线程。下面来看看具体代码:

    @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);
    }

在inflate方法中,先生成一个InflateRequest数据结构用来存储外界传递过来的布局文件id、父容器(Parent Group)和回调函数等数据,再将生成好的对象传入enqueue方法,接着我们进入mInflateThread线程看看enqueue方法做了什么:

public void enqueue(InflateRequest request) {
            try {
                mQueue.put(request);
            } catch (InterruptedException e) {
                throw new RuntimeException(
                        "Failed to enqueue async inflate request", e);
            }
        }

很简单,就一行代码。mQueue是一个ArrayBlockingQueue,可以抽象层一个队列。这行代码的作用无非就是把InflateRequest对象加入队列。

核心代码发生在InflateThread的run()方法中。如下图:

 @Override
        public void run() {
            while (true) {
                InflateRequest request;
                try {
                    request = mQueue.take();
                } catch (InterruptedException ex) {
                    // Odd, just continue
                    Log.w(TAG, ex);
                    continue;
                }

                try {
                    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();
            }
        }

方法里面是一个while死循环,在每次循环中从mQueue队列取出一个包含了布局资源id、parent、回调函数等所有需要的数据的InflateRequest对象。然后调用request.view = request.inflater.mInflater.inflate(request.resid,request.parent, false);加载布局,这行代码就是我们平时用的方式。加载完成之后在最后一行将结果抛给UI线程。我们来看看mHandler的回调:

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;
        }
    };

回调里面调用了request.callback.onInflateFinished(request.view, request.resid,request.parent);将最终结果传回给了UI线程。至此全部结束!

几个注意的细节:

1、  InflateRequest其实是一个单例模式,单例模式在这里的使用是一个很恰当的做法,因为线程本来就是一个十分消耗资源的对象,一个线程足够应付布局加载。

2、  为什么要用ArrayBlockingQueue做队列?其实这是一个“生产者-消费者”模型,当队列为空的时候InflateThread线程会被阻塞,有新的数据的时候会被唤醒。从而不会占用过多资源,如CPU。

3、  成员变量mInflater实际上是一个被重写的LayoutInflater类——BasicInflater,在这个类中对onCreateView进行了优化,优先加载android.widget.,android.webkit.,android.app.这三个package中的对象,因为绝大多数UI文件都包含在这三个类中。在一定程度上节省了时间。

4、  一些人的疑问:不是说View的绘制只能在UI线程里面吗?为何还能在非UI线程里加载布局?这里需要弄清楚一点,LayoutInflater只是进行布局树的解析,并没有真正走UI的绘制流程,UI的绘制需要被addView到确定的content里才会绘制的。

 

最后,留下一段思考空间:在前文我们提到了AsyncLayoutInflater的四个局限,你能够结合代码分析一下为什么会有这四条限制么?

参考资料

https://developer.android.com/reference/android/support/v4/view/AsyncLayoutInflater.html

 






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值