阅读 609

Android AsyncLayoutInflater 源码解析

先回顾下之前三篇文章,这个系列的文章从前往后顺序看最佳:

我们已经学习了 Layout 相关的方方面面,本文就来学习下一个相对新颖的知识点:AsyncLayoutInflater;说它相对新颖是因为它是Android 24.1.0版本之后才有的。

1、AsyncLayoutInflater 简介

Helper class for inflating layouts asynchronously. To use, construct an instance of AsyncLayoutInflater on the UI thread and call inflate(int, ViewGroup, OnInflateFinishedListener). The AsyncLayoutInflater.OnInflateFinishedListener will be invoked on the UI thread when the inflate request has completed.This is intended for parts of the UI that are created lazily or in response to user interactions. This allows the UI thread to continue to be responsive & animate while the relatively heavy inflate is being performed.

这是从 AsyncLayoutInflater 说明文档截出来的一段话,大意是:AsyncLayoutInflater 是来帮助做异步加载 layout 的,inflate(int, ViewGroup, OnInflateFinishedListener) 方法运行结束之后 OnInflateFinishedListener 会在主线程回调返回 View;这样做旨在 UI 的懒加载或者对用户操作的高响应。

简单的说我们知道默认情况下 setContentView 函数是在 UI 线程执行的,其中有一系列的耗时动作:Xml的解析、View的反射创建等过程同样是在UI线程执行的,AsyncLayoutInflater 就是来帮我们把这些过程以异步的方式执行,保持UI线程的高响应。

AsyncLayoutInflater 比较简单,只有一个构造函数及普通调用函数:inflate(int resid, ViewGroup parent, AsyncLayoutInflater.OnInflateFinishedListener callback),使用也非常方便。

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        new AsyncLayoutInflater(AsyncLayoutActivity.this)
                .inflate(R.layout.async_layout, null, new AsyncLayoutInflater.OnInflateFinishedListener() {
                    @Override
                    public void onInflateFinished(View view, int resid, ViewGroup parent) {
                        setContentView(view);
                    }
                });
        // 别的操作
    }
复制代码

2、AsyncLayoutInflater 构造函数

AsyncLayoutInflater 的源码非常简单,总共只有170行代码,我们就从调用的入口来看下。

    public AsyncLayoutInflater(@NonNull Context context) {
        mInflater = new BasicInflater(context);
        mHandler = new Handler(mHandlerCallback);
        mInflateThread = InflateThread.getInstance();
    }
复制代码

可以看到做了三件事情:

  • 创建 BasicInflater;
  • 创建 Handler;
  • 获取 InflateThread 对象;
  1. BasicInflater 继承自 LayoutInflater,只是覆写了 onCreateView:优先加载这三个前缀的 Layout,然后才按照默认的流程去加载,因为大多数情况下我们 Layout 中使用的View都在这三个 package 下
    private static class BasicInflater extends LayoutInflater {
        private static final String[] sClassPrefixList = {
            "android.widget.",
            "android.webkit.",
            "android.app."
        };
        @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) {
                }
            }
            return super.onCreateView(name, attrs);
        }
    }
复制代码
  1. 创建 Handler 和它普通的作用一样,就是为了线程切换,AsyncLayoutInflater 是在异步里 inflate layout,那创建出来的 View 对象需要回调给主线程,就是通过 Handler 来实现的

  2. InflateThread 从名字上就好理解,是来做 Inflate 工作的工作线程,通过 InflateThread.getInstance 可以猜测 InflateThread 里面是一个单例,默认只在一个线程中做所有的加载工作,这个类我们会在下面重点分析。

3、inflate

    @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);
    }
复制代码

首先会通过 InflateThread 去获取一个 InflateRequest,其中有一堆的成员变量。为什么需要这个类呢?因为后续异步 inflate 需要一堆的参数(对应 InflateRequest 中的变量),会导致方法签名过长,而使用 InflateRequest 就避免了很多个参数的传递。

    private static class InflateRequest {
        AsyncLayoutInflater inflater;
        ViewGroup parent;
        int resid;
        View view;
        OnInflateFinishedListener callback;

        InflateRequest() {
        }
    }
复制代码

接下来对 InflateRequest 变量赋值之后会将其加到 InflateThread 中的一个队列中等待执行

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

4、InflateThread

    private static class InflateThread extends Thread {
        private static final InflateThread sInstance;
        static {
            // 静态代码块,确保只会创建一次,并且创建即start。
            sInstance = new InflateThread();
            sInstance.start();
        }

        public static InflateThread getInstance() {
            return sInstance;
        }

        private ArrayBlockingQueue<InflateRequest> mQueue = new ArrayBlockingQueue<>(10);// 异步inflate的缓存队列;
        private SynchronizedPool<InflateRequest> mRequestPool = new SynchronizedPool<>(10);// Todo

        // 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();// 从队列中取一个request。
            } catch (InterruptedException ex) {
                // Odd, just continue
                Log.w(TAG, ex);
                return;
            }

            try {
                request.view = request.inflater.mInflater.inflate(
                        request.resid, request.parent, false);// Inflate layout
            } 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();// 循环,但其实不会一直执行
            }
        }

        public InflateRequest obtainRequest() {
            InflateRequest obj = mRequestPool.acquire();
            if (obj == null) {
                obj = new InflateRequest();
            }
            return obj;
        }

        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);
            }
        }
    }
复制代码
  • enqueue 函数;
    • 只是插入元素到 mQueue 队列中,如果元素过多那么是有排队机制的;
  • runInner 函数;
    • 运行于循环中,从 mQueue 队列取出元素;
    • 调用 inflate 方法;
    • 返回主线程;

此处提一个问题:runInner 运行于循环中,会一直在执行吗?

实际上不是的,mQueue 队列的类型是 ArrayBlockingQueue ,这是一个“生产者-消费者”的模型,如果队列中没有元素,那么 mQueue.take() 就会处于等待状态,直到 mQueue.put 唤醒才会继续执行。

5、总结

本文主要分析了 AsyncLayoutInflater 的源码实现,让我们想下其中的关键词:Handler、线程、队列、BasicInflater。

那 AsyncLayoutInflater 使用起来有什么注意点,我们可以对其进行哪些方面的改进呢?欢迎继续关注下一篇文章。

广告时间

今日头条各Android客户端团队招人火爆进行中,各个级别和应届实习生都需要,业务增长快、日活高、挑战大、待遇给力,各位大佬走过路过千万不要错过!

本科以上学历、对技术有热情,欢迎加我的微信详聊:KOBE8242011

欢迎关注

关注下面的标签,发现更多相似文章
评论