建议先回顾下之前四篇文章,这个系列的文章从前往后顺序看最佳:
上一篇文章中我们介绍了 AsyncLayoutInflater 的用法及源码实现,那么本文来分析下 AsyncLayoutInflater 使用的注意事项及改进方案。
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.
以上来自 AsyncLayoutInflater 的说明文档:
以上注意事项2、3、6项非常容易明白,下面分析下其余几项;
我们看下 ViewGroup 中的 generateLayoutParams 方法
/**
* 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 不支持设置,那么有些情况下效果肯定是不一样的,使用了异步之后导致效果不一样岂不是很坑,下面我们再具体解决。
前面的不支持三个字是不是让你心里一凉,其实这三个字不够准确,应该改为不完全支持。这一条要一篇文章的篇幅才能说明白,我们下篇文章再说哈。
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 标签的异步,以及真的不能异步吗?