1 /* 2 * Copyright (C) 2013 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.photos.data; 18 19 import android.graphics.Bitmap; 20 import android.graphics.Point; 21 import android.util.Pools.Pool; 22 import android.util.Pools.SynchronizedPool; 23 24 import com.android.photos.data.SparseArrayBitmapPool.Node; 25 26 /** 27 * Pool allowing the efficient reuse of bitmaps in order to avoid long 28 * garbage collection pauses. 29 */ 30 public class GalleryBitmapPool { 31 32 private static final int CAPACITY_BYTES = 20971520; 33 34 // We found that Gallery uses bitmaps that are either square (for example, 35 // tiles of large images or square thumbnails), match one of the common 36 // photo aspect ratios (4x3, 3x2, or 16x9), or, less commonly, are of some 37 // other aspect ratio. Taking advantage of this information, we use 3 38 // SparseArrayBitmapPool instances to back the GalleryBitmapPool, which affords 39 // O(1) lookups for square bitmaps, and average-case - but *not* asymptotically - 40 // O(1) lookups for common photo aspect ratios and other miscellaneous aspect 41 // ratios. Beware of the pathological case where there are many bitmaps added 42 // to the pool with different non-square aspect ratios but the same width, as 43 // performance will degrade and the average case lookup will approach 44 // O(# of different aspect ratios). 45 private static final int POOL_INDEX_NONE = -1; 46 private static final int POOL_INDEX_SQUARE = 0; 47 private static final int POOL_INDEX_PHOTO = 1; 48 private static final int POOL_INDEX_MISC = 2; 49 50 private static final Point[] COMMON_PHOTO_ASPECT_RATIOS = 51 { new Point(4, 3), new Point(3, 2), new Point(16, 9) }; 52 53 private int mCapacityBytes; 54 private SparseArrayBitmapPool [] mPools; 55 private Pool<Node> mSharedNodePool = new SynchronizedPool<Node>(128); 56 57 private GalleryBitmapPool(int capacityBytes) { 58 mPools = new SparseArrayBitmapPool[3]; 59 mPools[POOL_INDEX_SQUARE] = new SparseArrayBitmapPool(capacityBytes / 3, mSharedNodePool); 60 mPools[POOL_INDEX_PHOTO] = new SparseArrayBitmapPool(capacityBytes / 3, mSharedNodePool); 61 mPools[POOL_INDEX_MISC] = new SparseArrayBitmapPool(capacityBytes / 3, mSharedNodePool); 62 mCapacityBytes = capacityBytes; 63 } 64 65 private static GalleryBitmapPool sInstance = new GalleryBitmapPool(CAPACITY_BYTES); 66 67 public static GalleryBitmapPool getInstance() { 68 return sInstance; 69 } 70 71 private SparseArrayBitmapPool getPoolForDimensions(int width, int height) { 72 int index = getPoolIndexForDimensions(width, height); 73 if (index == POOL_INDEX_NONE) { 74 return null; 75 } else { 76 return mPools[index]; 77 } 78 } 79 80 private int getPoolIndexForDimensions(int width, int height) { 81 if (width <= 0 || height <= 0) { 82 return POOL_INDEX_NONE; 83 } 84 if (width == height) { 85 return POOL_INDEX_SQUARE; 86 } 87 int min, max; 88 if (width > height) { 89 min = height; 90 max = width; 91 } else { 92 min = width; 93 max = height; 94 } 95 for (Point ar : COMMON_PHOTO_ASPECT_RATIOS) { 96 if (min * ar.x == max * ar.y) { 97 return POOL_INDEX_PHOTO; 98 } 99 } 100 return POOL_INDEX_MISC; 101 } 102 103 /** 104 * @return Capacity of the pool in bytes. 105 */ 106 public synchronized int getCapacity() { 107 return mCapacityBytes; 108 } 109 110 /** 111 * @return Approximate total size in bytes of the bitmaps stored in the pool. 112 */ 113 public int getSize() { 114 // Note that this only returns an approximate size, since multiple threads 115 // might be getting and putting Bitmaps from the pool and we lock at the 116 // sub-pool level to avoid unnecessary blocking. 117 int total = 0; 118 for (SparseArrayBitmapPool p : mPools) { 119 total += p.getSize(); 120 } 121 return total; 122 } 123 124 /** 125 * @return Bitmap from the pool with the desired height/width or null if none available. 126 */ 127 public Bitmap get(int width, int height) { 128 SparseArrayBitmapPool pool = getPoolForDimensions(width, height); 129 if (pool == null) { 130 return null; 131 } else { 132 return pool.get(width, height); 133 } 134 } 135 136 /** 137 * Adds the given bitmap to the pool. 138 * @return Whether the bitmap was added to the pool. 139 */ 140 public boolean put(Bitmap b) { 141 if (b == null || b.getConfig() != Bitmap.Config.ARGB_8888) { 142 return false; 143 } 144 SparseArrayBitmapPool pool = getPoolForDimensions(b.getWidth(), b.getHeight()); 145 if (pool == null) { 146 b.recycle(); 147 return false; 148 } else { 149 return pool.put(b); 150 } 151 } 152 153 /** 154 * Empty the pool, recycling all the bitmaps currently in it. 155 */ 156 public void clear() { 157 for (SparseArrayBitmapPool p : mPools) { 158 p.clear(); 159 } 160 } 161 } 162