blob: 95e10267b28130fa45285973cfe31091f2e42352 [file] [log] [blame]
/*
* 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);
}
}