1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.launcher3.util; 17 18 import android.content.Context; 19 import android.os.Handler; 20 import android.util.Log; 21 import android.view.LayoutInflater; 22 import android.view.View; 23 import android.view.ViewGroup; 24 25 import androidx.annotation.AnyThread; 26 import androidx.annotation.Nullable; 27 import androidx.annotation.UiThread; 28 import androidx.annotation.VisibleForTesting; 29 30 import com.android.launcher3.util.ViewPool.Reusable; 31 32 /** 33 * Utility class to maintain a pool of reusable views. 34 * During initialization, views are inflated on the background thread. 35 */ 36 public class ViewPool<T extends View & Reusable> { 37 private static final String TAG = ViewPool.class.getSimpleName(); 38 39 private final Object[] mPool; 40 41 private final LayoutInflater mInflater; 42 private final ViewGroup mParent; 43 private final int mLayoutId; 44 45 private int mCurrentSize = 0; 46 47 @Nullable 48 private Thread mViewPoolInitThread; 49 ViewPool(Context context, @Nullable ViewGroup parent, int layoutId, int maxSize, int initialSize)50 public ViewPool(Context context, @Nullable ViewGroup parent, 51 int layoutId, int maxSize, int initialSize) { 52 this(LayoutInflater.from(context).cloneInContext(context), 53 parent, layoutId, maxSize, initialSize); 54 } 55 56 @VisibleForTesting ViewPool(LayoutInflater inflater, @Nullable ViewGroup parent, int layoutId, int maxSize, int initialSize)57 ViewPool(LayoutInflater inflater, @Nullable ViewGroup parent, 58 int layoutId, int maxSize, int initialSize) { 59 mLayoutId = layoutId; 60 mParent = parent; 61 mInflater = inflater; 62 mPool = new Object[maxSize]; 63 64 if (initialSize > 0) { 65 initPool(initialSize); 66 } 67 } 68 69 @UiThread initPool(int initialSize)70 private void initPool(int initialSize) { 71 Preconditions.assertUIThread(); 72 Handler handler = new Handler(); 73 74 // LayoutInflater is not thread safe as it maintains a global variable 'mConstructorArgs'. 75 // Create a different copy to use on the background thread. 76 LayoutInflater inflater = mInflater.cloneInContext(mInflater.getContext()); 77 78 // Inflate views on a non looper thread. This allows us to catch errors like calling 79 // "new Handler()" in constructor easily. 80 mViewPoolInitThread = new Thread(() -> { 81 for (int i = 0; i < initialSize; i++) { 82 T view = inflateNewView(inflater); 83 handler.post(() -> addToPool(view)); 84 } 85 Log.d(TAG, "initPool complete"); 86 mViewPoolInitThread = null; 87 }, "ViewPool-init"); 88 mViewPoolInitThread.start(); 89 } 90 91 @UiThread recycle(T view)92 public void recycle(T view) { 93 Preconditions.assertUIThread(); 94 view.onRecycle(); 95 addToPool(view); 96 } 97 98 @UiThread addToPool(T view)99 private void addToPool(T view) { 100 Preconditions.assertUIThread(); 101 if (mCurrentSize >= mPool.length) { 102 // pool is full 103 return; 104 } 105 106 mPool[mCurrentSize] = view; 107 mCurrentSize++; 108 } 109 110 @UiThread getView()111 public T getView() { 112 Preconditions.assertUIThread(); 113 if (mCurrentSize > 0) { 114 mCurrentSize--; 115 return (T) mPool[mCurrentSize]; 116 } 117 return inflateNewView(mInflater); 118 } 119 120 @AnyThread inflateNewView(LayoutInflater inflater)121 private T inflateNewView(LayoutInflater inflater) { 122 return (T) inflater.inflate(mLayoutId, mParent, false); 123 } 124 killOngoingInitializations()125 public void killOngoingInitializations() throws InterruptedException { 126 if (mViewPoolInitThread != null) { 127 mViewPoolInitThread.join(); 128 } 129 } 130 131 /** 132 * Interface to indicate that a view is reusable 133 */ 134 public interface Reusable { 135 136 /** 137 * Called when a view is recycled / added back to the pool 138 */ onRecycle()139 void onRecycle(); 140 } 141 } 142