I suggest reviewing the previous four articles. The articles in this series are best viewed from front to back:
- Android setContentView source code parsing;
- Android LayoutInflater source code analysis;
- Android LayoutInflater Factory source code parsing;
- Android AsyncLayoutInflater source code parsing;
In the last article we introduced the usage and source code implementation of AsyncLayoutInflater, so this article will analyze the use of AsyncLayoutInflater considerations and improvements.
1. Precautions
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.
This is from the AsyncLayoutInflater documentation:
- With asynchronous constructors, the generateLayoutParams function that requires the parent of this layout is thread-safe;
- All views built must not create handlers or call looper.mylooper; (Because it is loaded in an asynchronous thread, the asynchronous thread does not call looper.prepare by default);
- Asynchronously converted views are not added to the parent View. AsyncLayoutInflater calls layOutinflater.inflate (int, ViewGroup, false). So if we need to add it to the Parent View, we need to add it manually;
- AsyncLayoutInflater does not support LayoutInflater.Factory or LayoutInflater.Factory2.
- Loading a layout with fragments is not supported.
- If the AsyncLayoutInflater fails, it automatically falls back to the UI thread to load the layout;
2. Notes
Above matters 2, 3, 6 are very easy to understand, the following analysis of the remaining several items;
2.1 With asynchronous constructors, the generateLayoutParams function that requires the parent of this layout is thread-safe;
Let’s take a look at the generateLayoutParams method in ViewGroup
/**
* 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);
}
Copy the code
The generateLayoutParams method simply new an object directly, and thus is not the thread-safe case of creating multiple times with a different object.
2.2 AsyncLayoutInflater does not support LayoutInflater.Factory or LayoutInflater.Factory2.
This is easy to explain, because AsynClayOutInflaters don’t have a similar Api, but those of you who have read the previous article know that these two classes are very important. If asynclayOutInflaters don’t support Settings, then there are situations where asynclayOutInflaters have different effects. After the use of asynchrony results in a different effect is not very pit, we will solve the specific below.
2.3 Does not support loading layouts containing Fragments;
Do not support the front of the three words is not a chill in your heart, in fact, these three words are not accurate, should be changed to not fully support. This one can only be explained in the length of an article, but we will talk about it in the next article.
3, can be improved
The code of AsyncLayoutInflater is not very much, and the quality of the code is very high, so there are few areas that can be optimized. Here is my opinion:
- Asynclayoutinflaters can only return views that are actually Inflate out through callbacks, but consider a scenario where the AsyncLayoutInflater asynchronously loads a Layout using a different class;
- AsyncLayoutInflater does not have a setFactory, so layouts loaded by AsyncLayoutInflater are not compatible with the system (e.g. TextView becomes AppCompatTextView).
- Because of the task queuing mechanism, there may be a scenario where the task is still not executed when needed. At this time, it is better to load the task directly in the main thread than wait for the task to be executed.
Then the modification scheme is also simple:
- Encapsulate AsyncLayoutInflater, modify the calling method, and shield the influence caused by different types of use;
- Set AsyncLayoutInflater directly in the AsyncLayoutInflater Inflater.
- If the current task is not executed, load it directly in the UI thread.
4, encapsulation
Since AsyncLayoutInflater is final and cannot be inherited, we Copy it and modify the code directly. The modification points are for the areas that can be improved in Section 3. Without further ado, just Show The Code.
** 1. The default Factory is not used before the super.onCreate call; * 2. Optimization of excessive queuing; */ public class AsyncLayoutInflaterPlus { private static final String TAG ="AsyncLayoutInflaterPlus"; private Handler mHandler; private LayoutInflater mInflater; private InflateRunnable mInflateRunnable; / / real thread pool load task 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); } /** * Determines whether the task has started executing ** @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 voidrun() {
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) {// Add this to ensure AppCompatActivity can be compatactivity, Any layout loaded with AsyncLayoutInflater before super.oncreate can also have default effects 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();
}
returnobj; } public void releaseRequest(AsyncLayoutInflaterPlus.InflateRequest obj) { obj.callback = null; obj.inflater = null; obj.parent = null; obj.resid = 0; obj.view = null; sRequestPool.release(obj); }}Copy the code
/** * call the entry class; */ 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); } /** * the getLayoutLoader and getRealView methods are paired * for loading and retrieving views in different classes of scenarios ** @param resid * @return
*/
public static AsyncLayoutLoader getLayoutLoader(int resid) {
returnsArrayCompat.get(resid); } /** * the getLayoutLoader and getRealView methods are paired * for loading and retrieving views in different classes of scenarios ** @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);
}
returnmRealView; } /** * Set asynchronous load View's LayoutParamsView ** @param context * @param Parent * @param layoutResId * @param View */ private static voidsetLayoutParamByParent(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); }}Copy the code
5, summary
This article mainly analyzes the considerations for the use of AsyncLayoutInflater and improves the limitations, which are not mentioned here.
In the next article we will explore why AsyncLayoutInflater documents do not support asynchracy with Fragment tags, and whether asynchracy really is not possible.