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 save 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