| /* |
| * 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.bitmap; |
| |
| import android.util.Log; |
| import android.util.LruCache; |
| |
| import com.android.bitmap.util.Trace; |
| |
| import java.util.LinkedHashMap; |
| import java.util.Map; |
| import java.util.concurrent.LinkedBlockingQueue; |
| |
| /** |
| * An alternative implementation of a pool+cache. This implementation only counts |
| * unreferenced objects in its size calculation. Internally, it never evicts from |
| * its cache, and instead {@link #poll()} is allowed to return unreferenced cache |
| * entries. |
| * <p> |
| * You would only use this kind of cache if your objects are interchangeable and |
| * have significant allocation cost, and if your memory footprint is somewhat |
| * flexible. |
| * <p> |
| * Because this class only counts unreferenced objects toward targetSize, |
| * it will have a total memory footprint of: |
| * <code>(targetSize) + (# of threads concurrently writing to cache) + |
| * (total size of still-referenced entries)</code> |
| * |
| */ |
| public class UnrefedPooledCache<K, V extends Poolable> implements PooledCache<K, V> { |
| |
| private final LinkedHashMap<K, V> mCache; |
| private final LinkedBlockingQueue<V> mPool; |
| private final int mTargetSize; |
| private final LruCache<K, V> mNonPooledCache; |
| |
| private static final boolean DEBUG = DecodeTask.DEBUG; |
| private static final String TAG = UnrefedPooledCache.class.getSimpleName(); |
| |
| /** |
| * @param targetSize not exactly a max size in practice |
| * @param nonPooledFraction the fractional portion in the range [0.0,1.0] of targetSize to |
| * dedicate to non-poolable entries |
| */ |
| public UnrefedPooledCache(int targetSize, float nonPooledFraction) { |
| mCache = new LinkedHashMap<K, V>(0, 0.75f, true); |
| mPool = new LinkedBlockingQueue<V>(); |
| final int nonPooledSize = Math.round(targetSize * nonPooledFraction); |
| if (nonPooledSize > 0) { |
| mNonPooledCache = new NonPooledCache(nonPooledSize); |
| } else { |
| mNonPooledCache = null; |
| } |
| mTargetSize = targetSize - nonPooledSize; |
| } |
| |
| @Override |
| public V get(K key, boolean incrementRefCount) { |
| Trace.beginSection("cache get"); |
| synchronized (mCache) { |
| V result = mCache.get(key); |
| if (result == null && mNonPooledCache != null) { |
| result = mNonPooledCache.get(key); |
| } |
| if (incrementRefCount && result != null) { |
| result.acquireReference(); |
| } |
| Trace.endSection(); |
| return result; |
| } |
| } |
| |
| @Override |
| public V put(K key, V value) { |
| Trace.beginSection("cache put"); |
| // Null values not supported. |
| if (value == null) { |
| Trace.endSection(); |
| return null; |
| } |
| synchronized (mCache) { |
| final V prev; |
| if (value.isEligibleForPooling()) { |
| prev = mCache.put(key, value); |
| } else if (mNonPooledCache != null) { |
| prev = mNonPooledCache.put(key, value); |
| } else { |
| prev = null; |
| } |
| Trace.endSection(); |
| return prev; |
| } |
| } |
| |
| @Override |
| public void offer(V value) { |
| Trace.beginSection("pool offer"); |
| if (value.getRefCount() != 0 || !value.isEligibleForPooling()) { |
| Trace.endSection(); |
| throw new IllegalArgumentException("unexpected offer of an invalid object: " + value); |
| } |
| mPool.offer(value); |
| Trace.endSection(); |
| } |
| |
| @Override |
| public V poll() { |
| Trace.beginSection("pool poll"); |
| final V pooled = mPool.poll(); |
| if (pooled != null) { |
| Trace.endSection(); |
| return pooled; |
| } |
| |
| synchronized (mCache) { |
| int unrefSize = 0; |
| Map.Entry<K, V> eldestUnref = null; |
| for (Map.Entry<K, V> entry : mCache.entrySet()) { |
| final V value = entry.getValue(); |
| if (value.getRefCount() > 0 || !value.isEligibleForPooling()) { |
| continue; |
| } |
| if (eldestUnref == null) { |
| eldestUnref = entry; |
| } |
| unrefSize += sizeOf(value); |
| if (unrefSize > mTargetSize) { |
| break; |
| } |
| } |
| // only return a scavenged cache entry if the cache has enough |
| // eligible (unreferenced) items |
| if (unrefSize <= mTargetSize) { |
| if (DEBUG) { |
| Log.e(TAG, "POOL SCAVENGE FAILED, cache not fully warm yet. szDelta=" |
| + (mTargetSize-unrefSize)); |
| } |
| Trace.endSection(); |
| return null; |
| } else { |
| mCache.remove(eldestUnref.getKey()); |
| if (DEBUG) { |
| Log.e(TAG, "POOL SCAVENGE SUCCESS, oldKey=" + eldestUnref.getKey()); |
| } |
| Trace.endSection(); |
| return eldestUnref.getValue(); |
| } |
| } |
| } |
| |
| protected int sizeOf(V value) { |
| return 1; |
| } |
| |
| @Override |
| public String toDebugString() { |
| if (DEBUG) { |
| final StringBuilder sb = new StringBuilder("["); |
| sb.append(super.toString()); |
| int size = 0; |
| synchronized (mCache) { |
| sb.append(" poolCount="); |
| sb.append(mPool.size()); |
| sb.append(" cacheSize="); |
| sb.append(mCache.size()); |
| if (mNonPooledCache != null) { |
| sb.append(" nonPooledCacheSize="); |
| sb.append(mNonPooledCache.size()); |
| } |
| sb.append("\n---------------------"); |
| for (V val : mPool) { |
| size += sizeOf(val); |
| sb.append("\n\tpool item: "); |
| sb.append(val); |
| } |
| sb.append("\n---------------------"); |
| for (Map.Entry<K, V> item : mCache.entrySet()) { |
| final V val = item.getValue(); |
| sb.append("\n\tcache key="); |
| sb.append(item.getKey()); |
| sb.append(" val="); |
| sb.append(val); |
| size += sizeOf(val); |
| } |
| sb.append("\n---------------------"); |
| if (mNonPooledCache != null) { |
| for (Map.Entry<K, V> item : mNonPooledCache.snapshot().entrySet()) { |
| final V val = item.getValue(); |
| sb.append("\n\tnon-pooled cache key="); |
| sb.append(item.getKey()); |
| sb.append(" val="); |
| sb.append(val); |
| size += sizeOf(val); |
| } |
| sb.append("\n---------------------"); |
| } |
| sb.append("\nTOTAL SIZE=" + size); |
| } |
| sb.append("]"); |
| return sb.toString(); |
| } else { |
| return null; |
| } |
| } |
| |
| private class NonPooledCache extends LruCache<K, V> { |
| |
| public NonPooledCache(int maxSize) { |
| super(maxSize); |
| } |
| |
| @Override |
| protected int sizeOf(K key, V value) { |
| return UnrefedPooledCache.this.sizeOf(value); |
| } |
| |
| } |
| |
| @Override |
| public void clear() { |
| mCache.clear(); |
| mPool.clear(); |
| } |
| } |