• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
AltPooledCache(int targetSize, float nonPooledFraction)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
get(K key, boolean incrementRefCount)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
put(K key, V value)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
offer(V value)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
poll()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 
sizeOf(V value)144     protected int sizeOf(V value) {
145         return 1;
146     }
147 
148     @Override
toDebugString()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 
NonPooledCache(int maxSize)201         public NonPooledCache(int maxSize) {
202             super(maxSize);
203         }
204 
205         @Override
sizeOf(K key, V value)206         protected int sizeOf(K key, V value) {
207             return AltPooledCache.this.sizeOf(value);
208         }
209 
210     }
211 
212 }
213