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.view.LayoutInflater; 21 import android.view.View; 22 import android.view.ViewGroup; 23 24 import androidx.annotation.AnyThread; 25 import androidx.annotation.Nullable; 26 import androidx.annotation.UiThread; 27 28 import com.android.launcher3.util.ViewPool.Reusable; 29 30 /** 31 * Utility class to maintain a pool of reusable views. 32 * During initialization, views are inflated on the background thread. 33 */ 34 public class ViewPool<T extends View & Reusable> { 35 36 private final Object[] mPool; 37 38 private final LayoutInflater mInflater; 39 private final ViewGroup mParent; 40 private final int mLayoutId; 41 42 private int mCurrentSize = 0; 43 ViewPool(Context context, @Nullable ViewGroup parent, int layoutId, int maxSize, int initialSize)44 public ViewPool(Context context, @Nullable ViewGroup parent, 45 int layoutId, int maxSize, int initialSize) { 46 mLayoutId = layoutId; 47 mParent = parent; 48 mInflater = LayoutInflater.from(context); 49 mPool = new Object[maxSize]; 50 51 if (initialSize > 0) { 52 initPool(initialSize); 53 } 54 } 55 56 @UiThread initPool(int initialSize)57 private void initPool(int initialSize) { 58 Preconditions.assertUIThread(); 59 Handler handler = new Handler(); 60 61 // LayoutInflater is not thread safe as it maintains a global variable 'mConstructorArgs'. 62 // Create a different copy to use on the background thread. 63 LayoutInflater inflater = mInflater.cloneInContext(mInflater.getContext()); 64 65 // Inflate views on a non looper thread. This allows us to catch errors like calling 66 // "new Handler()" in constructor easily. 67 new Thread(() -> { 68 for (int i = 0; i < initialSize; i++) { 69 T view = inflateNewView(inflater); 70 handler.post(() -> addToPool(view)); 71 } 72 }, "ViewPool-init").start(); 73 } 74 75 @UiThread recycle(T view)76 public void recycle(T view) { 77 Preconditions.assertUIThread(); 78 view.onRecycle(); 79 addToPool(view); 80 } 81 82 @UiThread addToPool(T view)83 private void addToPool(T view) { 84 Preconditions.assertUIThread(); 85 if (mCurrentSize >= mPool.length) { 86 // pool is full 87 return; 88 } 89 90 mPool[mCurrentSize] = view; 91 mCurrentSize++; 92 } 93 94 @UiThread getView()95 public T getView() { 96 Preconditions.assertUIThread(); 97 if (mCurrentSize > 0) { 98 mCurrentSize--; 99 return (T) mPool[mCurrentSize]; 100 } 101 return inflateNewView(mInflater); 102 } 103 104 @AnyThread inflateNewView(LayoutInflater inflater)105 private T inflateNewView(LayoutInflater inflater) { 106 return (T) inflater.inflate(mLayoutId, mParent, false); 107 } 108 109 /** 110 * Interface to indicate that a view is reusable 111 */ 112 public interface Reusable { 113 114 /** 115 * Called when a view is recycled / added back to the pool 116 */ onRecycle()117 void onRecycle(); 118 } 119 } 120