1 /*
2  * Copyright (C) 2014 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 
17 package com.android.tv.settings.widget;
18 
19 import android.graphics.Bitmap;
20 import android.util.Log;
21 import android.util.SparseArray;
22 
23 import java.lang.ref.SoftReference;
24 import java.lang.reflect.InvocationTargetException;
25 import java.lang.reflect.Method;
26 import java.util.ArrayList;
27 
28 // FIXME: this class saves recycle bitmap as SoftReference,  which is too vulnerable to
29 // be garbage collected due to other part of application is re-allocating lots of
30 // memory,  we will lose all SoftReference in a GC run.  We should maintain
31 // certain amount of recycled bitmaps in memory, we may also need remove bitmap from LRUCache
32 // if we are not able to get a recycled bitmap here.
33 public class RecycleBitmapPool {
34 
35     private static final String TAG = "RecycleBitmapPool";
36     private static final boolean DEBUG = false;
37     // allow reuse bitmap with larger bytes, set to 0 to disable different byte size
38     // FIXME: wait b/10608305 to be fixed then turn back on
39     private static final int LARGER_BITMAP_ALLOWED_REUSE = 0;
40     private static final boolean LARGER_BITMAP_ALLOWED = LARGER_BITMAP_ALLOWED_REUSE > 0;
41 
42     private static Method sGetAllocationByteCount;
43 
44     static {
45         try {
46             // KLP or later
47             sGetAllocationByteCount = Bitmap.class.getMethod("getAllocationByteCount");
48         } catch (NoSuchMethodException e) {
49         }
50     }
51 
52     private final SparseArray<ArrayList<SoftReference<Bitmap>>> mRecycled8888 =
53             new SparseArray<ArrayList<SoftReference<Bitmap>>>();
54 
RecycleBitmapPool()55     public RecycleBitmapPool() {
56     }
57 
getSize(Bitmap bitmap)58     public static int getSize(Bitmap bitmap) {
59         if (sGetAllocationByteCount != null) {
60             try {
61                 return (Integer) sGetAllocationByteCount.invoke(bitmap);
62             } catch (IllegalArgumentException e) {
63                 Log.e(TAG, "getAllocationByteCount() failed", e);
64             } catch (IllegalAccessException e) {
65                 Log.e(TAG, "getAllocationByteCount() failed", e);
66             } catch (InvocationTargetException e) {
67                 Log.e(TAG, "getAllocationByteCount() failed", e);
68             }
69             sGetAllocationByteCount = null;
70         }
71         return bitmap.getByteCount();
72     }
73 
getSize(int width, int height)74     private static int getSize(int width, int height) {
75         if (width >= 2048 || height >= 2048) {
76             return 0;
77         }
78         return width * height * 4;
79     }
80 
addRecycledBitmap(Bitmap bitmap)81     public void addRecycledBitmap(Bitmap bitmap) {
82         if (bitmap.isRecycled()) {
83             return;
84         }
85         Bitmap.Config config = bitmap.getConfig();
86         if (config != Bitmap.Config.ARGB_8888) {
87             return;
88         }
89         int key = getSize(bitmap);
90         if (key == 0) {
91             return;
92         }
93         synchronized (mRecycled8888) {
94             ArrayList<SoftReference<Bitmap>> list = mRecycled8888.get(key);
95             if (list == null) {
96                 list = new ArrayList<SoftReference<Bitmap>>();
97                 mRecycled8888.put(key, list);
98             }
99             list.add(new SoftReference<Bitmap>(bitmap));
100             if (DEBUG) {
101                 Log.d(TAG, list.size() + " add bitmap " + bitmap.getWidth() + " "
102                         + bitmap.getHeight());
103             }
104         }
105     }
106 
getRecycledBitmap(int width, int height)107     public Bitmap getRecycledBitmap(int width, int height) {
108         int key = getSize(width, height);
109         if (key == 0) {
110             return null;
111         }
112         synchronized (mRecycled8888) {
113             // for the new version with getAllocationByteCount(), we allow larger size
114             // to be reused for the bitmap,  otherwise we just looks for same size
115             Bitmap bitmap = getRecycledBitmap(mRecycled8888.get(key));
116             if (sGetAllocationByteCount == null || bitmap != null) {
117                 return bitmap;
118             }
119             if (LARGER_BITMAP_ALLOWED) {
120                 for (int i = 0, c = mRecycled8888.size(); i < c; i++) {
121                     int k = mRecycled8888.keyAt(i);
122                     if (k > key && k <= key * LARGER_BITMAP_ALLOWED_REUSE) {
123                         bitmap = getRecycledBitmap(mRecycled8888.valueAt(i));
124                         if (bitmap != null) {
125                             return bitmap;
126                         }
127                     }
128                 }
129             }
130         }
131         if (DEBUG) {
132             Log.d(TAG, "not avaialbe for " + width + "," + height);
133         }
134         return null;
135     }
136 
getRecycledBitmap(ArrayList<SoftReference<Bitmap>> list)137     private static Bitmap getRecycledBitmap(ArrayList<SoftReference<Bitmap>> list) {
138         if (list != null && !list.isEmpty()) {
139             while (!list.isEmpty()) {
140                 SoftReference<Bitmap> ref = list.remove(list.size() - 1);
141                 Bitmap bitmap = ref.get();
142                 if (bitmap != null && !bitmap.isRecycled()) {
143                     if (DEBUG) {
144                         Log.d(TAG, "reuse " + bitmap.getWidth() + " " + bitmap.getHeight());
145                     }
146                     return bitmap;
147                 } else {
148                     if (DEBUG) {
149                         Log.d(TAG, " we lost SoftReference to bitmap");
150                     }
151                 }
152             }
153         }
154         return null;
155     }
156 }
157