从阻塞队列聊到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 (<condition does not hold>)
* 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为空