从阻塞队列聊到AsyncLayoutInflater

一.BlockingQueue阻塞队列

阻塞队列,Java给出的解释如下:


在队列的基础上额外支持了这些操作:当取元素的时候会等待队列至不为空的时候;当添加元素的时候会等待队列有可用空间的时候。

解释完阻塞队列了,思考一下问题:

1.为什么Java要提供这么一个队列?即阻塞队列的存在性是什么?

多线程环境中,通过队列可以很容易实现数据共享,比如经典的“生产者”和“消费者”模型中,通过队列可以很便利地实现两者之间的数据共享。假设我们有若干生产者线程,另外又有若干个消费者线程。如果生产者线程需要把准备好的数据共享给消费者线程,利用队列的方式来传递数据,就可以很方便地解决他们之间的数据共享问题。但如果生产者和消费者在某个时间段内,万一发生数据处理速度不匹配的情况呢?理想情况下,如果生产者产出数据的速度大于消费者消费的速度,并且当生产出来的数据累积到一定程度的时候,那么生产者必须暂停等待一下(阻塞生产者线程),以便等待消费者线程把累积的数据处理完毕,反之亦然。然而,在concurrent包发布以前,在多线程环境下,我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。

2.阻塞队列实现原理是什么?

在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤醒。

  • 当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列。
  • 当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有空的位置,线程被自动唤醒。

3.BlockingQueue核心方法

BlockingQueue接口有以下方法:

    boolean add(E e);

    boolean offer(E e);

    void put(E e) throws InterruptedException;

    boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException;

    E take() throws InterruptedException;

    E poll(long timeout, TimeUnit unit) throws InterruptedException;

    int remainingCapacity();

    boolean remove(Object o);

    boolean contains(Object o);

    int drainTo(Collection<? super E> c);

    int drainTo(Collection<? super E> c, int maxElements);
放入数据
  • (1)offer(anObject):表示如果可能的话,将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false.(本方法不阻塞当前执行方法
    的线程);
  • (2)offer(E o, long timeout, TimeUnit unit):可以设定等待的时间,如果在指定的时间内,还不能往队列中加入BlockingQueue,则返回失败。
  • (3)put(anObject):把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续.
  • (4)add(anObject):它与offer(anObject)功能大致一致,不过对此方法官方有如下备注:When using a capacity-restricted queue, it is generally preferable to use offer(Object),即:当使用容量限制的队列时,通常倾向于选择offer方法,容量限制的队列指的是在初始化队列是必须指定容量大小,eg:ArrayBlockingQueue
获取数据
  • (1)poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null;
  • (2)poll(long timeout, TimeUnit unit):从BlockingQueue取出一个队首的对象,如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则知道时间超时还没有数据可取,返回失败。
  • (3)take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入;
  • (4)drainTo():一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数),通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。

4.BlockingQueue实现类

衍生类

5.分析ArrayBlockingQueue内部机制

ArrayBlockingQueue官方解释为: A bounded blocking queue backed by an array,即由数组支持的有界阻塞队列;在构造ArrayBlockingQueue时需要指定容量,也就意味着底层数组一旦创建了,容量就不能改变了。那对此我提出一下问题。

  • 如何用固定容量的数组实现队列的FIFO机制
  • 如何在入栈,出栈的时候,达到阻塞效果,详细的说要做到当取元素的时候会等待队列至不为空的时候;当添加元素的时候会等待队列有可用空间的时候。

我们看下ArrayBlockingQueue是如何解决第一个问题的,ArrayBlockingQueue出入栈的源码如下:

    final Object[] items;
    int takeIndex;
    int putIndex;
    private void enqueue(E x) {
        // assert lock.getHoldCount() == 1;
        // assert items[putIndex] == null;
        final Object[] items = this.items;
        items[putIndex] = x;
        if (++putIndex == items.length) putIndex = 0;
        count++;
        notEmpty.signal();
    }

    private E dequeue() {
        // assert lock.getHoldCount() == 1;
        // assert items[takeIndex] != null;
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        E x = (E) items[takeIndex];
        items[takeIndex] = null;
        if (++takeIndex == items.length) takeIndex = 0;
        count--;
        if (itrs != null)
            itrs.elementDequeued();
        notFull.signal();
        return x;
    }

这里ArrayBlockingQueue内部记录两个全局变量,putIndex与takeIndex默认都为0,入栈的时候往putIndex对应下标的位置插入,同时putIndex自增1,当增加到固定容量-1的时候,再将putIndex置为0;当出栈的时候将takeIndex对应下标的位置清空即置为null,同时takeIndex自增1,当增加到固定容量-1的时候,再将takeIndex置为0;这样做的好处是时间复杂度最低,无需在出入栈的时候去遍历数组。

