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