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.util.SparseArray; 21 22 import android.util.Pools.Pool; 23 import android.util.Pools.SimplePool; 24 25 /** 26 * Bitmap pool backed by a sparse array indexing linked lists of bitmaps 27 * sharing the same width. Performance will degrade if using this to store 28 * many bitmaps with the same width but many different heights. 29 */ 30 public class SparseArrayBitmapPool { 31 32 private int mCapacityBytes; 33 private SparseArray<Node> mStore = new SparseArray<Node>(); 34 private int mSizeBytes = 0; 35 36 private Pool<Node> mNodePool; 37 private Node mPoolNodesHead = null; 38 private Node mPoolNodesTail = null; 39 40 protected static class Node { 41 Bitmap bitmap; 42 43 // Each node is part of two doubly linked lists: 44 // - A pool-level list (accessed by mPoolNodesHead and mPoolNodesTail) 45 // that is used for FIFO eviction of nodes when the pool gets full. 46 // - A bucket-level list for each index of the sparse array, so that 47 // each index can store more than one item. 48 Node prevInBucket; 49 Node nextInBucket; 50 Node nextInPool; 51 Node prevInPool; 52 } 53 54 /** 55 * @param capacityBytes Maximum capacity of the pool in bytes. 56 * @param nodePool Shared pool to use for recycling linked list nodes, or null. 57 */ SparseArrayBitmapPool(int capacityBytes, Pool<Node> nodePool)58 public SparseArrayBitmapPool(int capacityBytes, Pool<Node> nodePool) { 59 mCapacityBytes = capacityBytes; 60 if (nodePool == null) { 61 mNodePool = new SimplePool<Node>(32); 62 } else { 63 mNodePool = nodePool; 64 } 65 } 66 67 /** 68 * Set the maximum capacity of the pool, and if necessary trim it down to size. 69 */ setCapacity(int capacityBytes)70 public synchronized void setCapacity(int capacityBytes) { 71 mCapacityBytes = capacityBytes; 72 73 // No-op unless current size exceeds the new capacity. 74 freeUpCapacity(0); 75 } 76 freeUpCapacity(int bytesNeeded)77 private void freeUpCapacity(int bytesNeeded) { 78 int targetSize = mCapacityBytes - bytesNeeded; 79 // Repeatedly remove the oldest node until we have freed up at least bytesNeeded. 80 while (mPoolNodesTail != null && mSizeBytes > targetSize) { 81 unlinkAndRecycleNode(mPoolNodesTail, true); 82 } 83 } 84 unlinkAndRecycleNode(Node n, boolean recycleBitmap)85 private void unlinkAndRecycleNode(Node n, boolean recycleBitmap) { 86 // Unlink the node from its sparse array bucket list. 87 if (n.prevInBucket != null) { 88 // This wasn't the head, update the previous node. 89 n.prevInBucket.nextInBucket = n.nextInBucket; 90 } else { 91 // This was the head of the bucket, replace it with the next node. 92 mStore.put(n.bitmap.getWidth(), n.nextInBucket); 93 } 94 if (n.nextInBucket != null) { 95 // This wasn't the tail, update the next node. 96 n.nextInBucket.prevInBucket = n.prevInBucket; 97 } 98 99 // Unlink the node from the pool-wide list. 100 if (n.prevInPool != null) { 101 // This wasn't the head, update the previous node. 102 n.prevInPool.nextInPool = n.nextInPool; 103 } else { 104 // This was the head of the pool-wide list, update the head pointer. 105 mPoolNodesHead = n.nextInPool; 106 } 107 if (n.nextInPool != null) { 108 // This wasn't the tail, update the next node. 109 n.nextInPool.prevInPool = n.prevInPool; 110 } else { 111 // This was the tail, update the tail pointer. 112 mPoolNodesTail = n.prevInPool; 113 } 114 115 // Recycle the node. 116 n.nextInBucket = null; 117 n.nextInPool = null; 118 n.prevInBucket = null; 119 n.prevInPool = null; 120 mSizeBytes -= n.bitmap.getByteCount(); 121 if (recycleBitmap) n.bitmap.recycle(); 122 n.bitmap = null; 123 mNodePool.release(n); 124 } 125 126 /** 127 * @return Capacity of the pool in bytes. 128 */ getCapacity()129 public synchronized int getCapacity() { 130 return mCapacityBytes; 131 } 132 133 /** 134 * @return Total size in bytes of the bitmaps stored in the pool. 135 */ getSize()136 public synchronized int getSize() { 137 return mSizeBytes; 138 } 139 140 /** 141 * @return Bitmap from the pool with the desired height/width or null if none available. 142 */ get(int width, int height)143 public synchronized Bitmap get(int width, int height) { 144 Node cur = mStore.get(width); 145 146 // Traverse the list corresponding to the width bucket in the 147 // sparse array, and unlink and return the first bitmap that 148 // also has the correct height. 149 while (cur != null) { 150 if (cur.bitmap.getHeight() == height) { 151 Bitmap b = cur.bitmap; 152 unlinkAndRecycleNode(cur, false); 153 return b; 154 } 155 cur = cur.nextInBucket; 156 } 157 return null; 158 } 159 160 /** 161 * Adds the given bitmap to the pool. 162 * @return Whether the bitmap was added to the pool. 163 */ put(Bitmap b)164 public synchronized boolean put(Bitmap b) { 165 if (b == null) { 166 return false; 167 } 168 169 // Ensure there is enough room to contain the new bitmap. 170 int bytes = b.getByteCount(); 171 freeUpCapacity(bytes); 172 173 Node newNode = mNodePool.acquire(); 174 if (newNode == null) { 175 newNode = new Node(); 176 } 177 newNode.bitmap = b; 178 179 // We append to the head, and freeUpCapacity clears from the tail, 180 // resulting in FIFO eviction. 181 newNode.prevInBucket = null; 182 newNode.prevInPool = null; 183 newNode.nextInPool = mPoolNodesHead; 184 mPoolNodesHead = newNode; 185 186 // Insert the node into its appropriate bucket based on width. 187 int key = b.getWidth(); 188 newNode.nextInBucket = mStore.get(key); 189 if (newNode.nextInBucket != null) { 190 // The bucket already had nodes, update the old head. 191 newNode.nextInBucket.prevInBucket = newNode; 192 } 193 mStore.put(key, newNode); 194 195 if (newNode.nextInPool == null) { 196 // This is the only node in the list, update the tail pointer. 197 mPoolNodesTail = newNode; 198 } else { 199 newNode.nextInPool.prevInPool = newNode; 200 } 201 mSizeBytes += bytes; 202 return true; 203 } 204 205 /** 206 * Empty the pool, recycling all the bitmaps currently in it. 207 */ clear()208 public synchronized void clear() { 209 // Clearing is equivalent to ensuring all the capacity is available. 210 freeUpCapacity(mCapacityBytes); 211 } 212 } 213