ArrayBlockingQueue是如何解决第二个问题的呢?即如何在入栈,出栈的时候,达到阻塞效果呢

    int count;
    final ReentrantLock lock;
    private final Condition notEmpty;
    private final Condition notFull;
    public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = new Object[capacity];
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }

这里ArrayBlockingQueue首先声明了以上几个全局变量,并在构造函数中进行初始化。在这里用到java.util.concurrent.locks.ReentrantLock与java.util.concurrent.locks.Condition的使用,这两个类即做到了线程安全的目的,这里先拓展讲一下线程安全的知识点

5.1 线程安全探索

在讲这两个locks类之前,先看一下以下例子:

public class Demo {

    private static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(15);
        for (int i = 0; i < 500; i++){
            executorService.execute(() -> {
                add();
            });
        }
        Thread.sleep(1000);
        System.out.println(count);
    }

    private static int add(){
        return ++count;
    }
}

该Demo代码最终打印的不是500,而是小于500的数字,为了保证结果和预期一致,则要实现线程安全
首先提问:如何保证线程安全呢?


多线程有三个核心点:原子性、可见性、有序性;原子性即一个操作(有可能包含有多个子操作)要么全部执行(生效),要么全部都不执行(都不生效);可见性即当多个线程并发访问共享变量时,一个线程对共享变量的修改,其它线程能够立即看到;有序性即程序执行的顺序按照代码的先后顺序执行。
只要实现以上3个特性,就可以做到线程安全

1.互斥同步(阻塞同步/悲观的并发策略)

互斥同步(Mutual Exclusion & Synchronization)是最常见的一种并发正确性保证手段,同步是指在多个线程并发访问共享数据时,保证共享数据在同一时刻只被一条(或者是一些,使用信号量的时候)线程使用。而互斥是实现同步的一种手段,临界区(Critical Section)、互斥量(Mutex)和信号量(Sempahore)都是主要的互斥实现方式。因此,在这四个字里面,互斥是因,同步是果,互斥是方法,同步是目的。互斥即为阻塞,让其他线程阻塞;其实现了操作的原子性(这个操作没有被中断) 和 可见性(对数据进行更改后,会立马写入到内存中,其他线程在使用到这个数据时,会获取到最新的数据)

常用Synchronized 关键字;synchronized实现同步的基础就是:Java中的每一个对象都可以作为锁。具体表现为:

  • 1.普通同步方法,锁是当前实例对象 public synchronized void **()
  • 2.静态同步方法,锁是当前类的Class对象 private synchronized static void **()
  • 3.同步方法块,锁是Synchronized括号里匹配的对象 synchronized (lockContent){}

synchronized同步的原理是

首先Java代码在编译之后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行,JAVA中所使用的并发机制依赖于JVM的实现和CPU的指令。
synchronized经过编译之后,会在同步块的前后生成 monitorenter 和monitorexit这两个字节码指令。这两个字节码指令之后有一个reference类型(存在于java虚拟机栈的局部变量表中,可以根据reference数据,来操作堆上的具体对象)的参数来指明要锁定和解锁的对象。根据虚拟机规范,在执行monitorenter 指令时,首先会尝试获取对象的锁(堆内存中存储的对象内容中,起对象头带有线程持有的锁),如果该对象没有被锁定,或者当前线程已经拥有了那个对象的锁,把锁的计数器加一。若获取对象失败,那当前线程就要阻塞等待,知道对象锁被另一个线程释放。

锁的可重入性的可重入是指:当一个线程获得一个对象锁后,再次请求该对象锁时是可以获得该对象的锁的。比如

public class Test01 {
    public synchronized void sm1(){
        System.out.println("同步方法1");
        //线程执行sm1()方法,默认this作为锁对象,在sm1()方法中调用了sm2()方法,注意当前线程还是持有this锁对象的
        //sm2()同步方法默认的锁对象也是this对象, 要执行sm2()必须先获得this锁对象,当前this对象被当前线程持有,可以 再次获得this对象, 这就是锁的可重入性. 假设锁不可重入的话,可能会造成死锁
        sm2();
    }

    private synchronized void sm2() {
        System.out.println("同步方法2");
        sm3();
    }

    private synchronized void sm3() {
        System.out.println("同步方法3");
    }

    public static void main(String[] args) {
        Test01 obj = new Test01();
        new Thread(new Runnable() {
            @Override
            public void run() {
                obj.sm1();
            }
        }).start();
    }
}

线程间通信

