• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.bumptech.glide.load.engine;
2 
3 import android.os.Handler;
4 import android.os.Message;
5 
6 import com.bumptech.glide.load.Key;
7 import com.bumptech.glide.request.ResourceCallback;
8 import com.bumptech.glide.util.Util;
9 
10 import java.util.ArrayList;
11 import java.util.HashSet;
12 import java.util.List;
13 import java.util.Set;
14 import java.util.concurrent.ExecutorService;
15 import java.util.concurrent.Future;
16 
17 /**
18  * A class that manages a load by adding and removing callbacks for for the load and notifying callbacks when the
19  * load completes.
20  */
21 class EngineJob implements EngineRunnable.EngineRunnableManager {
22     private static final EngineResourceFactory DEFAULT_FACTORY = new EngineResourceFactory();
23     private static final Handler MAIN_THREAD_HANDLER = new Handler(new MainThreadCallback());
24 
25     private static final int MSG_COMPLETE = 1;
26     private static final int MSG_EXCEPTION = 2;
27 
28     private final List<ResourceCallback> cbs = new ArrayList<ResourceCallback>();
29     private final EngineResourceFactory engineResourceFactory;
30     private final EngineJobListener listener;
31     private final Key key;
32     private final ExecutorService diskCacheService;
33     private final ExecutorService sourceService;
34     private final boolean isCacheable;
35 
36     private boolean isCancelled;
37     // Either resource or exception (particularly exception) may be returned to us null, so use booleans to track if
38     // we've received them instead of relying on them to be non-null. See issue #180.
39     private Resource<?> resource;
40     private boolean hasResource;
41     private Exception exception;
42     private boolean hasException;
43     // A set of callbacks that are removed while we're notifying other callbacks of a change in status.
44     private Set<ResourceCallback> ignoredCallbacks;
45     private EngineRunnable engineRunnable;
46     private EngineResource<?> engineResource;
47 
48     private volatile Future<?> future;
49 
EngineJob(Key key, ExecutorService diskCacheService, ExecutorService sourceService, boolean isCacheable, EngineJobListener listener)50     public EngineJob(Key key, ExecutorService diskCacheService, ExecutorService sourceService, boolean isCacheable,
51             EngineJobListener listener) {
52         this(key, diskCacheService, sourceService, isCacheable, listener, DEFAULT_FACTORY);
53     }
54 
EngineJob(Key key, ExecutorService diskCacheService, ExecutorService sourceService, boolean isCacheable, EngineJobListener listener, EngineResourceFactory engineResourceFactory)55     public EngineJob(Key key, ExecutorService diskCacheService, ExecutorService sourceService, boolean isCacheable,
56             EngineJobListener listener, EngineResourceFactory engineResourceFactory) {
57         this.key = key;
58         this.diskCacheService = diskCacheService;
59         this.sourceService = sourceService;
60         this.isCacheable = isCacheable;
61         this.listener = listener;
62         this.engineResourceFactory = engineResourceFactory;
63     }
64 
start(EngineRunnable engineRunnable)65     public void start(EngineRunnable engineRunnable) {
66         this.engineRunnable = engineRunnable;
67         future = diskCacheService.submit(engineRunnable);
68     }
69 
70     @Override
submitForSource(EngineRunnable runnable)71     public void submitForSource(EngineRunnable runnable) {
72         future = sourceService.submit(runnable);
73     }
74 
addCallback(ResourceCallback cb)75     public void addCallback(ResourceCallback cb) {
76         Util.assertMainThread();
77         if (hasResource) {
78             cb.onResourceReady(engineResource);
79         } else if (hasException) {
80             cb.onException(exception);
81         } else {
82             cbs.add(cb);
83         }
84     }
85 
removeCallback(ResourceCallback cb)86     public void removeCallback(ResourceCallback cb) {
87         Util.assertMainThread();
88         if (hasResource || hasException) {
89             addIgnoredCallback(cb);
90         } else {
91             cbs.remove(cb);
92             if (cbs.isEmpty()) {
93                 cancel();
94             }
95         }
96     }
97 
98     // We cannot remove callbacks while notifying our list of callbacks directly because doing so would cause a
99     // ConcurrentModificationException. However, we need to obey the cancellation request such that if notifying a
100     // callback early in the callbacks list cancels a callback later in the request list, the cancellation for the later
101     // request is still obeyed. Using a set of ignored callbacks allows us to avoid the exception while still meeting
102     // the requirement.
addIgnoredCallback(ResourceCallback cb)103     private void addIgnoredCallback(ResourceCallback cb) {
104         if (ignoredCallbacks == null) {
105             ignoredCallbacks = new HashSet<ResourceCallback>();
106         }
107         ignoredCallbacks.add(cb);
108     }
109 
isInIgnoredCallbacks(ResourceCallback cb)110     private boolean isInIgnoredCallbacks(ResourceCallback cb) {
111         return ignoredCallbacks != null && ignoredCallbacks.contains(cb);
112     }
113 
114     // Exposed for testing.
cancel()115     void cancel() {
116         if (hasException || hasResource || isCancelled) {
117             return;
118         }
119         engineRunnable.cancel();
120         Future currentFuture = future;
121         if (currentFuture != null) {
122             currentFuture.cancel(true);
123         }
124         isCancelled = true;
125         listener.onEngineJobCancelled(this, key);
126     }
127 
128     // Exposed for testing.
isCancelled()129     boolean isCancelled() {
130         return isCancelled;
131     }
132 
133     @Override
onResourceReady(final Resource<?> resource)134     public void onResourceReady(final Resource<?> resource) {
135         this.resource = resource;
136         MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
137     }
138 
handleResultOnMainThread()139     private void handleResultOnMainThread() {
140         if (isCancelled) {
141             resource.recycle();
142             return;
143         } else if (cbs.isEmpty()) {
144             throw new IllegalStateException("Received a resource without any callbacks to notify");
145         }
146         engineResource = engineResourceFactory.build(resource, isCacheable);
147         hasResource = true;
148 
149         // Hold on to resource for duration of request so we don't recycle it in the middle of notifying if it
150         // synchronously released by one of the callbacks.
151         engineResource.acquire();
152         listener.onEngineJobComplete(key, engineResource);
153 
154         for (ResourceCallback cb : cbs) {
155             if (!isInIgnoredCallbacks(cb)) {
156                 engineResource.acquire();
157                 cb.onResourceReady(engineResource);
158             }
159         }
160         // Our request is complete, so we can release the resource.
161         engineResource.release();
162     }
163 
164     @Override
onException(final Exception e)165     public void onException(final Exception e) {
166         this.exception = e;
167         MAIN_THREAD_HANDLER.obtainMessage(MSG_EXCEPTION, this).sendToTarget();
168     }
169 
handleExceptionOnMainThread()170     private void handleExceptionOnMainThread() {
171         if (isCancelled) {
172             return;
173         } else if (cbs.isEmpty()) {
174             throw new IllegalStateException("Received an exception without any callbacks to notify");
175         }
176         hasException = true;
177 
178         listener.onEngineJobComplete(key, null);
179 
180         for (ResourceCallback cb : cbs) {
181             if (!isInIgnoredCallbacks(cb)) {
182                 cb.onException(exception);
183             }
184         }
185     }
186 
187     // Visible for testing.
188     static class EngineResourceFactory {
build(Resource<R> resource, boolean isMemoryCacheable)189         public <R> EngineResource<R> build(Resource<R> resource, boolean isMemoryCacheable) {
190             return new EngineResource<R>(resource, isMemoryCacheable);
191         }
192     }
193 
194     private static class MainThreadCallback implements Handler.Callback {
195 
196         @Override
handleMessage(Message message)197         public boolean handleMessage(Message message) {
198             if (MSG_COMPLETE == message.what || MSG_EXCEPTION == message.what) {
199                 EngineJob job = (EngineJob) message.obj;
200                 if (MSG_COMPLETE == message.what) {
201                     job.handleResultOnMainThread();
202                 } else {
203                     job.handleExceptionOnMainThread();
204                 }
205                 return true;
206             }
207 
208             return false;
209         }
210     }
211 }
212