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