AsyncLayoutInflater源码颇析
别看AsyncLayoutInflater源码只有233行,代码质量非常高。二百行左右的代码,值得开发者好好学习。这就是阅读源码的意义,也要写出这样优秀的代码。
1、构造AsyncLayoutInflater实例
传入Context构造AsyncLayoutInflater实例:
LayoutInflater mInflater;
Handler mHandler;
InflateThread mInflateThread;
public AsyncLayoutInflater(@NonNull Context context) {
mInflater = new BasicInflater(context);
mHandler = new Handler(mHandlerCallback);
mInflateThread = InflateThread.getInstance();
}初始化AsyncLayoutInflater内部这几个变量:LayoutInflater加载布局、Handler处理回调、InflateThread布局加载任务线程。
1.1、重写优化后的LayoutInflater
内部静态类BasicInflater继承中系统布局加载器LayoutInflater。别小瞧这33行代码,虽然看都能看懂。那是需要对框架有深刻的理解才能想出来,参考LayoutInflater的实现类PhoneLayoutInflater。
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);
}
}内部将常用的系统包路径:android.widget、android.webkit、android.app提取出来存放到sClassPrefixList,优先加载这几个包中的类。系统中控件相关的类大部分都在这里,对于绝大多数场景能够提高布局解析创建对象的速度。关于Android中的ClassLoader详见了解Android ClassLoader一文。
1.2、加载回调Handler
通过Handler.Callback接口构造回调Handler,AsyncLayoutInflater在主线程创建,所以该Handler用的也是主线程Looper,回调方法在主线程中执行。这里Callback返回true,不会再执行Handler的handleMessage(msg)方法,关于Callback详见Handler中Callback的作用。
private Callback mHandlerCallback = new Callback() {
@Override
public boolean handleMessage(Message msg) {
InflateRequest request = (InflateRequest) msg.obj;
//如果子线程布局加载解析失败返回null,则在主线程中再次尝试加载。
if (request.view == null) {
request.view = mInflater.inflate(
request.resid, request.parent, false);
}
//回调加载结束监听器OnInflateFinishedListener
request.callback.onInflateFinished(
request.view, request.resid, request.parent);
//释放Request布局加载请求:从mRequestPool中释放
mInflateThread.releaseRequest(request);
return true;
}
};1.3、异步加载线程InflateThread
AsyncLayoutInflater异步加载布局的核心就是InflateThread任务线程。在类加载的时候通过静态代码块初始化InflateThread单例sInstance,并开启任务线程。构造AsyncLayoutInflater的时候通过getInstance()获取单例。
private static final InflateThread sInstance;
//静态代码块初始化InflateThread单例sInstance,并开启任务线程
static {
sInstance = new InflateThread();
sInstance.start();
}
public static InflateThread getInstance() {
return sInstance;
}InflateThread类内部有两个集合:一个是保存布局加载任务的阻塞队列,另一个是用来复用InflateRequest的对象池。
private static class InflateThread extends Thread {
...
private ArrayBlockingQueue<InflateRequest> mQueue = new ArrayBlockingQueue<>(10);
private SynchronizedPool<InflateRequest> mRequestPool = new SynchronizedPool<>(10);
...
}2、inflate()异步加载布局
构造好AsyncLayoutInflater实例就可以通过它的inflate()方法开启异步加载布局,OnInflateFinishedListener回调callback必须非空,否则抛出异常。
@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);
}2.1、封装加载请求InflateRequest
内部类InflateRequest将布局加载请求相关的数据封装在一起,回调传递的时候也方便一些。合理的封装是开发者必要的编程素养。
private static class InflateRequest {
AsyncLayoutInflater inflater;
ViewGroup parent;
int resid;
View view;
OnInflateFinishedListener callback;
InflateRequest() {
}
}2.2、子线程执行加载任务
在前面1.3节提到InflateThread,类被加载的时候通过静态代码块初始化单例并开启线程,执行:
@Override
public void run() {
while (true) {
runInner();
}
}很多循环操作一样,抽取出单独的方法runInner(),目的是为了持有对象引用,避免在不确定的时间内保持有效引用。Volley网络请求框架内部的四个网络请求线程循环也是这样做的。
// 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();
} catch (InterruptedException ex) {
// Odd, just continue
Log.w(TAG, ex);
return;
}
try {
//在子线程中加载xml布局
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();
}最后,将加载好的布局封装在请求InflateRequest中,并通过Handler发送会主线程处理。
2.3、回调OnInflateFinishedListener
此前早已在AsyncLayoutInflater构造方法中初始化好的主线程Handler,处理子线程布局加载完成的回调:
private Callback mHandlerCallback = new Callback() {
@Override
public boolean handleMessage(Message msg) {
InflateRequest request = (InflateRequest) msg.obj;
//如果在子线程中xml布局加载失败,返回空View。重新在当前主线程中加载一遍
if (request.view == null) {
request.view = mInflater.inflate(
request.resid, request.parent, false);
}
request.callback.onInflateFinished(
request.view, request.resid, request.parent);
//释放当前请求,放回到mRequestPool中以便复用
mInflateThread.releaseRequest(request);
return true;
}
};2.4、InflateRequest对象复用
最后释放请求,这里涉及到对象池Pools。简单说明一下最后release()的意图,将InflateRequest释放并放入到对象池中以便后续复用。
public void releaseRequest(InflateRequest obj) {
obj.callback = null;
obj.inflater = null;
obj.parent = null;
obj.resid = 0;
obj.view = null;
mRequestPool.release(obj);
}在AsyncLayoutInflater的inflate()方法中,借助InflateThread调用obtainRequest()方法获取InflateRequest复用:
....
InflateRequest request = mInflateThread.obtainRequest();
....如果对象池中没有可用的,那么就创建一个新的InflateRequest实例使用。
public InflateRequest obtainRequest() {
InflateRequest obj = mRequestPool.acquire();
if (obj == null) {
obj = new InflateRequest();
}
return obj;
}
