/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.support.v4.view; import android.content.Context; import android.os.Handler; import android.os.Handler.Callback; import android.os.Looper; import android.os.Message; import android.support.annotation.LayoutRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; import android.support.v4.util.Pools.SynchronizedPool; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import java.util.concurrent.ArrayBlockingQueue; /** *
Helper class for inflating layouts asynchronously. To use, construct * an instance of {@link AsyncLayoutInflater} on the UI thread and call * {@link #inflate(int, ViewGroup, OnInflateFinishedListener)}. The * {@link OnInflateFinishedListener} will be invoked on the UI thread * when the inflate request has completed. * *
This is intended for parts of the UI that are created lazily or in * response to user interactions. This allows the UI thread to continue * to be responsive & animate while the relatively heavy inflate * is being performed. * *
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. * *
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. * *
This inflater does not support setting a {@link LayoutInflater.Factory}
* nor {@link LayoutInflater.Factory2}. Similarly it does not support inflating
* layouts that contain fragments.
*/
public final class AsyncLayoutInflater {
private static final String TAG = "AsyncLayoutInflater";
LayoutInflater mInflater;
Handler mHandler;
InflateThread mInflateThread;
public AsyncLayoutInflater(@NonNull Context context) {
mInflater = new BasicInflater(context);
mHandler = new Handler(mHandlerCallback);
mInflateThread = InflateThread.getInstance();
}
@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);
}
private Callback mHandlerCallback = new Callback() {
@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;
}
};
public interface OnInflateFinishedListener {
void onInflateFinished(View view, int resid, ViewGroup parent);
}
private static class InflateRequest {
AsyncLayoutInflater inflater;
ViewGroup parent;
int resid;
View view;
OnInflateFinishedListener callback;
InflateRequest() {
}
}
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 {
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);
}
}
private static class InflateThread extends Thread {
private static final InflateThread sInstance;
static {
sInstance = new InflateThread();
sInstance.start();
}
public static InflateThread getInstance() {
return sInstance;
}
private ArrayBlockingQueue