多个线程间相互配合工作,需要依靠线程间通信。在 Object对象中 , 提供了wait/notify/notifyall,可以用于控制线程的状态。


public class WaitNotifyDemo {

    private static Object object = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread threadA = new Thread(() -> {
            synchronized (object) {
                System.out.println("ThreadA---Start");
                try {
                    // 释放锁等待
                    object.wait();
                    System.out.println("ThreadA---Wait");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread threadB = new Thread(() -> {
            synchronized (object) {
                System.out.println("ThreadB---Start");
                // 唤醒ThreadA,需要注意的是(ThreadB需要执行完同步代码)
                object.notify();
                System.out.println("ThreadB---Wait");
            }
        });

        threadA.start();
        TimeUnit.SECONDS.sleep(2);
        threadB.start();
        threadA.join();
        threadB.join();
    }
}

打印如下:
ThreadA---Start
ThreadB---Start
ThreadB---Wait
ThreadA---Wait

在这里要特别强调一点,官方对wait有如下备注:

     * A thread can also wake up without being notified, interrupted, or
     * timing out, a so-called <i>spurious wakeup</i>.  While this will rarely
     * occur in practice, applications must guard against it by testing for
     * the condition that should have caused the thread to be awakened, and
     * continuing to wait if the condition is not satisfied.  In other words,
     * waits should always occur in loops, like this one:
     * <pre>
     *     synchronized (obj) {
     *         while (&lt;condition does not hold&gt;)
     *             obj.wait(timeout);
     *         ... // Perform action appropriate to condition
     *     }

java为了避免所谓的虚假唤醒,在多线程判断中,用while来代替if。所谓的虚假唤醒指的是:当一个条件满足时,很多线程都被唤醒了,但是只有其中部分是有用的唤醒,其它的唤醒都是无用功
这样在同步方法条件判断时,用while去做判断,避免了notifyall,让那些不满足条件的线程依然还在挂起中

synchronized可重入锁的实现
java 在虚拟机中除了线程计数器,java虚拟机栈 是线程私有的,其余的java堆,方法区,和运行时常量池都是线程共享的内存区域。java堆是存储对象和数组的,但是对象在内存中的存储布局可以分为三块区域:对象头,实例数据(对象真正存储的有效信息,程序代码中所定义的各个类型的字段内容),对齐填充。
每个锁关联一个线程持有者和一个计数器。当计数器为0时表示该锁没有被任何线程持有,那么任何线程都都可能获得该锁而调用相应方法。当一个线程请求成功后,JVM会记下持有锁的线程,并将计数器计为1。此时其他线程请求该锁,则必须等待。而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增。当线程退出一个synchronized方法/块时,计数器会递减,如果计数器为0则释放该锁。

存在的问题
互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能问题,因而这种同步又称为阻塞同步,它属于一种悲观的并发策略,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在 CPU 转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起 CPU 频繁的上下文切换导致效率很低。synchronized 采用的就是这种并发策略。

随着指令集的发展,我们有了另一种选择:基于冲突检测的乐观并发策略,通俗地讲就是先进性操作,如果没有其他线程争用共享数据,那操作就成功了,如果共享数据被争用,产生了冲突,那就再进行其他的补偿措施(最常见的补偿措施就是不断地重拾,直到试成功为止),这种乐观的并发策略的许多实现都不需要把线程挂起,因此这种同步被称为非阻塞同步

2.非阻塞同步(乐观并发策略)

因为使用synchronized的时候,只能有一个线程可以获取对象的锁,其他线程就会进入阻塞状态,阻塞状态就会引起线程的挂起和唤醒,会带来很大的性能问题,所以就出现了非阻塞同步的实现方法。互斥同步的原理是用同步实现了原子性和可见性。而非阻塞同步的原理就是: 先进行操作,如果没有其他线程争用共享数据,那么操作就成功了,如果共享数据有争用,就采取补偿措施(不断地重试)

常用类如下:



我们可以看下AtomicInteger类incrementAndGet方法

public final int incrementAndGet() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return next;
        }
}

public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

由以上源码可知,自增操作,就是不断在for循环,直到比较操作为true,才会跳出循环,这就是补偿措施; 比较操作compareAndSet最终调用了unsafe的compareAndSwapInt方法,即原子操作CAS指令(Compare-And-Swap比较并交换),它有三个操作数:内存位置,旧的预期值,新值,在执行CAS操作时,当且仅当内存地址的值符合旧的预期值的时候,才会用新值来更新内存地址的值,否则就不执行更新。

3.无同步方案

线程本地存储:将共享数据的可见范围限制在一个线程中。这样无需同步也能保证线程之间不出现数据争用问题。常用的是ThreadLocal类

