AsyncLayoutInflater
是一個異步初始化布局的 Helper
類剔应。
它的本質(zhì)就是把對布局文件的 inflate
放入到了子線程里面,等到初始化成功后牍疏,在通過接口拋回到主線程。
在 Activity
中的簡單代碼實現(xiàn)為:
// 異步加載 xml, 在 Activity.onCreate(xxx) 里面
AsyncLayoutInflater(this).inflate(R.layout.activity_try_everything, null, object : AsyncLayoutInflater.OnInflateFinishedListener {
override fun onInflateFinished(view: View, p1: Int, p2: ViewGroup?) {
setContentView(view)
}
})
一般來說,我們都是通過 setContentView(R.layout.xxx)
的形式加載 xml
的.
其實有三個不同方法參數(shù)的 setContentView()
函數(shù)臭挽, 如下
public void setContentView(@LayoutRes int layoutResID) {
this.getDelegate().setContentView(layoutResID);
}
public void setContentView(View view) {
this.getDelegate().setContentView(view);
}
public void setContentView(View view, LayoutParams params) {
this.getDelegate().setContentView(view, params);
}
我們可以通過 inflate
出來 layoutResID
成一個對應的 view
, 然后調(diào)用 setContentView(View view)
, 通過這樣傳遞咬腕。
那么 AsyncLayoutInflater
是如何工作的呢欢峰?
以下內(nèi)容分為以下部分:
-
AsyncLayoutInflater
的簡單介紹和創(chuàng)建 - 添加
request
初始化布局的請求:AsyncLayoutInflater.inflate(xxx)
-
Handler
里面處理Message
發(fā)送的消息, 把結(jié)果返回給UI
線程 - 小結(jié)
1. AsyncLayoutInflater
的簡單介紹和創(chuàng)建
1.1 AsyncLayoutInflater
的簡單介紹
AsyncLayoutInflater
的實現(xiàn)涨共, 它主要有三個成員變量和一個內(nèi)部類:
-
LayoutInflater
用來inflate
布局 -
Handler
用來 post 到主線程 -
InflateThread
是一個Thread
纽帖,一個子線程,在里面完成inflate
過程 -
InflateRequest
靜態(tài)內(nèi)部類举反,用來承載必要的信息 -
OnInflateFinishedListener
對外部暴漏的接口懊直,用于通知外部,已經(jīng)初始化View
完成
1.2 AsyncLayoutInflater
的創(chuàng)建
AsyncLayoutInflater
的創(chuàng)建火鼻, 從代碼中可以看到:
public AsyncLayoutInflater(@NonNull Context context) {
// 在創(chuàng)建一個 AsyncLayoutInflater 對象時室囊,會同時新建 inflater, handler, 和 thread
mInflater = new BasicInflater(context);
mHandler = new Handler(mHandlerCallback);
mInflateThread = InflateThread.getInstance();
}
其中雕崩,代碼分析如下:
mInflater
是后續(xù)用來inflate
布局-
mInflateThread
是一個單例對象,在獲取時該thread
已經(jīng)開始工作start
代碼如下:private static final InflateThread sInstance; // 靜態(tài)代碼塊融撞,在加載該類時就會被調(diào)用 static { sInstance = new InflateThread(); // 此時子線程已經(jīng)開始跑 sInstance.start(); }
-
mHandler
是用來接收thread
發(fā)出的消息盼铁,回到UI
線程Message
的發(fā)送消息位置:Message.obtain(request.inflater.mHandler, 0, request).sendToTarget()
并且把主要的信息「即
request
」放入在Message
中的object
中,
做好上面的準備后尝偎,此時 mInflateThread
里面是沒有請求要執(zhí)行的饶火,一旦它的隊列里有了 request
, 則會執(zhí)行對應的邏輯
2. 添加 request
初始化布局的請求: AsyncLayoutInflater.inflate(xxx)
當調(diào)用該方法時致扯,會有一系列步驟:
- 創(chuàng)建
InflateRequest
請求肤寝; - 通過
mInflateThread.enqueue(request)
把該請求放入到mInflateThread
子線程里面
源碼如下:
@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);
}
從代碼中可以看到,每一次調(diào)用 inflate(xxx)
方法都會新創(chuàng)建一個 InflateRequest
, 并且把該 request
加入 mInflateThread
的隊列中抖僵。
2.1 創(chuàng)建 InflateRequest
請求
而在 InflateThread
中有一個隊列 mQueue
用來存放 InflateRequest
請求
private ArrayBlockingQueue<InflateRequest> mQueue = new ArrayBlockingQueue<>(10);
InflateThread
線程在 start()
之后鲤看,會去調(diào)用 runInner()
嘗試獲取 inflateRequest
然后執(zhí)行對應邏輯。
@Override
public void run() {
while (true) {
runInner();
}
}
注:這里并不會是一個死循環(huán)耍群, 因為
mQueue.take()
方法罢绽。
ArrayBlockingQueue
是會產(chǎn)生阻塞的隊列敞曹,在take()
方法中瑟曲,如果count == 0
, 則會一直陷入notEmpty.await()
ArrayBlockingQueue
的 take()
方法源碼:
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 這里會一直等待驶臊,直到有消息為止
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
2.2 通過 mInflateThread.enqueue(request)
添加請求后,mQueue
不為空
當該 mQueue
里面可以獲取到 request
. 則會通過 inflater.inflate(xxx)
在子線程中完成 view
的構(gòu)建耘婚,并通過 Message
發(fā)送消息給對應的 handler
處理罢浇。代碼如下:
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();
}
3. Handler
里面處理 Message
發(fā)送的消息, 把結(jié)果返回給 UI
線程
通過 Message
發(fā)送消息后沐祷,真正接受消息的地方是在 Handler
的 handleMessage(xxx)
方法里面嚷闭,
此時已經(jīng)切回到了 UI
線程中:
@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;
}
代碼邏輯:
- 從
msg.obj
中獲取到InflateRequest
- 判斷
request.view
是否為null
- 如果為空,則重新
inflate
赖临,此時是在UI
線程中進行的胞锰,和一般的初始化一樣; - 通過接口
OnInflateFinishedListener
通知外部兢榨,并把得到的view
傳遞出去
注:在這里做了兼容嗅榕,防止在異步中
inflate
失敗,做了判斷
對外暴漏的接口:AsyncLayoutInflater.OnInflateFinishedListener
沒太多值得說的吵聪,類似與最簡單的 View.OnClickListener
一樣凌那,通過接口把事情拋到外部的調(diào)用方。
4. 小結(jié)
上述部分吟逝,簡單的介紹了 AsyncLayoutInflater
的內(nèi)部實現(xiàn)帽蝶。
可能會在心里產(chǎn)生一點點疑問:這個簡單來說不就是 new
了一個新的子線程,然后 inflate
好 view
后块攒,重新放入到 UI
線程中, 會有很大用處嗎励稳?
確實佃乘,AsyncLayoutInflater
簡單來說就是實現(xiàn)了上述表述。
而且它也有很多缺點驹尼,官方文檔是明確的寫著:
This inflater does not support setting a LayoutInflater.Factory nor LayoutInflater.Factory2. Similarly it does not support inflating layouts that contain fragments.
有著局限性趣避,這也是我們一般不會使用這種方式的一個很大原因。
但是呢扶欣?當一個 XML
已經(jīng)可以支持異步 inflate
, 那么能不能 PreInflate
呢?
能不能提前在子線程中去 inflate
布局千扶?然后在真正需要這些 view
的時候料祠,就可以省去加載布局的時間了?
當然可以澎羞,具體怎么實現(xiàn)還需要接下來再次梳理髓绽。
本次只是一個簡單的介紹 AsyncLayoutInflater
。
期待后續(xù)能夠整理好妆绞。
2019.10.10 by chendroid
文章來自公眾號:「Droid 二三事
」
PS:
后續(xù)會接著整理梳理知識顺呕。
參考:
- 官方文檔說明:
https://developer.android.com/reference/android/support/v4/view/AsyncLayoutInflater
https://juejin.im/post/5b651ad8e51d4519635008bd
簡書:http://www.reibang.com/p/a3a3bd314c45