diff options
Diffstat (limited to 'src/com/android/photos/data/SparseArrayBitmapPool.java')
-rw-r--r-- | src/com/android/photos/data/SparseArrayBitmapPool.java | 212 |
1 files changed, 212 insertions, 0 deletions
diff --git a/src/com/android/photos/data/SparseArrayBitmapPool.java b/src/com/android/photos/data/SparseArrayBitmapPool.java new file mode 100644 index 000000000..95e10267b --- /dev/null +++ b/src/com/android/photos/data/SparseArrayBitmapPool.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.photos.data; + +import android.graphics.Bitmap; +import android.util.SparseArray; + +import android.util.Pools.Pool; +import android.util.Pools.SimplePool; + +/** + * Bitmap pool backed by a sparse array indexing linked lists of bitmaps + * sharing the same width. Performance will degrade if using this to store + * many bitmaps with the same width but many different heights. + */ +public class SparseArrayBitmapPool { + + private int mCapacityBytes; + private SparseArray<Node> mStore = new SparseArray<Node>(); + private int mSizeBytes = 0; + + private Pool<Node> mNodePool; + private Node mPoolNodesHead = null; + private Node mPoolNodesTail = null; + + protected static class Node { + Bitmap bitmap; + + // Each node is part of two doubly linked lists: + // - A pool-level list (accessed by mPoolNodesHead and mPoolNodesTail) + // that is used for FIFO eviction of nodes when the pool gets full. + // - A bucket-level list for each index of the sparse array, so that + // each index can store more than one item. + Node prevInBucket; + Node nextInBucket; + Node nextInPool; + Node prevInPool; + } + + /** + * @param capacityBytes Maximum capacity of the pool in bytes. + * @param nodePool Shared pool to use for recycling linked list nodes, or null. + */ + public SparseArrayBitmapPool(int capacityBytes, Pool<Node> nodePool) { + mCapacityBytes = capacityBytes; + if (nodePool == null) { + mNodePool = new SimplePool<Node>(32); + } else { + mNodePool = nodePool; + } + } + + /** + * Set the maximum capacity of the pool, and if necessary trim it down to size. + */ + public synchronized void setCapacity(int capacityBytes) { + mCapacityBytes = capacityBytes; + + // No-op unless current size exceeds the new capacity. + freeUpCapacity(0); + } + + private void freeUpCapacity(int bytesNeeded) { + int targetSize = mCapacityBytes - bytesNeeded; + // Repeatedly remove the oldest node until we have freed up at least bytesNeeded. + while (mPoolNodesTail != null && mSizeBytes > targetSize) { + unlinkAndRecycleNode(mPoolNodesTail, true); + } + } + + private void unlinkAndRecycleNode(Node n, boolean recycleBitmap) { + // Unlink the node from its sparse array bucket list. + if (n.prevInBucket != null) { + // This wasn't the head, update the previous node. + n.prevInBucket.nextInBucket = n.nextInBucket; + } else { + // This was the head of the bucket, replace it with the next node. + mStore.put(n.bitmap.getWidth(), n.nextInBucket); + } + if (n.nextInBucket != null) { + // This wasn't the tail, update the next node. + n.nextInBucket.prevInBucket = n.prevInBucket; + } + + // Unlink the node from the pool-wide list. + if (n.prevInPool != null) { + // This wasn't the head, update the previous node. + n.prevInPool.nextInPool = n.nextInPool; + } else { + // This was the head of the pool-wide list, update the head pointer. + mPoolNodesHead = n.nextInPool; + } + if (n.nextInPool != null) { + // This wasn't the tail, update the next node. + n.nextInPool.prevInPool = n.prevInPool; + } else { + // This was the tail, update the tail pointer. + mPoolNodesTail = n.prevInPool; + } + + // Recycle the node. + n.nextInBucket = null; + n.nextInPool = null; + n.prevInBucket = null; + n.prevInPool = null; + mSizeBytes -= n.bitmap.getByteCount(); + if (recycleBitmap) n.bitmap.recycle(); + n.bitmap = null; + mNodePool.release(n); + } + + /** + * @return Capacity of the pool in bytes. + */ + public synchronized int getCapacity() { + return mCapacityBytes; + } + + /** + * @return Total size in bytes of the bitmaps stored in the pool. + */ + public synchronized int getSize() { + return mSizeBytes; + } + + /** + * @return Bitmap from the pool with the desired height/width or null if none available. + */ + public synchronized Bitmap get(int width, int height) { + Node cur = mStore.get(width); + + // Traverse the list corresponding to the width bucket in the + // sparse array, and unlink and return the first bitmap that + // also has the correct height. + while (cur != null) { + if (cur.bitmap.getHeight() == height) { + Bitmap b = cur.bitmap; + unlinkAndRecycleNode(cur, false); + return b; + } + cur = cur.nextInBucket; + } + return null; + } + + /** + * Adds the given bitmap to the pool. + * @return Whether the bitmap was added to the pool. + */ + public synchronized boolean put(Bitmap b) { + if (b == null) { + return false; + } + + // Ensure there is enough room to contain the new bitmap. + int bytes = b.getByteCount(); + freeUpCapacity(bytes); + + Node newNode = mNodePool.acquire(); + if (newNode == null) { + newNode = new Node(); + } + newNode.bitmap = b; + + // We append to the head, and freeUpCapacity clears from the tail, + // resulting in FIFO eviction. + newNode.prevInBucket = null; + newNode.prevInPool = null; + newNode.nextInPool = mPoolNodesHead; + mPoolNodesHead = newNode; + + // Insert the node into its appropriate bucket based on width. + int key = b.getWidth(); + newNode.nextInBucket = mStore.get(key); + if (newNode.nextInBucket != null) { + // The bucket already had nodes, update the old head. + newNode.nextInBucket.prevInBucket = newNode; + } + mStore.put(key, newNode); + + if (newNode.nextInPool == null) { + // This is the only node in the list, update the tail pointer. + mPoolNodesTail = newNode; + } else { + newNode.nextInPool.prevInPool = newNode; + } + mSizeBytes += bytes; + return true; + } + + /** + * Empty the pool, recycling all the bitmaps currently in it. + */ + public synchronized void clear() { + // Clearing is equivalent to ensuring all the capacity is available. + freeUpCapacity(mCapacityBytes); + } +} |