Engine.java revision 0be7fb306834626a8c389e0a685d4017f5f84c71
15d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)package com.bumptech.glide.load.engine;
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.os.Handler;
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.os.Looper;
55d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import android.os.MessageQueue;
65d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import android.util.Log;
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import com.bumptech.glide.Priority;
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import com.bumptech.glide.load.Key;
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import com.bumptech.glide.load.ResourceDecoder;
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import com.bumptech.glide.load.ResourceEncoder;
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import com.bumptech.glide.load.Transformation;
125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import com.bumptech.glide.load.data.DataFetcher;
135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import com.bumptech.glide.load.engine.cache.DiskCache;
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import com.bumptech.glide.load.engine.cache.MemoryCache;
155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import com.bumptech.glide.request.ResourceCallback;
174e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)import com.bumptech.glide.util.LogTime;
185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import java.io.InputStream;
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import java.lang.ref.ReferenceQueue;
212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import java.lang.ref.WeakReference;
222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import java.util.HashMap;
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import java.util.Map;
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import java.util.concurrent.ExecutorService;
255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, Resource.ResourceListener {
2768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    private static final String TAG = "Engine";
2868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    private final Map<Key, ResourceRunner> runners;
2968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    private final ResourceRunnerFactory factory;
3068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    private final EngineKeyFactory keyFactory;
3168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    private final MemoryCache cache;
3268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    private final Map<Key, WeakReference<Resource>> activeResources;
335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    private final ReferenceQueue<Resource> resourceReferenceQueue;
345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    public static class LoadStatus {
365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        private final EngineJob engineJob;
375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        private final ResourceCallback cb;
3868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
3968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        public LoadStatus(ResourceCallback cb, EngineJob engineJob) {
404e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)            this.cb = cb;
415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            this.engineJob = engineJob;
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        }
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        public void cancel() {
455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            engineJob.removeCallback(cb);
465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        }
475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    }
485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    public Engine(MemoryCache memoryCache, DiskCache diskCache, ExecutorService resizeService,
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            ExecutorService diskCacheService) {
514e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        this(null, memoryCache, diskCache, resizeService, diskCacheService, null, null, null);
524e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    }
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
544e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    Engine(ResourceRunnerFactory factory, MemoryCache cache, DiskCache diskCache, ExecutorService resizeService,
554e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)            ExecutorService diskCacheService, Map<Key, ResourceRunner> runners, EngineKeyFactory keyFactory,
562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            Map<Key, WeakReference<Resource>> activeResources) {
574e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        this.cache = cache;
584e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        if (activeResources == null) {
6068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)            activeResources = new HashMap<Key, WeakReference<Resource>>();
6168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        }
6268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        this.activeResources = activeResources;
6368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
6468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        if (keyFactory == null) {
6568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)            keyFactory = new EngineKeyFactory();
6668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        }
675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        this.keyFactory = keyFactory;
685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        if (runners == null) {
705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            runners = new HashMap<Key, ResourceRunner>();
715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        }
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.runners = runners;
7368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
7468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        if (factory == null) {
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            factory = new DefaultResourceRunnerFactory(diskCache, new Handler(Looper.getMainLooper()),
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    diskCacheService, resizeService);
7768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        }
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.factory = factory;
7968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        resourceReferenceQueue = new ReferenceQueue<Resource>();
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        MessageQueue queue = Looper.myQueue();
822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        queue.addIdleHandler(new RefQueueIdleHandler(activeResources, resourceReferenceQueue));
8368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        cache.setResourceRemovedListener(this);
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
8568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /**
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * @param cacheDecoder
884e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)     * @param fetcher
8968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)     * @param decoder
9068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)     * @param encoder
9168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)     * @param transcoder
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * @param priority
9368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)     * @param <T>          The type of data the resource will be decoded from.
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * @param <Z>          The type of the resource that will be decoded.
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * @param <R>          The type of the resource that will be transcoded from the decoded resource.
965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)     */
975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    public <T, Z, R> LoadStatus load(int width, int height, ResourceDecoder<InputStream, Z> cacheDecoder,
985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            DataFetcher<T> fetcher, ResourceDecoder<T, Z> decoder, Transformation<Z> transformation,
995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            ResourceEncoder<Z> encoder, ResourceTranscoder<Z, R> transcoder, Priority priority,
1005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            boolean isMemoryCacheable, ResourceCallback cb) {
1015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        long startTime = LogTime.getLogTime();
1025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        final String id = fetcher.getId();
1045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        EngineKey key = keyFactory.buildKey(id, width, height, cacheDecoder, decoder, transformation, encoder,
1055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                transcoder);
1065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        Resource cached = cache.remove(key);
1085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        if (cached != null) {
1095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            cached.acquire(1);
1105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            activeResources.put(key, new ResourceWeakReference(key, cached, resourceReferenceQueue));
1115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            cb.onResourceReady(cached);
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            if (Log.isLoggable(TAG, Log.VERBOSE)) {
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                Log.v(TAG, "loaded resource from cache in " + LogTime.getElapsedMillis(startTime));
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            }
1152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            return null;
11668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        }
1172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        WeakReference<Resource> activeRef = activeResources.get(key);
11968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        if (activeRef != null) {
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            Resource active = activeRef.get();
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            if (active != null) {
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                active.acquire(1);
12368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                cb.onResourceReady(active);
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                if (Log.isLoggable(TAG, Log.VERBOSE)) {
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    Log.v(TAG, "loaded resource from active resources in " + LogTime.getElapsedMillis(startTime));
1264e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                }
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                return null;
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            } else {
1295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                activeResources.remove(key);
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            }
1315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        }
132
133        ResourceRunner current = runners.get(key);
134        if (current != null) {
135            EngineJob job = current.getJob();
136            job.addCallback(cb);
137            if (Log.isLoggable(TAG, Log.VERBOSE)) {
138                Log.v(TAG, "added to existing load in " + LogTime.getElapsedMillis(startTime));
139            }
140            return new LoadStatus(cb, job);
141        }
142
143        long start = LogTime.getLogTime();
144        ResourceRunner<Z, R> runner = factory.build(key, width, height, cacheDecoder, fetcher, decoder, transformation,
145                encoder, transcoder, priority, isMemoryCacheable, this);
146        runner.getJob().addCallback(cb);
147        runners.put(key, runner);
148        runner.queue();
149        if (Log.isLoggable(TAG, Log.VERBOSE)) {
150            Log.v(TAG, "queued new load in " + LogTime.getElapsedMillis(start));
151            Log.v(TAG, "finished load in engine in " + LogTime.getElapsedMillis(startTime));
152        }
153        return new LoadStatus(cb, runner.getJob());
154    }
155
156    @Override
157    public void onEngineJobComplete(Key key, Resource resource) {
158        // A null resource indicates that the load failed, usually due to an exception.
159        if (resource != null) {
160            resource.setResourceListener(key, this);
161            activeResources.put(key, new ResourceWeakReference(key, resource, resourceReferenceQueue));
162        }
163        runners.remove(key);
164    }
165
166    @Override
167    public void onEngineJobCancelled(Key key) {
168        ResourceRunner runner = runners.remove(key);
169        runner.cancel();
170    }
171
172    @Override
173    public void onResourceRemoved(Resource resource) {
174        resource.recycle();
175    }
176
177    @Override
178    public void onResourceReleased(Key cacheKey, Resource resource) {
179        activeResources.remove(cacheKey);
180        if (resource.isCacheable()) {
181            cache.put(cacheKey, resource);
182        } else {
183            resource.recycle();
184        }
185    }
186
187    private static class ResourceWeakReference extends WeakReference<Resource> {
188        public final Object resource;
189        public final Key key;
190
191        public ResourceWeakReference(Key key, Resource r, ReferenceQueue<? super Resource> q) {
192            super(r, q);
193            this.key = key;
194            resource = r.get();
195        }
196    }
197
198    private static class RefQueueIdleHandler implements MessageQueue.IdleHandler {
199        private Map<Key, WeakReference<Resource>> activeResources;
200        private ReferenceQueue<Resource> queue;
201
202        public RefQueueIdleHandler(Map<Key, WeakReference<Resource>> activeResources, ReferenceQueue<Resource> queue) {
203            this.activeResources = activeResources;
204            this.queue = queue;
205        }
206
207        @Override
208        public boolean queueIdle() {
209            ResourceWeakReference ref = (ResourceWeakReference) queue.poll();
210            if (ref != null) {
211                activeResources.remove(ref.key);
212                if (Log.isLoggable(TAG, Log.DEBUG)) {
213                    Log.d(TAG, "Maybe leaked a resource: " + ref.resource);
214                }
215            }
216
217            return true;
218        }
219    }
220}
221