Skip to content

Commit 77cbcea

Browse files
committed
Merge pull request androidannotations#569 from rom1v/cancel_background
Cancel background tasks
2 parents 2323d7a + 4161c62 commit 77cbcea

6 files changed

Lines changed: 574 additions & 38 deletions

File tree

‎AndroidAnnotations/androidannotations-api/src/main/java/org/androidannotations/annotations/Background.java‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,26 @@
2828
@Retention(RetentionPolicy.CLASS)
2929
@Target(ElementType.METHOD)
3030
public @interface Background {
31-
long delay() default 0;
31+
/**
32+
* Identifier for task cancellation.
33+
*
34+
* To cancel all tasks having a specified background id:
35+
*
36+
* <pre>
37+
* boolean mayInterruptIfRunning = true;
38+
* BackgroundExecutor.cancelAll(&quot;my_background_id&quot;, mayInterruptIfRunning);
39+
* </pre>
40+
**/
41+
String id() default "";
42+
43+
/** Minimum delay, in milliseconds, before the background task is executed. */
44+
int delay() default 0;
45+
46+
/**
47+
* Serial execution group.
48+
*
49+
* All background tasks having the same <code>serial</code> will be executed
50+
* sequentially.
51+
**/
52+
String serial() default "";
3253
}

‎AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/api/BackgroundExecutor.java‎

Lines changed: 269 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,291 @@
1515
*/
1616
package org.androidannotations.api;
1717

18+
import java.util.ArrayList;
19+
import java.util.List;
1820
import java.util.concurrent.Executor;
21+
import java.util.concurrent.ExecutorService;
1922
import java.util.concurrent.Executors;
23+
import java.util.concurrent.Future;
2024
import java.util.concurrent.ScheduledExecutorService;
2125
import java.util.concurrent.TimeUnit;
2226

