先来看看AsyncInflater的基本使用。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new AsyncLayoutInflater(this).inflate(R.layout.activity_test, null, new AsyncLayoutInflater.OnInflateFinishedListener() {
@Override
public void onInflateFinished(@NonNull View view, int resid, @Nullable ViewGroup parent) {
setContentView(view);
Log.d(TAG, "onInflateFinished:");
}
});
Log.d(TAG, "onCreate: ");
}
这样就可以异步地加载ContentView了。内部是如何实现的呢?先来看看AsyncInflater的构造方法AsyncLayoutInflater。
public AsyncLayoutInflater(@NonNull Context context) {
//1
mInflater = new BasicInflater(context);
//2
mHandler = new Handler(mHandlerCallback);
//3
mInflateThread = InflateThread.getInstance();
}
初始化操作只有三行代码,先来看看BasicInflater。
private static class BasicInflater extends LayoutInflater {
...
}
BasicInflater 继承自LayoutInflater,我们知道LayoutInflater可以根据R.layout.xxx来创造一个ContentView,BasicInflater也是。
再来看看代码2的Callback做了什么。
private Callback mHandlerCallback = new Callback() {
@Override
public boolean handleMessage(Message msg) {
//A
InflateRequest request = (InflateRequest) msg.obj;
if (request.view == null) {
request.view = mInflater.inflate(
request.resid, request.parent, false);
}
//B
request.callback.onInflateFinished(
request.view, request.resid, request.parent);
//C
mInflateThread.releaseRequest(request);
return true;
}
};
子线程完成了耗时的布局创建后发送一个消息给主线程Handler进行回调,更新UI。注意,这里强调是在主线程回调。mHandler = new Handler(mHandlerCallback) 这样的方式获取的Handler会首先在调用它的线程里查找只属于该线程的Looper(也就是线程局部变量)。如果我们在子线程里new AsyncLayoutInflater,会出现两种结果。
1.该子线程无法提供Looper,直接报错。
2.该子线程可以提供Looper(例如HandlerThread),导致UI任务在子线程上调用,同样会报错。
对Handler和线程问题还不是很了解的朋友可以看看我写的这篇文章,非常建议大家理解Handler的内部原理,很多Android的库的用到了Handler,要知道Handler可是内存泄漏的常客,这些库对Handler使用非常熟练,理解了Handler的内部原理对这些库的源码阅读是很有帮助的。
回到代码,从代码A可以看出来InflateRequest被装入了Message中,来看看它的代码。
private static class InflateRequest {
AsyncLayoutInflater inflater;
ViewGroup parent;
int resid;
View view;
OnInflateFinishedListener callback;
InflateRequest() {
}
}
这个类没有什么特别的功能,就是用来传递子线程Inflate好的ContentView及更新界面的回调。
主线程收到消息就在代码B更新界面。代码C进行释放操作,这是很有必要的,等等会分析。整个mHandlerCallback就做了这些事情,至此mHandler初始化也就完成了。
最后在代码3获取InflateThread 就行了。
//3
mInflateThread = InflateThread.getInstance();
mInflateThread 得到的是一个单例。Activity、View、Fragment等用到单例时要有内存泄漏的警觉。InflateThread这个类继承自Thread,它就是用于处理耗时操作的子线程,处理完后通过Handler回到主线程更新界面。
先看看这个线程在做什么。
@Override
public void run() {
//1
while (true) {
runInner();
}
}
public void runInner() {
InflateRequest request;
try {
request = mQueue.take();
} catch (InterruptedException ex) {
// Odd, just continue
Log.w(TAG, ex);
return;
}
try {
//2
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);
}
//3
Message.obtain(request.inflater.mHandler, 0, request)
.sendToTarget();
}
其实这个线程的任务很简单,就是把ContentView创造出来(代码2),然后更新界面(代码3)。因为可能有多个Activity要异步加载,所以这个子线程会在死循环里(代码1),不断等待新的加载请求。
再来看看obtainRequest方法的代码。
private SynchronizedPool<InflateRequest> mRequestPool = new SynchronizedPool<>(10);
public InflateRequest obtainRequest() {
InflateRequest obj = mRequestPool.acquire();
if (obj == null) {
obj = new InflateRequest();
}
return obj;
}
这个方法和Message.obtain()非常相似。一个InflateRequest的会经历这么几个过程。
调用obtainRequest方法从请求池里取出一个InflateRequest,接着等子线程完成了耗时任务后利用这个InflateRequest装载ContentView,然后InflateRequest被发送至主线程,
最后回收。
现在来分析刚刚提到的回收操作及它的必要性,来看看源码。
public void releaseRequest(InflateRequest obj) {
obj.callback = null;
obj.inflater = null;
obj.parent = null;
obj.resid = 0;
//1
obj.view = null;
mRequestPool.release(obj);
}
操作很简单,就是各种置空然后把这个InflateRequest 回收到mRequestPool再利用。代码1的obj.view就是指Activity的ContentView。如果这里不置空会有什么后果呢?来看看下面这张图。
private static final InflateThread sInstance;
mInflateThread指向的是一个单例对象,这个单例是一个静态变量。一个静态变量间接地持有了Activity,即便Activity销毁了也无法被回收,这就导致内存泄漏了。
最后再来看看AsyncInflater的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!");
}
//1
InflateRequest request = mInflateThread.obtainRequest();
request.inflater = this;
request.resid = resid;
request.parent = parent;
request.callback = callback;
//2
mInflateThread.enqueue(request);
}
代码1通过obtainRequest方法从请求池中获取一个空的InflateRequest,装入参数后放入请求队列中等待处理(代码2)。说到串行处理任务请求,Handler是非常好的选择。