建議先回顧下以前四篇文章,這個系列的文章從前日後順序看最佳:android
上一篇文章中咱們介紹了 AsyncLayoutInflater 的用法及源碼實現,那麼本文來分析下 AsyncLayoutInflater 使用的注意事項及改進方案。web
For a layout to be inflated asynchronously it needs to have a parent whose generateLayoutParams(AttributeSet) is thread-safe and all the Views being constructed as part of inflation must not create any Handlers or otherwise call myLooper(). If the layout that is trying to be inflated cannot be constructed asynchronously for whatever reason, AsyncLayoutInflater will automatically fall back to inflating on the UI thread.緩存
NOTE that the inflated View hierarchy is NOT added to the parent. It is equivalent to calling inflate(int, ViewGroup, boolean) with attachToRoot set to false. Callers will likely want to call addView(View) in the AsyncLayoutInflater.OnInflateFinishedListener callback at a minimum.安全
This inflater does not support setting a LayoutInflater.Factory nor LayoutInflater.Factory2. Similarly it does not support inflating layouts that contain fragments.bash
以上來自 AsyncLayoutInflater 的說明文檔:微信
以上注意事項二、三、6兩項很是容易明白,下面分析下其他幾項;併發
咱們看下 ViewGroup 中的 generateLayoutParams 方法app
/**
* Returns a new set of layout parameters based on the supplied attributes set.
* @param attrs the attributes to build the layout parameters from
* @return an instance of {@link android.view.ViewGroup.LayoutParams} or one
* of its descendants
*/
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
複製代碼
generateLayoutParams 方法只是直接new了一個對象,於是非線程安全狀況下建立屢次而使用非同一個對象的狀況。異步
這個很好解釋,由於 AsyncLayoutInflater 沒有提供相似的Api,可是看過以前文章的小夥伴確定知道這兩個類是很是關鍵的,若是 AsyncLayoutInflater 不支持設置,那麼有些狀況下效果確定是不同的,使用了異步以後致使效果不同豈不是很坑,下面咱們再具體解決。async
前面的不支持三個字是否是讓你內心一涼,其實這三個字不夠準確,應該改成不徹底支持。這一條要一篇文章的篇幅才能說明白,咱們下篇文章再說哈。
AsyncLayoutInflater 的代碼並很少,並且代碼質量也很高,因此其中能夠優化的地方寥寥,簡單說下個人見解:
那麼修改方案也很簡單:
由於 AsyncLayoutInflater 是 final 的,於是不能使用繼承,咱們就將其 Copy 一份直接修改其中代碼,修改點就是 針對章節3中可改進的地方。很少說,直接 Show The Code。
/**
* 實現異步加載佈局的功能,修改點:
* 1. 單一線程;
* 2. super.onCreate以前調用沒有了默認的Factory;
* 3. 排隊過多的優化;
*/
public class AsyncLayoutInflaterPlus {
private static final String TAG = "AsyncLayoutInflaterPlus";
private Handler mHandler;
private LayoutInflater mInflater;
private InflateRunnable mInflateRunnable;
// 真正執行加載任務的線程池
private static ExecutorService sExecutor = Executors.newFixedThreadPool(Math.max(2,
Runtime.getRuntime().availableProcessors() - 2));
// InflateRequest pool
private static Pools.SynchronizedPool<AsyncLayoutInflaterPlus.InflateRequest> sRequestPool = new Pools.SynchronizedPool<>(10);
private Future<?> future;
public AsyncLayoutInflaterPlus(@NonNull Context context) {
mInflater = new AsyncLayoutInflaterPlus.BasicInflater(context);
mHandler = new Handler(mHandlerCallback);
}
@UiThread
public void inflate(@LayoutRes int resid, @Nullable ViewGroup parent, @NonNull CountDownLatch countDownLatch,
@NonNull AsyncLayoutInflaterPlus.OnInflateFinishedListener callback) {
if (callback == null) {
throw new NullPointerException("callback argument may not be null!");
}
AsyncLayoutInflaterPlus.InflateRequest request = obtainRequest();
request.inflater = this;
request.resid = resid;
request.parent = parent;
request.callback = callback;
request.countDownLatch = countDownLatch;
mInflateRunnable = new InflateRunnable(request);
future = sExecutor.submit(mInflateRunnable);
}
public void cancel() {
future.cancel(true);
}
/**
* 判斷這個任務是否已經開始執行
*
* @return
*/
public boolean isRunning() {
return mInflateRunnable.isRunning();
}
private Handler.Callback mHandlerCallback = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
AsyncLayoutInflaterPlus.InflateRequest request = (AsyncLayoutInflaterPlus.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);
request.countDownLatch.countDown();
releaseRequest(request);
return true;
}
};
public interface OnInflateFinishedListener {
void onInflateFinished(View view, int resid, ViewGroup parent);
}
private class InflateRunnable implements Runnable {
private InflateRequest request;
private boolean isRunning;
public InflateRunnable(InflateRequest request) {
this.request = request;
}
@Override
public void run() {
isRunning = true;
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();
}
public boolean isRunning() {
return isRunning;
}
}
private static class InflateRequest {
AsyncLayoutInflaterPlus inflater;
ViewGroup parent;
int resid;
View view;
AsyncLayoutInflaterPlus.OnInflateFinishedListener callback;
CountDownLatch countDownLatch;
InflateRequest() {
}
}
private static class BasicInflater extends LayoutInflater {
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
BasicInflater(Context context) {
super(context);
if (context instanceof AppCompatActivity) {
// 加上這些能夠保證AppCompatActivity的狀況下,super.onCreate以前
// 使用AsyncLayoutInflater加載的佈局也擁有默認的效果
AppCompatDelegate appCompatDelegate = ((AppCompatActivity) context).getDelegate();
if (appCompatDelegate instanceof LayoutInflater.Factory2) {
LayoutInflaterCompat.setFactory2(this, (LayoutInflater.Factory2) appCompatDelegate);
}
}
}
@Override
public LayoutInflater cloneInContext(Context newContext) {
return new AsyncLayoutInflaterPlus.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);
}
}
public AsyncLayoutInflaterPlus.InflateRequest obtainRequest() {
AsyncLayoutInflaterPlus.InflateRequest obj = sRequestPool.acquire();
if (obj == null) {
obj = new AsyncLayoutInflaterPlus.InflateRequest();
}
return obj;
}
public void releaseRequest(AsyncLayoutInflaterPlus.InflateRequest obj) {
obj.callback = null;
obj.inflater = null;
obj.parent = null;
obj.resid = 0;
obj.view = null;
sRequestPool.release(obj);
}
}
複製代碼
/**
* 調用入口類;同時解決加載和獲取View在不一樣類的場景
*/
public class AsyncLayoutLoader {
private int mLayoutId;
private View mRealView;
private Context mContext;
private ViewGroup mRootView;
private CountDownLatch mCountDownLatch;
private AsyncLayoutInflaterPlus mInflater;
private static SparseArrayCompat<AsyncLayoutLoader> sArrayCompat = new SparseArrayCompat<AsyncLayoutLoader>();
public static AsyncLayoutLoader getInstance(Context context) {
return new AsyncLayoutLoader(context);
}
private AsyncLayoutLoader(Context context) {
this.mContext = context;
mCountDownLatch = new CountDownLatch(1);
}
@UiThread
public void inflate(@LayoutRes int resid, @Nullable ViewGroup parent) {
inflate(resid, parent, null);
}
@UiThread
public void inflate(@LayoutRes int resid, @Nullable ViewGroup parent,
AsyncLayoutInflaterPlus.OnInflateFinishedListener listener) {
mRootView = parent;
mLayoutId = resid;
sArrayCompat.append(mLayoutId, this);
if (listener == null) {
listener = new AsyncLayoutInflaterPlus.OnInflateFinishedListener() {
@Override
public void onInflateFinished(View view, int resid, ViewGroup parent) {
mRealView = view;
}
};
}
mInflater = new AsyncLayoutInflaterPlus(mContext);
mInflater.inflate(resid, parent, mCountDownLatch, listener);
}
/**
* getLayoutLoader 和 getRealView 方法配對出現
* 用於加載和獲取View在不一樣類的場景
*
* @param resid
* @return
*/
public static AsyncLayoutLoader getLayoutLoader(int resid) {
return sArrayCompat.get(resid);
}
/**
* getLayoutLoader 和 getRealView 方法配對出現
* 用於加載和獲取View在不一樣類的場景
*
* @param resid
* @return
*/
public View getRealView() {
if (mRealView == null && !mInflater.isRunning()) {
mInflater.cancel();
inflateSync();
} else if (mRealView == null) {
try {
mCountDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
setLayoutParamByParent(mContext, mRootView, mLayoutId, mRealView);
} else {
setLayoutParamByParent(mContext, mRootView, mLayoutId, mRealView);
}
return mRealView;
}
/**
* 根據Parent設置異步加載View的LayoutParamsView
*
* @param context
* @param parent
* @param layoutResId
* @param view
*/
private static void setLayoutParamByParent(Context context, ViewGroup parent, int layoutResId, View view) {
if (parent == null) {
return;
}
final XmlResourceParser parser = context.getResources().getLayout(layoutResId);
try {
final AttributeSet attrs = Xml.asAttributeSet(parser);
ViewGroup.LayoutParams params = parent.generateLayoutParams(attrs);
view.setLayoutParams(params);
} catch (Exception e) {
e.printStackTrace();
} finally {
parser.close();
}
}
private void inflateSync() {
mRealView = LayoutInflater.from(mContext).inflate(mLayoutId, mRootView, false);
}
}
複製代碼
本文主要是分析 AsyncLayoutInflater 的使用注意事項,並對其中的限制進行了改進,此處再也不累述。
下一篇文章咱們一塊兒探究下爲何 AsyncLayoutInflater 文檔上寫不支持包含 Fragment 標籤的異步,以及真的不能異步嗎?
今日頭條各Android客戶端團隊招人火爆進行中,各個級別和應屆實習生都須要,業務增加快、日活高、挑戰大、待遇給力,各位大佬走過路過千萬不要錯過!
本科以上學歷、非頻繁跳槽(如兩年兩跳),歡迎加個人微信詳聊:KOBE8242011