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.bitmap; 18 19 import android.support.v4.util.LruCache; 20 21 import java.util.LinkedHashMap; 22 import java.util.Map; 23 import java.util.concurrent.LinkedBlockingQueue; 24 25 26 /** 27 * An alternative implementation of a pool+cache. This implementation only counts 28 * unreferenced objects in its size calculation. Internally, it never evicts from 29 * its cache, and instead {@link #poll()} is allowed to return unreferenced cache 30 * entries. 31 * <p> 32 * You would only use this kind of cache if your objects are interchangeable and 33 * have significant allocation cost, and if your memory footprint is somewhat 34 * flexible. 35 * <p> 36 * Because this class only counts unreferenced objects toward targetSize, 37 * it will have a total memory footprint of: 38 * <code>(targetSize) + (# of threads concurrently writing to cache) + 39 * (total size of still-referenced entries)</code> 40 * 41 */ 42 public class AltPooledCache<K, V extends Poolable> implements PooledCache<K, V> { 43 44 private final LinkedHashMap<K, V> mCache; 45 private final LinkedBlockingQueue<V> mPool; 46 private final int mTargetSize; 47 private final LruCache<K, V> mNonPooledCache; 48 49 private final boolean DEBUG = DecodeTask.DEBUG; 50 51 /** 52 * @param targetSize not exactly a max size in practice 53 * @param nonPooledFraction the fractional portion in the range [0.0,1.0] of targetSize to 54 * dedicate to non-poolable entries 55 */ 56 public AltPooledCache(int targetSize, float nonPooledFraction) { 57 mCache = new LinkedHashMap<K, V>(0, 0.75f, true); 58 mPool = new LinkedBlockingQueue<V>(); 59 final int nonPooledSize = Math.round(targetSize * nonPooledFraction); 60 if (nonPooledSize > 0) { 61 mNonPooledCache = new NonPooledCache(nonPooledSize); 62 } else { 63 mNonPooledCache = null; 64 } 65 mTargetSize = targetSize - nonPooledSize; 66 } 67 68 @Override 69 public V get(K key, boolean incrementRefCount) { 70 synchronized (mCache) { 71 V result = mCache.get(key); 72 if (result == null && mNonPooledCache != null) { 73 result = mNonPooledCache.get(key); 74 } 75 if (incrementRefCount && result != null) { 76 result.acquireReference(); 77 } 78 return result; 79 } 80 } 81 82 @Override 83 public V put(K key, V value) { 84 synchronized (mCache) { 85 final V prev; 86 if (value.isEligibleForPooling()) { 87 prev = mCache.put(key, value); 88 } else if (mNonPooledCache != null) { 89 prev = mNonPooledCache.put(key, value); 90 } else { 91 prev = null; 92 } 93 return prev; 94 } 95 } 96 97 @Override 98 public void offer(V value) { 99 if (value.getRefCount() != 0 || !value.isEligibleForPooling()) { 100 throw new IllegalArgumentException("unexpected offer of an invalid object: " + value); 101 } 102 mPool.offer(value); 103 } 104 105 @Override 106 public V poll() { 107 final V pooled = mPool.poll(); 108 if (pooled != null) { 109 return pooled; 110 } 111 112 synchronized (mCache) { 113 int unrefSize = 0; 114 Map.Entry<K, V> eldestUnref = null; 115 for (Map.Entry<K, V> entry : mCache.entrySet()) { 116 final V value = entry.getValue(); 117 if (value.getRefCount() > 0 || !value.isEligibleForPooling()) { 118 continue; 119 } 120 if (eldestUnref == null) { 121 eldestUnref = entry; 122 } 123 unrefSize += sizeOf(value); 124 if (unrefSize > mTargetSize) { 125 break; 126 } 127 } 128 // only return a scavenged cache entry if the cache has enough 129 // eligible (unreferenced) items 130 if (unrefSize <= mTargetSize) { 131 if (DEBUG) System.err.println( 132 "POOL SCAVENGE FAILED, cache not fully warm yet. szDelta=" 133 + (mTargetSize-unrefSize)); 134 return null; 135 } else { 136 mCache.remove(eldestUnref.getKey()); 137 if (DEBUG) System.err.println("POOL SCAVENGE SUCCESS, oldKey=" 138 + eldestUnref.getKey()); 139 return eldestUnref.getValue(); 140 } 141 } 142 } 143 144 protected int sizeOf(V value) { 145 return 1; 146 } 147 148 @Override 149 public String toDebugString() { 150 if (DEBUG) { 151 final StringBuilder sb = new StringBuilder("["); 152 sb.append(super.toString()); 153 int size = 0; 154 synchronized (mCache) { 155 sb.append(" poolCount="); 156 sb.append(mPool.size()); 157 sb.append(" cacheSize="); 158 sb.append(mCache.size()); 159 if (mNonPooledCache != null) { 160 sb.append(" nonPooledCacheSize="); 161 sb.append(mNonPooledCache.size()); 162 } 163 sb.append("\n---------------------"); 164 for (V val : mPool) { 165 size += sizeOf(val); 166 sb.append("\n\tpool item: "); 167 sb.append(val); 168 } 169 sb.append("\n---------------------"); 170 for (Map.Entry<K, V> item : mCache.entrySet()) { 171 final V val = item.getValue(); 172 sb.append("\n\tcache key="); 173 sb.append(item.getKey()); 174 sb.append(" val="); 175 sb.append(val); 176 size += sizeOf(val); 177 } 178 sb.append("\n---------------------"); 179 if (mNonPooledCache != null) { 180 for (Map.Entry<K, V> item : mNonPooledCache.snapshot().entrySet()) { 181 final V val = item.getValue(); 182 sb.append("\n\tnon-pooled cache key="); 183 sb.append(item.getKey()); 184 sb.append(" val="); 185 sb.append(val); 186 size += sizeOf(val); 187 } 188 sb.append("\n---------------------"); 189 } 190 sb.append("\nTOTAL SIZE=" + size); 191 } 192 sb.append("]"); 193 return sb.toString(); 194 } else { 195 return null; 196 } 197 } 198 199 private class NonPooledCache extends LruCache<K, V> { 200 201 public NonPooledCache(int maxSize) { 202 super(maxSize); 203 } 204 205 @Override 206 protected int sizeOf(K key, V value) { 207 return AltPooledCache.this.sizeOf(value); 208 } 209 210 } 211 212 } 213