Android AsyncLayoutInflater 源码解析

本文概述

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

  • 《Android setContentView 源码解析》;
  • 《Android LayoutInflater 源码解析》;
  • 《Android LayoutInflater Factory 源码解析》;

我们已经学习了 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 使用起来有什么注意点,我们可以对其进行哪些方面的改进呢?欢迎继续关注下一篇文章。

原文发布于微信公众号 - 双十二技术哥(gh_b0e7544783e2)

原文发表时间:2018-08-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android先生

RxJava2 实战知识梳理(5) - 简单及进阶的轮询操作

今天,我们介绍一种新的场景,轮询操作。也就是说,我们会尝试间隔一段时间就向服务器发起一次请求,在使用RxJava之前,该需求的实现一般有两种方式:

25720
来自专栏技术点滴

适配器模式(Adapter)

适配器模式(Adapter) 适配器模式(Adapter)[Wrapper] 意图:将类的一个接口转换成用户希望的另一个接口,使得原本由于接口不兼容而不能一起工...

21290
来自专栏MelonTeam专栏

Viewpager循环滑动的实现

导语 本文讲述实现ViewPager循环滑动效果的两种方案: 方案1: 复写ViewPager或者Adapter,扩展dataList,左右各加1...

24560
来自专栏desperate633

设计模式之适配器模式(Adapter Pattern)适配器模式的定义

适配器模式(Adapter Pattern)在生活中的应用随处可见。最常见的,我们使用的转接头就是利用了适配器模式的思想,我们可能用type-c接口的手机,但现...

11220
来自专栏林冠宏的技术文章

浅谈 Glide - BitmapPool 的存储时机 & 解答 ViewTarget 在同一View显示不同的图片时,总用同一个 Bitmap 引用的原因

作者:林冠宏 / 指尖下的幽灵 掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8 博客:htt...

431100
来自专栏屈定‘s Blog

设计模式--适配器模式的思考

个人认为适配器模式是一种加中间层来解决问题的思想,为的是减少开发工作量,提高代码复用率.另外在对于第三方的服务中使用适配器层则可以很好的把自己系统与第三方依赖解...

12650
来自专栏流媒体

Android RTMP推流之MediaCodec硬编码一(H.264进行flv封装)

在前面Android平台下使用FFmpeg进行RTMP推流(摄像头推流)的文章中,介绍了如何使用FFmpeg进行H264编码和Rtmp推流。接下来讲分几篇文章来...

99830
来自专栏陈仁松博客

Android开发中应该避免的内存泄露

一、背景和目的: 目前许多开发人员在Android开发过程中,较少关注实现细节和内存使用,容易会造成内存泄露,导致程序OOM。 本文会通过代码向大家介绍在And...

36250
来自专栏Android知识点总结

P1-Android基于MVP实现号码归属地查询

12220
来自专栏Android干货

关于安卓开发实现可展开的列表组件

408120

扫码关注云+社区

领取腾讯云代金券