本文主要從如下幾點學(xué)習(xí)AsyncLayoutInflater
- AsyncLayoutInfalter是啥
- AsyncLayoutInflater代碼實操
- AsyncLayoutInflater的源碼分析
- AsyncLayoutInflater的問題
AsyncLayoutInflater是啥
官方源碼定義
-
如下
Helper class for inflating layouts asynchronously //用于異步加載布局的幫助類
AsyncLayoutInflater代碼實操
-
代碼如下
new AsyncLayoutInflater(this).inflate(R.layout.activity_main, null, new AsyncLayoutInflater.OnInflateFinishedListener() { @Override public void onInflateFinished(@NonNull View view, int resid, @Nullable ViewGroup parent) { Log.d(TAG, "currentThread = " + Thread.currentThread().getName()); setContentView(view); } });
AsyncLayoutInflater源碼解析
AsyncLayoutInflater的構(gòu)造方法
-
代碼如下
public AsyncLayoutInflater(@NonNull Context context) { //創(chuàng)建BasicInflater對象筋粗,BasicInflater繼承至 LayoutInflater mInflater = new BasicInflater(context); //創(chuàng)建一個Handler對象前标,目的是線程的切換,從布局加載的工作線程切換到主線程中。 mHandler = new Handler(mHandlerCallback); //創(chuàng)建一個InflateThread對象屋讶,該類是繼承至Thread類悍抑, mInflateThread = InflateThread.getInstance(); }
AsyncLayoutInflater的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!"); } // 構(gòu)建InflateRequest對象,將resid、parent望迎、callback等變量存儲到這個變量中。 InflateRequest request = mInflateThread.obtainRequest(); request.inflater = this; request.resid = resid; request.parent = parent; request.callback = callback; // 然后將帶有參數(shù)的InflateRequest對象凌外,通過Inflatethread類中的enqueue方法保存到ArrayBlockingQueue隊列中 mInflateThread.enqueue(request); }
AsyncLayoutInflater中的InflateThread類
-
代碼如下
// InflateThread是AsyncLayoutInfalter類中的一個靜態(tài)內(nèi)部類 private static class InflateThread extends Thread { private static final InflateThread sInstance; // 當(dāng)類加載的時候辩尊,會初始化該類并且開啟線程 static { sInstance = new InflateThread(); sInstance.start(); } //對外提供獲取實例的方法 public static InflateThread getInstance() { return sInstance; } //生產(chǎn)者-消費者的模型,阻塞隊列 private ArrayBlockingQueue<InflateRequest> mQueue = new ArrayBlockingQueue<>(10); //使用了對象池康辑,來緩存創(chuàng)建的InflateRequest對象摄欲,防止重復(fù)創(chuàng)建 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 { //LayoutInfalter加載view的操作 //獲取到View對象 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); } //無論inflate方法的失敗或者成功疮薇,都將request發(fā)送到主線程中胸墙。 Message.obtain(request.inflater.mHandler, 0, request) .sendToTarget(); } @Override public void run() { //死循環(huán) while (true) { runInner(); } } //主要創(chuàng)建一個InflateRequest對象 public InflateRequest obtainRequest() { //先從對象緩存池中查詢 InflateRequest obj = mRequestPool.acquire(); //如果當(dāng)前InflateRequest對象沒有創(chuàng)建過,就創(chuàng)建一個 if (obj == null) { obj = new InflateRequest(); } //否則的話就將當(dāng)前內(nèi)存中存在的返回按咒; return obj; } //將對象緩存池中的對象數(shù)據(jù)清空迟隅,方便對象的復(fù)用。 public void releaseRequest(InflateRequest obj) { obj.callback = null; obj.inflater = null; obj.parent = null; obj.resid = 0; obj.view = null; mRequestPool.release(obj); } //將inflate請求中的request存儲到隊列中 public void enqueue(InflateRequest request) { try { mQueue.put(request); } catch (InterruptedException e) { throw new RuntimeException( "Failed to enqueue async inflate request", e); } } }
AsyncLayoutInflater中的InflateRequest類
-
代碼如下
//該類主要是攜帶數(shù)據(jù)励七,賦給Message.obj變量智袭,通過Message將數(shù)據(jù)發(fā)送到主線程中 private static class InflateRequest { AsyncLayoutInflater inflater; ViewGroup parent; int resid; View view; OnInflateFinishedListener callback; InflateRequest() { } }
AsyncLayoutInflater的BasicInflater類
-
代碼如下
//該類是繼承至LayoutInflater //重寫了onCreateView 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 { //在onCreateView中優(yōu)先加載 android.widget.", "android.webkit.","android.app."中的控件,之后在按照常規(guī)的加載方式去加載 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); } }
AsyncLayoutInflater的Callback
-
代碼如下
private Callback mHandlerCallback = new Callback() { @Override public boolean handleMessage(Message msg) { //通過Handler從Message中獲取InflateRequest中的數(shù)據(jù)帶到主線程 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); //置空InflateRequest中攜帶的數(shù)據(jù) mInflateThread.releaseRequest(request); return true; } };
AsyncLayoutInflater的問題
同樣看源碼定義
-
定義如下
For a layout to be inflated asynchronously it needs to have a parent whose {@link ViewGroup#generateLayoutParams(AttributeSet)} is thread-safe and all the Views being constructed as part of inflation must not create any {@link Handler}s or otherwise call {@link Looper#myLooper()}. If the layout that is trying to be inflated cannot be constructed asynchronously for whatever reason, {@link AsyncLayoutInflater} will automatically fall back to inflating on the UI thread. // 1. 對于異步加載的布局需要它的父View的generateLayoutParams(AttributeSet attrs)方法是線程安全的掠抬。 // 2. 所有被創(chuàng)建的View不能在內(nèi)部創(chuàng)建Handler或者是調(diào)用Looper.myLooper()方法吼野。 // 如果使用AsynclayoutInflater.inflate()的方法異步加載失敗,不管是什么原因剿另,都會會退到主線程中就加載布局箫锤。
NOTE that the inflated View hierarchy is NOT added to the parent. It is equivalent to calling {@link LayoutInflater#inflate(int, ViewGroup, boolean)} with attachToRoot set to false. Callers will likely want to call {@link ViewGroup#addView(View)} in the {@link OnInflateFinishedListener} callback at a minimum. // 3. 異步加載布局獲取的View沒有添加到父View中,因為源碼中request.inflater.mInflater.inflate( // request.resid, request.parent, false);是這樣調(diào)用去加載布局的 attachToRoot 為 false // 所以如果希望被添加到父View中雨女,需要在onInflateFinishedListener方法中去手動添加View
This inflater does not support setting a {@link LayoutInflater.Factory} nor {@link LayoutInflater.Factory2}. Similarly it does not support inflating layouts that contain fragments. // 4. 不支持設(shè)置LayoutInflater.Factory和Factory2 // 5. 不支持加載包含F(xiàn)ragment的布局
為什么會有這些問題
針對第一點:對于異步加載的布局需要它的父View(ViewGroup)的generateLayoutParams(AttributeSet attrs)方法是線程安全的谚攒。
-
看ViewGroup的generateLayoutParams(AttributeSet attrs)f方法(注意參數(shù)喲,ViewGroup中有很多同名的重載方法)
public LayoutParams generateLayoutParams(AttributeSet attrs) { return new LayoutParams(getContext(), attrs); } // 可以看到氛堕,直接new了一個對象出來馏臭,如果是在非線程安全的情況下去調(diào)用,會創(chuàng)建多個對象讼稚。
針對第二點:所有被創(chuàng)建的View不能在內(nèi)部創(chuàng)建Handler或者是調(diào)用Looper.myLooper()方法括儒。
- 因為是異步加載,在子線程中如果使用Handler必須在同線程下創(chuàng)建一個looper對象锐想,不然會報錯的帮寻。同樣Looper.myLooper是獲取同線程下的looper對象,你的有才能用赠摇,才能獲取固逗。