  public T get() { }  
  public void set(T value) { }  
  public void remove() { }  
  protected T initialValue() { }  

get()方法是用来获取ThreadLocal在当前线程中保存的变量副本,set()用来设置当前线程中变量的副本,remove()用来移除当前线程中变量的副本,initialValue()是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法;
其实引起线程不安全最根本的原因 就是 :线程对于共享数据的更改会引起程序结果错误。线程安全的解决策略就是:保护共享数据在多线程的情况下,保持正确的取值。


所以要想解决Demo打印值符合预期值500,既可以让count声明为AtomicInteger类型;也可以对add静态方法,用synchronized修改(当然也可以在调用add方法地方加synchronized修饰)

  executorService.execute(() -> {
                synchronized (Demo.class){
                    add();
                }
            });
    或者
    private static int add(){
        synchronized (Demo.class){
            return ++count;
        }
    }

5.2聊聊ReentrantLock与Condition

以上例子也可以用ReentrantLock类

public class Demo {

    private static int count = 0;

    private static Lock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(15);
        for (int i = 0; i < 500; i++){
            executorService.execute(() -> {
                add();
            });
        }
        Thread.sleep(1000);
        System.out.println(count);
    }

    private static int add(){
        lock.lock();
        try {
            return ++count;
        }finally {
            lock.unlock();
        }

    }
}

ReetrantLock 采用的乐观并发策略,就是非阻塞同步;在基本用法上,ReentranLock与synchronized很相似,他们都具备一样的线程重入特性,synchronized 是 JVM 实现的,而 ReentrantLock 是 JDK 实现的,一个表现为API层面的互斥锁(lock和unlock方法配合try/finally语句块来完成),一个表现为原生语法层面的互斥锁;ReentrantLock 可中断,而 synchronized 不行。即ReentrantLock比synchronized增加了一些高级功能,主要有以下三项:等待可中断、可实现公平锁,一级锁可以绑定多个条件。

  • 等待可中断是指当持有锁的线程长期不是放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情,可中断特性对处理执行时间非常长的同步块很有帮助。
  • 公平锁是指多个线程在等待同一锁时,必须按照申请锁的时间顺序来依次获得锁;而非公平锁则不保证这一点,在锁被释放的时,任何一个等待锁的线程都有机会获得锁。synchronized中的锁是非公平的,ReentrantLock默认情况下也是非公平的,但可以通过带布尔值的构造函数要求使用公平锁。
  • 锁绑定多个条件是指一个ReentrantLock对象可以同时绑定多个Condition对象,而在sychronized中,锁对象的wait()和notify()或notifyAll()方法可以实现一个隐含的条件,如果要和多余一个的条件关联的时候,就不得不额外地添加一个锁,而ReentrantLock则无须这样做,只需要多次调用newCondition()方法即可。

ReetrantLock 当调用Condition的await方法后,当前线程会释放锁并等待,而其他线程调用condition 对象的 signal 或者 signalall 方法通知被阻塞的线程,然后自己执行 unlock 释放锁,被唤醒的线程获得之前的锁继续执行,最后释放锁。
所以,condition 中两个最重要的方法,一个是 await,一个是 signal 方法await:把当前线程阻塞挂起signal:唤醒阻塞的线程

我们回来再想想如何实现阻塞队列的代码原理,即:当添加元素的时候会等待队列有可用空间的时候;当取元素的时候会等待队列至不为空的时候;

  • 为了做到插入要等待有空间,在插入时判断count==size,符合就阻塞线程,不符合就执行插入操作
  • 为了做到取出要等待不为空,在取出时判断count==0,符合就阻塞线程,不符合就执行取出操作
    但是这两个操作是彼此影响的,即插入线程和取出线程是要通信的,插入完成之后,通知取出线程继续操作;取出完成之后,通知插入线程继续操作;
 class BoundedBuffer {
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition(); 
   final Condition notEmpty = lock.newCondition(); 
 
   final Object[] items = new Object[100];
   int putptr, takeptr, count;
 
   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length) 
         notFull.await();
       items[putptr] = x; 
       if (++putptr == items.length) putptr = 0;
       ++count;
       notEmpty.signal();
     } finally {
       lock.unlock();
     }
   }
 
   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0) 
         notEmpty.await();
       Object x = items[takeptr]; 
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally {
       lock.unlock();
     }
   } 
 }

这里有两个Condition,一个是notFull,一个是notEmpty。当执行入队列时先判count!=size条件,如果不符合会执行notFull的await操作,会去阻塞线程并释放线程锁,此时出队列操作中,如果有阻塞线程的情况,收到释放线程锁信号会继续判断notEmpty条件,当count!=0时,会执行出队列实际操作,执行完成后执行notFull的signal方法,通知入队列的阻塞线程继续判断notFull条件,如何有多余空间,执行实际入队列操作

