1c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck/* 2c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * Copyright (C) 2015 The Android Open Source Project 3c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * 4c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * Licensed under the Apache License, Version 2.0 (the "License"); 5c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * you may not use this file except in compliance with the License. 6c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * You may obtain a copy of the License at 7c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * 8c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * http://www.apache.org/licenses/LICENSE-2.0 9c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * 10c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * Unless required by applicable law or agreed to in writing, software 11c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * distributed under the License is distributed on an "AS IS" BASIS, 12c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * See the License for the specific language governing permissions and 14c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * limitations under the License. 15c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck */ 16c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck 17c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckpackage android.support.v4.view; 18c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck 1989f7eba0480c5c9603b295640fcd8a354be2ea20John Reckimport android.content.Context; 20c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckimport android.os.Handler; 21c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckimport android.os.Handler.Callback; 22c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckimport android.os.Looper; 23c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckimport android.os.Message; 24c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckimport android.support.annotation.LayoutRes; 25c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckimport android.support.annotation.NonNull; 26c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckimport android.support.annotation.Nullable; 27c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckimport android.support.annotation.UiThread; 28c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckimport android.support.v4.util.Pools.SynchronizedPool; 2989f7eba0480c5c9603b295640fcd8a354be2ea20John Reckimport android.util.AttributeSet; 30c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckimport android.util.Log; 31c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckimport android.view.LayoutInflater; 32c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckimport android.view.View; 33c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckimport android.view.ViewGroup; 34c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck 35c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckimport java.util.concurrent.ArrayBlockingQueue; 36c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck 37c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck/** 38c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * <p>Helper class for inflating layouts asynchronously. To use, construct 39c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * an instance of {@link AsyncLayoutInflater} on the UI thread and call 40c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * {@link #inflate(int, ViewGroup, OnInflateFinishedListener)}. The 41c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * {@link OnInflateFinishedListener} will be invoked on the UI thread 42c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * when the inflate request has completed. 43c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * 44c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * <p>This is intended for parts of the UI that are created lazily or in 45c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * response to user interactions. This allows the UI thread to continue 46c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * to be responsive & animate while the relatively heavy inflate 47c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * is being performed. 48c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * 49c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * <p>For a layout to be inflated asynchronously it needs to have a parent 50c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * whose {@link ViewGroup#generateLayoutParams(AttributeSet)} is thread-safe 51c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * and all the Views being constructed as part of inflation must not create 52c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * any {@link Handler}s or otherwise call {@link Looper#myLooper()}. If the 53c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * layout that is trying to be inflated cannot be constructed 54c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * asynchronously for whatever reason, {@link AsyncLayoutInflater} will 55c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * automatically fall back to inflating on the UI thread. 56c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * 57c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * <p>NOTE that the inflated View hierarchy is NOT added to the parent. It is 58c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * equivalent to calling {@link LayoutInflater#inflate(int, ViewGroup, boolean)} 59c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * with attachToRoot set to false. Callers will likely want to call 60c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * {@link ViewGroup#addView(View)} in the {@link OnInflateFinishedListener} 61c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * callback at a minimum. 6289f7eba0480c5c9603b295640fcd8a354be2ea20John Reck * 6389f7eba0480c5c9603b295640fcd8a354be2ea20John Reck * <p>This inflater does not support setting a {@link LayoutInflater.Factory} 6489f7eba0480c5c9603b295640fcd8a354be2ea20John Reck * nor {@link LayoutInflater.Factory2}. Similarly it does not support inflating 6589f7eba0480c5c9603b295640fcd8a354be2ea20John Reck * layouts that contain fragments. 66c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck */ 67c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckpublic final class AsyncLayoutInflater { 68c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck private static final String TAG = "AsyncLayoutInflater"; 69c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck 70c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck private LayoutInflater mInflater; 71c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck private Handler mHandler; 72c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck private InflateThread mInflateThread; 73c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck 7489f7eba0480c5c9603b295640fcd8a354be2ea20John Reck public AsyncLayoutInflater(@NonNull Context context) { 7589f7eba0480c5c9603b295640fcd8a354be2ea20John Reck mInflater = new BasicInflater(context); 76c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck mHandler = new Handler(mHandlerCallback); 77c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck mInflateThread = InflateThread.getInstance(); 78c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck } 79c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck 80c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck @UiThread 81c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck public void inflate(@LayoutRes int resid, @Nullable ViewGroup parent, 82c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck @NonNull OnInflateFinishedListener callback) { 83c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck if (callback == null) { 84c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck throw new NullPointerException("callback argument may not be null!"); 85c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck } 86c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck InflateRequest request = mInflateThread.obtainRequest(); 87c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck request.inflater = this; 88c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck request.resid = resid; 89c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck request.parent = parent; 90c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck request.callback = callback; 91c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck mInflateThread.enqueue(request); 92c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck } 93c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck 94c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck private Callback mHandlerCallback = new Callback() { 95c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck @Override 96c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck public boolean handleMessage(Message msg) { 97c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck InflateRequest request = (InflateRequest) msg.obj; 98c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck if (request.view == null) { 99c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck request.view = mInflater.inflate( 100c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck request.resid, request.parent, false); 101c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck } 102c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck request.callback.onInflateFinished( 103c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck request.view, request.resid, request.parent); 104c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck mInflateThread.releaseRequest(request); 105c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck return true; 106c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck } 107c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck }; 108c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck 109c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck public interface OnInflateFinishedListener { 110c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck public void onInflateFinished(View view, int resid, ViewGroup parent); 111c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck } 112c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck 113c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck private static class InflateRequest { 114c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck AsyncLayoutInflater inflater; 115c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck ViewGroup parent; 116c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck int resid; 117c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck View view; 118c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck OnInflateFinishedListener callback; 119c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck } 120c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck 12189f7eba0480c5c9603b295640fcd8a354be2ea20John Reck private static class BasicInflater extends LayoutInflater { 12289f7eba0480c5c9603b295640fcd8a354be2ea20John Reck private static final String[] sClassPrefixList = { 12389f7eba0480c5c9603b295640fcd8a354be2ea20John Reck "android.widget.", 12489f7eba0480c5c9603b295640fcd8a354be2ea20John Reck "android.webkit.", 12589f7eba0480c5c9603b295640fcd8a354be2ea20John Reck "android.app." 12689f7eba0480c5c9603b295640fcd8a354be2ea20John Reck }; 12789f7eba0480c5c9603b295640fcd8a354be2ea20John Reck 12889f7eba0480c5c9603b295640fcd8a354be2ea20John Reck public BasicInflater(Context context) { 12989f7eba0480c5c9603b295640fcd8a354be2ea20John Reck super(context); 13089f7eba0480c5c9603b295640fcd8a354be2ea20John Reck } 13189f7eba0480c5c9603b295640fcd8a354be2ea20John Reck 13289f7eba0480c5c9603b295640fcd8a354be2ea20John Reck @Override 13389f7eba0480c5c9603b295640fcd8a354be2ea20John Reck public LayoutInflater cloneInContext(Context newContext) { 13489f7eba0480c5c9603b295640fcd8a354be2ea20John Reck return new BasicInflater(newContext); 13589f7eba0480c5c9603b295640fcd8a354be2ea20John Reck } 13689f7eba0480c5c9603b295640fcd8a354be2ea20John Reck 13789f7eba0480c5c9603b295640fcd8a354be2ea20John Reck @Override 13889f7eba0480c5c9603b295640fcd8a354be2ea20John Reck protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException { 13989f7eba0480c5c9603b295640fcd8a354be2ea20John Reck for (String prefix : sClassPrefixList) { 14089f7eba0480c5c9603b295640fcd8a354be2ea20John Reck try { 14189f7eba0480c5c9603b295640fcd8a354be2ea20John Reck View view = createView(name, prefix, attrs); 14289f7eba0480c5c9603b295640fcd8a354be2ea20John Reck if (view != null) { 14389f7eba0480c5c9603b295640fcd8a354be2ea20John Reck return view; 14489f7eba0480c5c9603b295640fcd8a354be2ea20John Reck } 14589f7eba0480c5c9603b295640fcd8a354be2ea20John Reck } catch (ClassNotFoundException e) { 14689f7eba0480c5c9603b295640fcd8a354be2ea20John Reck // In this case we want to let the base class take a crack 14789f7eba0480c5c9603b295640fcd8a354be2ea20John Reck // at it. 14889f7eba0480c5c9603b295640fcd8a354be2ea20John Reck } 14989f7eba0480c5c9603b295640fcd8a354be2ea20John Reck } 15089f7eba0480c5c9603b295640fcd8a354be2ea20John Reck 15189f7eba0480c5c9603b295640fcd8a354be2ea20John Reck return super.onCreateView(name, attrs); 15289f7eba0480c5c9603b295640fcd8a354be2ea20John Reck } 15389f7eba0480c5c9603b295640fcd8a354be2ea20John Reck } 15489f7eba0480c5c9603b295640fcd8a354be2ea20John Reck 155c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck private static class InflateThread extends Thread { 156c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck private static final InflateThread sInstance; 157c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck static { 158c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck sInstance = new InflateThread(); 159c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck sInstance.start(); 160c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck } 161c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck 162c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck public static InflateThread getInstance() { 163c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck return sInstance; 164c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck } 165c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck 166c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck private ArrayBlockingQueue<InflateRequest> mQueue 167c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck = new ArrayBlockingQueue<>(10); 168c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck private SynchronizedPool<InflateRequest> mRequestPool 169c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck = new SynchronizedPool<>(10); 170c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck 171c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck @Override 172c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck public void run() { 173c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck while (true) { 174c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck InflateRequest request; 175c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck try { 176c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck request = mQueue.take(); 177c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck } catch (InterruptedException ex) { 178c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck // Odd, just continue 179c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck Log.w(TAG, ex); 180c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck continue; 181c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck } 182c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck 183c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck try { 184c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck request.view = request.inflater.mInflater.inflate( 185c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck request.resid, request.parent, false); 186c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck } catch (RuntimeException ex) { 187c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck // Probably a Looper failure, retry on the UI thread 188c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck Log.w(TAG, "Failed to inflate resource in the background! Retrying on the UI thread", 189c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck ex); 190c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck } 191c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck Message.obtain(request.inflater.mHandler, 0, request) 192c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck .sendToTarget(); 193c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck } 194c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck } 195c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck 196c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck public InflateRequest obtainRequest() { 197c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck InflateRequest obj = mRequestPool.acquire(); 198c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck if (obj == null) { 199c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck obj = new InflateRequest(); 200c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck } 201c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck return obj; 202c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck } 203c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck 204c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck public void releaseRequest(InflateRequest obj) { 205c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck obj.callback = null; 206c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck obj.inflater = null; 207c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck obj.parent = null; 208c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck obj.resid = 0; 209c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck obj.view = null; 210c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck mRequestPool.release(obj); 211c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck } 212c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck 213c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck public void enqueue(InflateRequest request) { 214c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck try { 215c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck mQueue.put(request); 216c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck } catch (InterruptedException e) { 217c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck throw new RuntimeException( 218c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck "Failed to enqueue async inflate request", e); 219c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck } 220c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck } 221c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck } 222c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck} 223