27+
import android.util.Log;
28+
2329
public class BackgroundExecutor {
2430

25-
private static Executor executor = Executors.newCachedThreadPool();
26-
private static ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(2 * Runtime.getRuntime().availableProcessors());
31+
private static final String TAG = "BackgroundExecutor";
32+
33+
private static Executor executor = Executors.newScheduledThreadPool(2 * Runtime.getRuntime().availableProcessors());
34+
35+
private static final List<Task> tasks = new ArrayList<Task>();
36+
37+
/**
38+
* Execute a runnable after the given delay.
39+
*
40+
* @param runnable
41+
* the task to execute
42+
* @param delay
43+
* the time from now to delay execution, in milliseconds
44+
* @throws IllegalArgumentException
45+
* if <code>delay</code> is strictly positive and the current
46+
* executor does not support scheduling (if
47+
* {@link #setExecutor(Executor)} has been called with such an
48+
* executor)
49+
* @return Future associated to the running task
50+
*/
51+
private static Future<?> directExecute(Runnable runnable, int delay) {
52+
Future<?> future = null;
53+
if (delay > 0) {
54+
/* no serial, but a delay: schedule the task */
55+
if (!(executor instanceof ScheduledExecutorService)) {
56+
throw new IllegalArgumentException("The executor set does not support scheduling");
57+
}
58+
ScheduledExecutorService scheduledExecutorService = (ScheduledExecutorService) executor;
59+
future = scheduledExecutorService.schedule(runnable, delay, TimeUnit.MILLISECONDS);
60+
} else {
61+
if (executor instanceof ExecutorService) {
62+
ExecutorService executorService = (ExecutorService) executor;
63+
future = executorService.submit(runnable);
64+
} else {
65+
/* non-cancellable task */
66+
executor.execute(runnable);
67+
}
68+
}
69+
return future;
70+
}
71+
72+
/**
73+
* Execute a task after (at least) its delay <strong>and</strong> after all
74+
* tasks added with the same non-null <code>serial</code> (if any) have
75+
* completed execution.
76+
*
77+
* @param task
78+
* the task to execute
79+
* @throws IllegalArgumentException
80+
* if <code>task.delay</code> is strictly positive and the
81+
* current executor does not support scheduling (if
82+
* {@link #setExecutor(Executor)} has been called with such an
83+
* executor)
84+
*/
85+
public static synchronized void execute(Task task) {
86+
Future<?> future = null;
87+
if (task.serial == null || !hasSerialRunning(task.serial)) {
88+
task.executionAsked = true;
89+
future = directExecute(task, task.remainingDelay);
90+
}
91+
if (task.id != null || task.serial != null) {
92+
/* keep task */
93+
task.future = future;
94+
tasks.add(task);
95+
}
96+
}
2797

98+
/**
99+
* Execute a task.
100+
*
101+
* @param runnable
102+
* the task to execute
103+
* @param id
104+
* identifier used for task cancellation
105+
* @param delay
106+
* the time from now to delay execution, in milliseconds
107+
* @param serial
108+
* the serial queue (<code>null</code> or <code>""</code> for no
109+
* serial execution)
110+
* @throws IllegalArgumentException
111+
* if <code>delay</code> is strictly positive and the current
112+
* executor does not support scheduling (if
113+
* {@link #setExecutor(Executor)} has been called with such an
114+
* executor)
115+
*/
116+
public static void execute(final Runnable runnable, String id, int delay, String serial) {
117+
execute(new Task(id, delay, serial) {
118+
@Override
119+
public void execute() {
120+
runnable.run();
121+
}
122+
});
123+
}
124+
125+
/**
126+
* Execute a task after the given delay.
127+
*
128+
* @param runnable
129+
* the task to execute
130+
* @param delay
131+
* the time from now to delay execution, in milliseconds
132+
* @throws IllegalArgumentException
133+
* if <code>delay</code> is strictly positive and the current
134+
* executor does not support scheduling (if
135+
* {@link #setExecutor(Executor)} has been called with such an
136+
* executor)
137+
*/
138+
public static void execute(Runnable runnable, int delay) {
139+
directExecute(runnable, delay);
140+
}
141+
142+
/**
143+
* Execute a task.
144+
*
145+
* @param runnable
146+
* the task to execute
147+
*/
28148
public static void execute(Runnable runnable) {
29-
executor.execute(runnable);
149+
directExecute(runnable, 0);
150+
}
151+
152+
/**
153+
* Execute a task after all tasks added with the same non-null
154+
* <code>serial</code> (if any) have completed execution.
155+
*
156+
* Equivalent to {@link #execute(Runnable, String, int, String)
157+
* execute(runnable, id, 0, serial)}.
158+
*
159+
* @param runnable
160+
* the task to execute
161+
* @param id
162+
* identifier used for task cancellation
163+
* @param serial
164+
* the serial queue to use (<code>null</code> or <code>""</code>
165+
* for no serial execution)
166+
*/
167+
public static void execute(Runnable runnable, String id, String serial) {
168+
execute(runnable, id, 0, serial);
30169
}
31170

171+
/**
172+
* Change the executor.
173+
*
174+
* Note that if the given executor is not a {@link ScheduledExecutorService}
175+
* then executing a task after a delay will not be supported anymore. If it
176+
* is not even a {@link ExecutorService} then tasks will not be cancellable
177+
* anymore.
178+
*
179+
* @param executor
180+
* the new executor
181+
*/
32182
public static void setExecutor(Executor executor) {
33183
BackgroundExecutor.executor = executor;
34184
}
35185

36-
public static void executeDelayed(Runnable runnable, long delay) {
37-
scheduledExecutor.schedule(runnable, delay, TimeUnit.MILLISECONDS);
186+
/**
187+
* Cancel all tasks having the specified <code>id</code>.
188+
*
189+
* @param id
190+
* the cancellation identifier
191+
* @param mayInterruptIfRunning
192+
* <code>true</code> if the thread executing this task should be
193+
* interrupted; otherwise, in-progress tasks are allowed to
194+
* complete
195+
*/
196+
public static synchronized void cancelAll(String id, boolean mayInterruptIfRunning) {
197+
for (int i = tasks.size() - 1; i >= 0; i--) {
198+
Task task = tasks.get(i);
199+
if (id.equals(task.id)) {
200+
tasks.remove(i);
201+
if (task.future != null) {
202+
task.future.cancel(mayInterruptIfRunning);
203+
} else if (task.executionAsked) {
204+
Log.w(TAG, "A task with id " + task.id + " cannot be cancelled (the executor set does not support it)");
205+
}
206+
}
207+
}
38208
}
39209

40-
public static void setScheduledExecutor(ScheduledExecutorService scheduledExecutor) {
41-
BackgroundExecutor.scheduledExecutor = scheduledExecutor;
210+
/**
211+
* Indicates whether a task with the specified <code>serial</code> has been
212+
* submitted to the executor.
213+
*
214+
* @param serial
215+
* the serial queue
216+
* @return <code>true</code> if such a task has been submitted,
217+
* <code>false</code> otherwise
218+
*/
219+
private static boolean hasSerialRunning(String serial) {
220+
for (Task task : tasks) {
221+
if (task.executionAsked && serial.equals(task.serial)) {
222+
return true;
223+
}
224+
}
225+
return false;
42226
}
227+
228+
/**
229+
* Retrieve and remove the first task having the specified
230+
* <code>serial</code> (if any).
231+
*
232+
* @param serial
233+
* the serial queue
234+
* @return task if found, <code>null</code> otherwise
235+
*/
236+
private static Task take(String serial) {
237+
int len = tasks.size();
238+
for (int i = 0; i < len; i++) {
239+
if (serial.equals(tasks.get(i).serial)) {
240+
return tasks.remove(i);
241+
}
242+
}
243+
return null;
244+
}
245+
246+
public static abstract class Task implements Runnable {
247+
248+
private String id;
249+
private int remainingDelay;
250+
private long targetTimeMillis; /* since epoch */
251+
private String serial;
252+
private boolean executionAsked;
253+
private Future<?> future;
254+
255+
public Task(String id, int delay, String serial) {
256+
if (!"".equals(id)) {
257+
this.id = id;
258+
}
259+
if (delay > 0) {
260+
remainingDelay = delay;
261+
targetTimeMillis = System.currentTimeMillis() + delay;
262+
}
263+
if (!"".equals(serial)) {
264+
this.serial = serial;
265+
}
266+
}
267+
268+
@Override
269+
public void run() {
270+
try {
271+
execute();
272+
} finally {
273+
/* handle next tasks */
274+
postExecute();
275+
}
276+
}
277+
278+
public abstract void execute();
279+
280+
private void postExecute() {
281+
if (id == null && serial == null) {
282+
/* nothing to do */
283+
return;
284+
}
285+
synchronized (BackgroundExecutor.class) {
286+
/* execution complete */
287+
tasks.remove(this);
288+
289+
if (serial != null) {
290+
Task next = take(serial);
291+
if (next != null) {
292+
if (next.remainingDelay != 0) {
293+
/* the delay may not have elapsed yet */
294+
next.remainingDelay = Math.max(0, (int) (targetTimeMillis - System.currentTimeMillis()));
295+
}
296+
/* a task having the same serial was queued, execute it */
297+
BackgroundExecutor.execute(next);
298+
}
299+
}
300+
}
301+
}
302+
303+
}
304+
43305
}

‎AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/helper/APTCodeModelHelper.java‎

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,20 @@ public String getIdStringFromIdFieldRef(JFieldRef idRef) {
228228
throw new IllegalStateException("Unable to extract target name from JFieldRef");
229229
}
230230

231+
public JTryBlock surroundWithTryCatch(EBeanHolder holder, JBlock block, JBlock content, String exceptionMessage) {
232+
Classes classes = holder.classes();
233+
JTryBlock tryBlock = block._try();
234+
tryBlock.body().add(content);
235+
JCatchBlock catchBlock = tryBlock._catch(classes.RUNTIME_EXCEPTION);
236+
JVar exceptionParam = catchBlock.param("e");
237+
JInvocation errorInvoke = classes.LOG.staticInvoke("e");
238+
errorInvoke.arg(holder.generatedClass.name());
239+
errorInvoke.arg(exceptionMessage);
240+
errorInvoke.arg(exceptionParam);
241+
catchBlock.body().add(errorInvoke);
242+
return tryBlock;
243+
}
244+
231245
public JDefinedClass createDelegatingAnonymousRunnableClass(EBeanHolder holder, JMethod delegatedMethod) {
232246

233247
JCodeModel codeModel = holder.codeModel();
@@ -242,20 +256,9 @@ public JDefinedClass createDelegatingAnonymousRunnableClass(EBeanHolder holder,
242256
runMethod.annotate(Override.class);
243257

244258
JBlock runMethodBody = runMethod.body();
245-
JTryBlock runTry = runMethodBody._try();
246259

247-
runTry.body().add(previousMethodBody);
248-
249-
JCatchBlock runCatch = runTry._catch(classes.RUNTIME_EXCEPTION);
250-
JVar exceptionParam = runCatch.param("e");
251-
252-
JInvocation errorInvoke = classes.LOG.staticInvoke("e");
253-
254-
errorInvoke.arg(holder.generatedClass.name());
255-
errorInvoke.arg("A runtime exception was thrown while executing code in a runnable");
256-
errorInvoke.arg(exceptionParam);
260+
surroundWithTryCatch(holder, runMethodBody, previousMethodBody, "A runtime exception was thrown while executing code in a runnable");
257261

258-
runCatch.body().add(errorInvoke);
259262
return anonymousRunnableClass;
260263
}
261264

0 commit comments

Comments
 (0)