ReentrantLock和Condition具体使用与分析可看以下文章:
ReentrantLock和condition源码浅析(一)
Java多线程之ReentrantLock与Condition
漫谈Java中的互斥同步
什么是线程安全?如何保证线程安全?

二.AsyncLayoutInflater分析

google对此解释如下:Helper class for inflating layouts asynchronously,即异步加载布局的帮助类;传统中,对于布局文件的加载必须要在主线程,为什么要在主线程呢?是因为ViewRootImpl在以下操作前都做了checkThread判断



checkThread源码如下:

    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

    public ViewRootImpl(Context context, Display display) {
        -------------------
        mThread = Thread.currentThread();
        -------------------
    }

有以上源码可知,checkThread方法判断,当前操作线程是否等于当时初始化ViewRootImpl时的线程。如果不相等抛出“Only the original thread that created a view hierarchy can touch its views”异常,Android View 绘制流程之 DecorView 与 ViewRootImpl

为了加载耗时布局,官方提供了AsyncLayoutInflater异步加载工具。那它内部如何实现异步加载布局的呢?我们阅读一下源码

public final class AsyncLayoutInflater {
    LayoutInflater mInflater;
    Handler mHandler;
    InflateThread mInflateThread;

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

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

AsyncLayoutInflater 构造器内初始化了主线程的Handler和单例InflateThread 线程以及一个自定义的布局加载器BasicInflater;当用户调用inflate方法时,就会调用obtainRequest方法从InflateThread 线程内获取一个InflateRequest加载布局请求,之后调用enqueue去入队列到InflateThread线程的队列中;我们再看下InflateThread 内部类的源码:

    private static class InflateThread extends Thread {
        private static final InflateThread sInstance;
        static {
            sInstance = new InflateThread();
            sInstance.start();
        }

        public static InflateThread getInstance() {
            return sInstance;
        }

        private ArrayBlockingQueue<InflateRequest> mQueue = new ArrayBlockingQueue<>(10);
        private SynchronizedPool<InflateRequest> mRequestPool = new SynchronizedPool<>(10);

        public void runInner() {
            InflateRequest request;
            try {
                request = mQueue.take();
            } catch (InterruptedException ex) {
                // Odd, just continue
                Log.w(TAG, ex);
                return;
            }

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

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

由源码可知,只要第一次初始化InflateThread 类,就会启动一个单例InflateThread 线程,注意run方法内部用了while循环runInner,是为了保证该线程都不会关闭;obtainRequest方法就是从mRequestPool的同步池内获取Request加载布局请求(其实这样的操作等同于Handler的obtainMessage方法去获取消息,而不是直接实例化Message,这样就可以避免内存抖动问题);enqueue方法就是入队列,注意看mQueue是一个ArrayBlockingQueue类型的队列,就是上文讲解的数组阻塞队列,通过调用put方法可达到其内部数组没有空间存放时而去阻塞队列,直到调用take出队列操作等到数组有空间时,才会真正存入数组中;线程run方法不断调用runInner方法,从阻塞队列中取出request加载布局请求,从而异步执行BasicInflater的inflate方法
我们看下BasicInflater类源码

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

可以看下LayoutInflater部分源码,如下:

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }

        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;

            try {
                // Look for the root node.
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }

                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
                }

                final String name = parser.getName();

                if (DEBUG) {
                    System.out.println("**************************");
                    System.out.println("Creating root view: "
                            + name);
                    System.out.println("**************************");
                }

                if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }

                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // Create layout params that match root, if supplied
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);
                        }
                    }

                    if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }

                    // Inflate all children under temp against its context.
                    rInflateChildren(parser, temp, attrs, true);

                    if (DEBUG) {
                        System.out.println("-----> done inflating children");
                    }

                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            } catch (XmlPullParserException e) {
                final InflateException ie = new InflateException(e.getMessage(), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } catch (Exception e) {
                final InflateException ie = new InflateException(parser.getPositionDescription()
                        + ": " + e.getMessage(), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } finally {
                // Don't retain static reference on context.
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;

                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }

            return result;
        }
    }

inflate方法内调用的createViewFromTag方法最终回、会调用onCreateView方法;我们在看下AsyncLayoutInflater的Handler传入的mHandlerCallback

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

mHandlerCallback 的主线程回调中会去检查异步请求后的view,如果为空,主线程会去加载布局,达到二次检验,避免异步加载后的View为空

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容