1package com.bumptech.glide.load.engine; 2 3import android.os.Handler; 4import android.os.Looper; 5import android.os.MessageQueue; 6import android.util.Log; 7 8import com.bumptech.glide.Priority; 9import com.bumptech.glide.load.Encoder; 10import com.bumptech.glide.load.Key; 11import com.bumptech.glide.load.ResourceDecoder; 12import com.bumptech.glide.load.ResourceEncoder; 13import com.bumptech.glide.load.Transformation; 14import com.bumptech.glide.load.data.DataFetcher; 15import com.bumptech.glide.load.engine.cache.DiskCache; 16import com.bumptech.glide.load.engine.cache.MemoryCache; 17import com.bumptech.glide.load.resource.transcode.ResourceTranscoder; 18import com.bumptech.glide.request.ResourceCallback; 19import com.bumptech.glide.util.LogTime; 20 21import java.io.InputStream; 22import java.lang.ref.ReferenceQueue; 23import java.lang.ref.WeakReference; 24import java.util.HashMap; 25import java.util.Map; 26import java.util.concurrent.ExecutorService; 27 28public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, Resource.ResourceListener { 29 private static final String TAG = "Engine"; 30 private final Map<Key, ResourceRunner> runners; 31 private final ResourceRunnerFactory factory; 32 private final EngineKeyFactory keyFactory; 33 private final MemoryCache cache; 34 private final Map<Key, WeakReference<Resource>> activeResources; 35 private final ReferenceQueue<Resource> resourceReferenceQueue; 36 37 public static class LoadStatus { 38 private final EngineJob engineJob; 39 private final ResourceCallback cb; 40 41 public LoadStatus(ResourceCallback cb, EngineJob engineJob) { 42 this.cb = cb; 43 this.engineJob = engineJob; 44 } 45 46 public void cancel() { 47 engineJob.removeCallback(cb); 48 } 49 } 50 51 public Engine(MemoryCache memoryCache, DiskCache diskCache, ExecutorService resizeService, 52 ExecutorService diskCacheService) { 53 this(null, memoryCache, diskCache, resizeService, diskCacheService, null, null, null); 54 } 55 56 Engine(ResourceRunnerFactory factory, MemoryCache cache, DiskCache diskCache, ExecutorService resizeService, 57 ExecutorService diskCacheService, Map<Key, ResourceRunner> runners, EngineKeyFactory keyFactory, 58 Map<Key, WeakReference<Resource>> activeResources) { 59 this.cache = cache; 60 61 if (activeResources == null) { 62 activeResources = new HashMap<Key, WeakReference<Resource>>(); 63 } 64 this.activeResources = activeResources; 65 66 if (keyFactory == null) { 67 keyFactory = new EngineKeyFactory(); 68 } 69 this.keyFactory = keyFactory; 70 71 if (runners == null) { 72 runners = new HashMap<Key, ResourceRunner>(); 73 } 74 this.runners = runners; 75 76 if (factory == null) { 77 factory = new DefaultResourceRunnerFactory(diskCache, new Handler(Looper.getMainLooper()), 78 diskCacheService, resizeService); 79 } 80 this.factory = factory; 81 82 resourceReferenceQueue = new ReferenceQueue<Resource>(); 83 MessageQueue queue = Looper.myQueue(); 84 queue.addIdleHandler(new RefQueueIdleHandler(activeResources, resourceReferenceQueue)); 85 cache.setResourceRemovedListener(this); 86 } 87 88 /** 89 * @param cacheDecoder 90 * @param fetcher 91 * @param decoder 92 * @param encoder 93 * @param transcoder 94 * @param priority 95 * @param <T> The type of data the resource will be decoded from. 96 * @param <Z> The type of the resource that will be decoded. 97 * @param <R> The type of the resource that will be transcoded from the decoded resource. 98 */ 99 public <T, Z, R> LoadStatus load(int width, int height, ResourceDecoder<InputStream, Z> cacheDecoder, 100 DataFetcher<T> fetcher, boolean cacheSource, Encoder<T> sourceEncoder, 101 ResourceDecoder<T, Z> decoder, Transformation<Z> transformation, ResourceEncoder<Z> encoder, 102 ResourceTranscoder<Z, R> transcoder, Priority priority, boolean isMemoryCacheable, ResourceCallback cb) { 103 long startTime = LogTime.getLogTime(); 104 105 final String id = fetcher.getId(); 106 EngineKey key = keyFactory.buildKey(id, width, height, cacheDecoder, decoder, transformation, encoder, 107 transcoder, sourceEncoder); 108 if (Log.isLoggable(TAG, Log.VERBOSE)) { 109 Log.v(TAG, "loading: " + key); 110 } 111 112 Resource cached = cache.remove(key); 113 if (cached != null) { 114 cached.acquire(1); 115 activeResources.put(key, new ResourceWeakReference(key, cached, resourceReferenceQueue)); 116 cb.onResourceReady(cached); 117 if (Log.isLoggable(TAG, Log.VERBOSE)) { 118 Log.v(TAG, "loaded resource from cache in " + LogTime.getElapsedMillis(startTime)); 119 } 120 return null; 121 } 122 123 WeakReference<Resource> activeRef = activeResources.get(key); 124 if (activeRef != null) { 125 Resource active = activeRef.get(); 126 if (active != null) { 127 active.acquire(1); 128 cb.onResourceReady(active); 129 if (Log.isLoggable(TAG, Log.VERBOSE)) { 130 Log.v(TAG, "loaded resource from active resources in " + LogTime.getElapsedMillis(startTime)); 131 } 132 return null; 133 } else { 134 activeResources.remove(key); 135 } 136 } 137 138 ResourceRunner current = runners.get(key); 139 if (current != null) { 140 EngineJob job = current.getJob(); 141 job.addCallback(cb); 142 if (Log.isLoggable(TAG, Log.VERBOSE)) { 143 Log.v(TAG, "added to existing load in " + LogTime.getElapsedMillis(startTime)); 144 } 145 return new LoadStatus(cb, job); 146 } 147 148 long start = LogTime.getLogTime(); 149 ResourceRunner<Z, R> runner = factory.build(key, width, height, cacheDecoder, fetcher, cacheSource, 150 sourceEncoder, decoder, transformation, encoder, transcoder, priority, isMemoryCacheable, this); 151 runner.getJob().addCallback(cb); 152 runners.put(key, runner); 153 runner.queue(); 154 if (Log.isLoggable(TAG, Log.VERBOSE)) { 155 Log.v(TAG, "queued new load in " + LogTime.getElapsedMillis(start)); 156 Log.v(TAG, "finished load in engine in " + LogTime.getElapsedMillis(startTime)); 157 } 158 return new LoadStatus(cb, runner.getJob()); 159 } 160 161 @Override 162 public void onEngineJobComplete(Key key, Resource resource) { 163 // A null resource indicates that the load failed, usually due to an exception. 164 if (resource != null) { 165 resource.setResourceListener(key, this); 166 activeResources.put(key, new ResourceWeakReference(key, resource, resourceReferenceQueue)); 167 } 168 runners.remove(key); 169 } 170 171 @Override 172 public void onEngineJobCancelled(Key key) { 173 ResourceRunner runner = runners.remove(key); 174 runner.cancel(); 175 } 176 177 @Override 178 public void onResourceRemoved(Resource resource) { 179 resource.recycle(); 180 } 181 182 @Override 183 public void onResourceReleased(Key cacheKey, Resource resource) { 184 if (Log.isLoggable(TAG, Log.VERBOSE)) { 185 Log.v(TAG, "released: " + cacheKey); 186 } 187 activeResources.remove(cacheKey); 188 if (resource.isCacheable()) { 189 if (Log.isLoggable(TAG, Log.VERBOSE)) { 190 Log.v(TAG, "recaching: " + cacheKey); 191 } 192 cache.put(cacheKey, resource); 193 } else { 194 if (Log.isLoggable(TAG, Log.VERBOSE)) { 195 Log.v(TAG, "recycling: " + cacheKey); 196 } 197 resource.recycle(); 198 } 199 } 200 201 private static class ResourceWeakReference extends WeakReference<Resource> { 202 public final Object resource; 203 public final Key key; 204 205 public ResourceWeakReference(Key key, Resource r, ReferenceQueue<? super Resource> q) { 206 super(r, q); 207 this.key = key; 208 resource = r.get(); 209 } 210 } 211 212 private static class RefQueueIdleHandler implements MessageQueue.IdleHandler { 213 private Map<Key, WeakReference<Resource>> activeResources; 214 private ReferenceQueue<Resource> queue; 215 216 public RefQueueIdleHandler(Map<Key, WeakReference<Resource>> activeResources, ReferenceQueue<Resource> queue) { 217 this.activeResources = activeResources; 218 this.queue = queue; 219 } 220 221 @Override 222 public boolean queueIdle() { 223 ResourceWeakReference ref = (ResourceWeakReference) queue.poll(); 224 if (ref != null) { 225 activeResources.remove(ref.key); 226 if (Log.isLoggable(TAG, Log.DEBUG)) { 227 Log.d(TAG, "Maybe leaked a resource: " + ref.resource); 228 } 229 } 230 231 return true; 232 } 233 } 234} 235