Skip to content

Commit 6495da7

Browse files
committed
Merge branch 'Artyomcool-execution-thread-control' into develop
Conflicts: AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/handler/AnnotationHandlers.java AndroidAnnotations/functional-test-1-5-tests/pom.xml
2 parents c6e3753 + 7abadb1 commit 6495da7

9 files changed

Lines changed: 466 additions & 1 deletion

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package org.androidannotations.annotations;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Retention(RetentionPolicy.CLASS)
9+
@Target(ElementType.METHOD)
10+
public @interface SupposeBackground {
11+
12+
String[] serial() default {};
13+
14+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.androidannotations.annotations;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
/**
9+
* Ensures that method is called from the UI thread. If it is not, then
10+
* {@link java.lang.IllegalStateException} will be thrown (by default).
11+
* //TODO how to change default
12+
*/
13+
@Retention(RetentionPolicy.CLASS)
14+
@Target(ElementType.METHOD)
15+
public @interface SupposeUiThread {
16+
}

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

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.androidannotations.api;
1717

1818
import java.util.ArrayList;
19+
import java.util.Arrays;
1920
import java.util.List;
2021
import java.util.concurrent.Executor;
2122
import java.util.concurrent.ExecutorService;
@@ -25,6 +26,7 @@
2526
import java.util.concurrent.TimeUnit;
2627
import java.util.concurrent.atomic.AtomicBoolean;
2728

29+
import android.os.Looper;
2830
import android.util.Log;
2931

3032
public class BackgroundExecutor {
@@ -33,8 +35,32 @@ public class BackgroundExecutor {
3335

3436
public static Executor DEFAULT_EXECUTOR = Executors.newScheduledThreadPool(2 * Runtime.getRuntime().availableProcessors());
3537
private static Executor executor = DEFAULT_EXECUTOR;
38+
public static final WrongThreadListener DEFAULT_WRONG_THREAD_LISTENER = new WrongThreadListener() {
39+
@Override
40+
public void onUiExpected() {
41+
throw new IllegalStateException("Method invocation is expected from the UI thread");
42+
}
43+
44+
@Override
45+
public void onBgExpected(String... expectedSerials) {
46+
if (expectedSerials.length == 0) {
47+
throw new IllegalStateException("Method invocation is expected from a background thread, but it was called from the UI thread");
48+
}
49+
throw new IllegalStateException("Method invocation is expected from one of serials " + Arrays.toString(expectedSerials) + ", but it was called from the UI thread");
50+
}
51+
52+
@Override
53+
public void onWrongBgSerial(String currentSerial, String... expectedSerials) {
54+
if (currentSerial == null) {
55+
currentSerial = "anonymous";
56+
}
57+
throw new IllegalStateException("Method invocation is expected from one of serials " + Arrays.toString(expectedSerials) + ", but it was called from " + currentSerial + " serial");
58+
}
59+
};
60+
private static WrongThreadListener wrongThreadListener = DEFAULT_WRONG_THREAD_LISTENER;
3661

3762
private static final List<Task> tasks = new ArrayList<Task>();
63+
private static final ThreadLocal<String> currentSerial = new ThreadLocal<String>();
3864

3965
/**
4066
* Execute a runnable after the given delay.
@@ -185,6 +211,14 @@ public static void setExecutor(Executor executor) {
185211
BackgroundExecutor.executor = executor;
186212
}
187213

214+
/**
215+
* Change the WrongThreadListener.
216+
* @param listener the new WrongThreadListener
217+
*/
218+
public static void setWrongThreadListener(WrongThreadListener listener) {
219+
BackgroundExecutor.wrongThreadListener = listener;
220+
}
221+
188222
/**
189223
* Cancel all tasks having the specified <code>id</code>.
190224
*
@@ -219,6 +253,49 @@ public static synchronized void cancelAll(String id, boolean mayInterruptIfRunni
219253
}
220254
}
221255

256+
/**
257+
* Checks if current thread is UI and notifies
258+
* {@link BackgroundExecutor.WrongThreadListener#onUiExpected()} if it doesn't.
259+
*/
260+
public static void checkUiThread() {
261+
if (Looper.getMainLooper().getThread() != Thread.currentThread()) {
262+
wrongThreadListener.onUiExpected();
263+
}
264+
}
265+
266+
/**
267+
* Check if current thread is a background thread and, optionally, restrict it
268+
* with passed serials. If no serials passed and current thread is UI, then
269+
* {@link WrongThreadListener#onBgExpected(String...)} will be called.
270+
* If current thread is not UI and serials list is empty, then method just returns.
271+
* Otherwise, if method was called not during {@link Task} execution or the task has no
272+
* serial, then {@link WrongThreadListener#onWrongBgSerial(String, String...)} will be called
273+
* with null for the first parameter. If task has serial but passed serials don't contain that,
274+
* then {@link WrongThreadListener#onWrongBgSerial(String, String...)} will be called with
275+
* task's serial for the first parameter.
276+
*
277+
* @param serials (optional) list of allowed serials
278+
*/
279+
public static void checkBgThread(String... serials) {
280+
if (serials.length == 0) {
281+
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
282+
wrongThreadListener.onBgExpected(serials);
283+
}
284+
return;
285+
}
286+
String current = currentSerial.get();
287+
if (current == null) {
288+
wrongThreadListener.onWrongBgSerial(null, serials);
289+
return;
290+
}
291+
for (String serial : serials) {
292+
if (serial.equals(current)) {
293+
return;
294+
}
295+
}
296+
wrongThreadListener.onWrongBgSerial(current, serials);
297+
}
298+
222299
/**
223300
* Indicates whether a task with the specified <code>serial</code> has been
224301
* submitted to the executor.
@@ -299,6 +376,7 @@ public void run() {
299376
}
300377

301378
try {
379+
currentSerial.set(serial);
302380
execute();
303381
} finally {
304382
/* handle next tasks */
@@ -313,6 +391,7 @@ private void postExecute() {
313391
/* nothing to do */
314392
return;
315393
}
394+
currentSerial.set(null);
316395
synchronized (BackgroundExecutor.class) {
317396
/* execution complete */
318397
tasks.remove(this);
@@ -332,5 +411,14 @@ private void postExecute() {
332411
}
333412

334413
}
335-
414+
/**
415+
* A callback interface to be notified when current thread, in which method has been invoked,
416+
* is wrong.
417+
* @see #setWrongThreadListener(WrongThreadListener)
418+
*/
419+
public static interface WrongThreadListener {
420+
void onUiExpected();
421+
void onBgExpected(String... expectedSerials);
422+
void onWrongBgSerial(String currentSerial, String... expectedSerials);
423+
}
336424
}

AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/handler/AnnotationHandlers.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,11 @@ public AnnotationHandlers(ProcessingEnvironment processingEnvironment) {
117117
/* UIThreadHandler and BackgroundHandler must be after TraceHandler and IgnoredWhenDetached */
118118
add(new UiThreadHandler(processingEnvironment));
119119
add(new BackgroundHandler(processingEnvironment));
120+
121+
/* SupposeUiThreadHandler and SupposeBackgroundHandler must be
122+
after all handlers that modifies generated method body */
123+
add(new SupposeUiThreadHandler(processingEnvironment));
124+
add(new SupposeBackgroundHandler(processingEnvironment));
120125
}
121126

122127
private void add(AnnotationHandler<? extends GeneratedClassHolder> annotationHandler) {
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package org.androidannotations.handler;
2+
3+
import com.sun.codemodel.JBlock;
4+
import com.sun.codemodel.JClass;
5+
import com.sun.codemodel.JInvocation;
6+
import com.sun.codemodel.JMethod;
7+
8+
import org.androidannotations.annotations.SupposeBackground;
9+
import org.androidannotations.api.BackgroundExecutor;
10+
import org.androidannotations.helper.APTCodeModelHelper;
11+
import org.androidannotations.holder.EComponentHolder;
12+
13+
import javax.annotation.processing.ProcessingEnvironment;
14+
import javax.lang.model.element.Element;
15+
import javax.lang.model.element.ExecutableElement;
16+
17+
import static com.sun.codemodel.JExpr.lit;
18+
19+
public class SupposeBackgroundHandler extends SupposeThreadHandler {
20+
21+
private static final String METHOD_CHECK_BG_THREAD = "checkBgThread";
22+
23+
private final APTCodeModelHelper helper = new APTCodeModelHelper();
24+
25+
public SupposeBackgroundHandler(ProcessingEnvironment processingEnvironment) {
26+
super(SupposeBackground.class, processingEnvironment);
27+
}
28+
29+
@Override
30+
public void process(Element element, EComponentHolder holder) throws Exception {
31+
ExecutableElement executableElement = (ExecutableElement) element;
32+
33+
JMethod delegatingMethod = helper.overrideAnnotatedMethod(executableElement, holder);
34+
35+
JClass bgExecutor = refClass(BackgroundExecutor.class);
36+
37+
SupposeBackground annotation = element.getAnnotation(SupposeBackground.class);
38+
String[] serial = annotation.serial();
39+
JInvocation invocation = bgExecutor.staticInvoke(METHOD_CHECK_BG_THREAD);
40+
for (String s : serial) {
41+
invocation.arg(lit(s));
42+
}
43+
44+
JBlock body = delegatingMethod.body();
45+
body.pos(0);
46+
body.add(invocation);
47+
body.pos(body.getContents().size());
48+
}
49+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.androidannotations.handler;
2+
3+
import org.androidannotations.holder.EComponentHolder;
4+
import org.androidannotations.model.AnnotationElements;
5+
import org.androidannotations.process.IsValid;
6+
7+
import javax.annotation.processing.ProcessingEnvironment;
8+
import javax.lang.model.element.Element;
9+
10+
public abstract class SupposeThreadHandler extends BaseAnnotationHandler<EComponentHolder> {
11+
12+
public SupposeThreadHandler(Class<?> targetClass, ProcessingEnvironment processingEnvironment) {
13+
super(targetClass, processingEnvironment);
14+
}
15+
16+
@Override
17+
protected void validate(Element element, AnnotationElements validatedElements, IsValid valid) {
18+
validatorHelper.enclosingElementHasEnhancedComponentAnnotation(element, validatedElements, valid);
19+
validatorHelper.isNotPrivate(element, valid);
20+
}
21+
22+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package org.androidannotations.handler;
2+
3+
import com.sun.codemodel.JBlock;
4+
import com.sun.codemodel.JClass;
5+
import com.sun.codemodel.JMethod;
6+
7+
import org.androidannotations.annotations.SupposeUiThread;
8+
import org.androidannotations.api.BackgroundExecutor;
9+
import org.androidannotations.helper.APTCodeModelHelper;
10+
import org.androidannotations.holder.EComponentHolder;
11+
12+
import javax.annotation.processing.ProcessingEnvironment;
13+
import javax.lang.model.element.Element;
14+
import javax.lang.model.element.ExecutableElement;
15+
16+
public class SupposeUiThreadHandler extends SupposeThreadHandler {
17+
18+
private static final String METHOD_CHECK_UI_THREAD = "checkUiThread";
19+
20+
private final APTCodeModelHelper helper = new APTCodeModelHelper();
21+
22+
public SupposeUiThreadHandler(ProcessingEnvironment processingEnvironment) {
23+
super(SupposeUiThread.class, processingEnvironment);
24+
}
25+
26+
@Override
27+
public void process(Element element, EComponentHolder holder) throws Exception {
28+
ExecutableElement executableElement = (ExecutableElement) element;
29+
30+
JMethod delegatingMethod = helper.overrideAnnotatedMethod(executableElement, holder);
31+
JBlock body = delegatingMethod.body();
32+
33+
JClass bgExecutor = refClass(BackgroundExecutor.class);
34+
35+
body.pos(0);
36+
body.staticInvoke(bgExecutor, METHOD_CHECK_UI_THREAD);
37+
body.pos(body.getContents().size());
38+
}
39+
}

0 commit comments

Comments
 (0)