Skip to content
This repository was archived by the owner on Feb 26, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,14 +1,52 @@
package org.androidannotations.annotations;

import org.androidannotations.api.BackgroundExecutor;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Ensures that the method is called from the background thread with (optionally) restrictions by allowed serials.
* If it is not called from a supposed background thread, then {@link IllegalStateException}
* will be thrown (by default).
* <p/>
* <blockquote> <b>Example</b> :
* <p/>
* <pre>
* &#064;EBean
* public class MyBean {
*
* &#064;SupposeBackground
* boolean someMethodThatShouldNotBeCalledFromUiThread() {
* //if this method will be called from the UI-thread an exception will be thrown
* }
*
* &#064;SupposeBackground(serial = {"serial1", "serial2"})
* boolean someMethodThatShouldBeCalledFromSerial1OrSerial2() {
* //if this method will be called from another thread then a background thread with a
* //serial "serial1" or "serial2", an exception will be thrown
* }
*
* }
* </pre>
* <p/>
* </blockquote>
*
* @see BackgroundExecutor#setWrongThreadListener(BackgroundExecutor.WrongThreadListener)
* @see BackgroundExecutor#DEFAULT_WRONG_THREAD_LISTENER
* @see BackgroundExecutor#checkBgThread(String...)
*/
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface SupposeBackground {

String[] serial() default {};
/**
* Allowed serials to restrict a calling thread. If it is an empty list, then any background thread is allowed.
*
* @see BackgroundExecutor#checkBgThread(String...)
*/
String[] serial() default {};

}
Original file line number Diff line number Diff line change
@@ -1,14 +1,35 @@
package org.androidannotations.annotations;

import org.androidannotations.api.BackgroundExecutor;

import java.lang.IllegalStateException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Ensures that method is called from the UI thread. If it is not, then
* {@link java.lang.IllegalStateException} will be thrown (by default).
* //TODO how to change default
* {@link IllegalStateException} will be thrown (by default).
*
* <blockquote> <b>Example</b> :
*
* <pre>
* &#064;EBean
* public class MyBean {
*
* &#064;SupposeUiThread
* boolean someMethodThatShouldBeCalledOnlyFromUiThread() {
* //if this method will be called from a background thread an exception will be thrown
* }
* }
* </pre>
*
* </blockquote>
*
* @see BackgroundExecutor#setWrongThreadListener(BackgroundExecutor.WrongThreadListener)
* @see BackgroundExecutor#DEFAULT_WRONG_THREAD_LISTENER
* @see BackgroundExecutor#checkUiThread()
*/
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,39 +28,51 @@

import android.os.Looper;
import android.util.Log;
import org.androidannotations.annotations.SupposeBackground;
import org.androidannotations.annotations.SupposeUiThread;

public class BackgroundExecutor {

private static final String TAG = "BackgroundExecutor";

public static Executor DEFAULT_EXECUTOR = Executors.newScheduledThreadPool(2 * Runtime.getRuntime().availableProcessors());
private static Executor executor = DEFAULT_EXECUTOR;
public static final WrongThreadListener DEFAULT_WRONG_THREAD_LISTENER = new WrongThreadListener() {
@Override
public void onUiExpected() {
throw new IllegalStateException("Method invocation is expected from the UI thread");
}

@Override
public void onBgExpected(String... expectedSerials) {
if (expectedSerials.length == 0) {
throw new IllegalStateException("Method invocation is expected from a background thread, but it was called from the UI thread");
}
throw new IllegalStateException("Method invocation is expected from one of serials " + Arrays.toString(expectedSerials) + ", but it was called from the UI thread");
}

@Override
public void onWrongBgSerial(String currentSerial, String... expectedSerials) {
if (currentSerial == null) {
currentSerial = "anonymous";
}
throw new IllegalStateException("Method invocation is expected from one of serials " + Arrays.toString(expectedSerials) + ", but it was called from " + currentSerial + " serial");
}
};
private static WrongThreadListener wrongThreadListener = DEFAULT_WRONG_THREAD_LISTENER;

/**
* The default invocation handler for wrong thread execution.
* It just throws {@link IllegalStateException} with explanation what is going wrong.
*
* @see #setWrongThreadListener(BackgroundExecutor.WrongThreadListener)
* @see SupposeBackground
* @see SupposeUiThread
*/
public static final WrongThreadListener DEFAULT_WRONG_THREAD_LISTENER = new WrongThreadListener() {
@Override
public void onUiExpected() {
throw new IllegalStateException("Method invocation is expected from the UI thread");
}

@Override
public void onBgExpected(String... expectedSerials) {
if (expectedSerials.length == 0) {
throw new IllegalStateException("Method invocation is expected from a background thread, but it was called from the UI thread");
}
throw new IllegalStateException("Method invocation is expected from one of serials " + Arrays.toString(expectedSerials) + ", but it was called from the UI thread");
}

@Override
public void onWrongBgSerial(String currentSerial, String... expectedSerials) {
if (currentSerial == null) {
currentSerial = "anonymous";
}
throw new IllegalStateException("Method invocation is expected from one of serials " + Arrays.toString(expectedSerials) + ", but it was called from " + currentSerial + " serial");
}
};

private static WrongThreadListener wrongThreadListener = DEFAULT_WRONG_THREAD_LISTENER;

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

/**
* Execute a runnable after the given delay.
Expand Down Expand Up @@ -211,17 +223,19 @@ public static void setExecutor(Executor executor) {
BackgroundExecutor.executor = executor;
}

/**
* Change the WrongThreadListener.
* @param listener the new WrongThreadListener
*/
public static void setWrongThreadListener(WrongThreadListener listener) {
BackgroundExecutor.wrongThreadListener = listener;
}
/**
* Changes the default {@link WrongThreadListener}.
* To restore the default one use {@link #DEFAULT_WRONG_THREAD_LISTENER}.
*
* @param listener the new {@link WrongThreadListener}
*/
public static void setWrongThreadListener(WrongThreadListener listener) {
wrongThreadListener = listener;
}

/**
* Cancel all tasks having the specified <code>id</code>.
*
*
* @param id
* the cancellation identifier
* @param mayInterruptIfRunning
Expand Down Expand Up @@ -253,57 +267,56 @@ public static synchronized void cancelAll(String id, boolean mayInterruptIfRunni
}
}

/**
* Checks if current thread is UI and notifies
* {@link BackgroundExecutor.WrongThreadListener#onUiExpected()} if it doesn't.
*/
public static void checkUiThread() {
if (Looper.getMainLooper().getThread() != Thread.currentThread()) {
wrongThreadListener.onUiExpected();
}
}

/**
* Check if current thread is a background thread and, optionally, restrict it
* with passed serials. If no serials passed and current thread is UI, then
* {@link WrongThreadListener#onBgExpected(String...)} will be called.
* If current thread is not UI and serials list is empty, then method just returns.
* Otherwise, if method was called not during {@link Task} execution or the task has no
* serial, then {@link WrongThreadListener#onWrongBgSerial(String, String...)} will be called
* with null for the first parameter. If task has serial but passed serials don't contain that,
* then {@link WrongThreadListener#onWrongBgSerial(String, String...)} will be called with
* task's serial for the first parameter.
*
* @param serials (optional) list of allowed serials
*/
public static void checkBgThread(String... serials) {
if (serials.length == 0) {
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
wrongThreadListener.onBgExpected(serials);
}
return;
}
String current = currentSerial.get();
if (current == null) {
wrongThreadListener.onWrongBgSerial(null, serials);
return;
}
for (String serial : serials) {
if (serial.equals(current)) {
return;
}
}
wrongThreadListener.onWrongBgSerial(current, serials);
}
/**
* Checks if the current thread is UI thread and notifies
* {@link BackgroundExecutor.WrongThreadListener#onUiExpected()} if it doesn't.
*/
public static void checkUiThread() {
if (Looper.getMainLooper().getThread() != Thread.currentThread()) {
wrongThreadListener.onUiExpected();
}
}

/**
* Checks if the current thread is a background thread and, optionally, restricts it
* with passed serials. If no serials passed and current thread is the UI thread, then
* {@link WrongThreadListener#onBgExpected(String...)} will be called.
* If the current thread is not UI and serials list is empty, then this method just returns.
* Otherwise, if the method was called not during {@link Task} execution or the task has no
* serial, then the {@link WrongThreadListener#onWrongBgSerial(String, String...)} will be called
* with null for the first parameter. If task has a serial but passed serials don't contain that,
* then {@link WrongThreadListener#onWrongBgSerial(String, String...)} will be called with
* the task's serial for the first parameter.
*
* @param serials (optional) list of allowed serials
*/
public static void checkBgThread(String... serials) {
if (serials.length == 0) {
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
wrongThreadListener.onBgExpected(serials);
}
return;
}
String current = currentSerial.get();
if (current == null) {
wrongThreadListener.onWrongBgSerial(null, serials);
return;
}
for (String serial : serials) {
if (serial.equals(current)) {
return;
}
}
wrongThreadListener.onWrongBgSerial(current, serials);
}

/**
* Indicates whether a task with the specified <code>serial</code> has been
* submitted to the executor.
*
* @param serial
* the serial queue
*
* @param serial the serial queue
* @return <code>true</code> if such a task has been submitted,
* <code>false</code> otherwise
* <code>false</code> otherwise
*/
private static boolean hasSerialRunning(String serial) {
for (Task task : tasks) {
Expand Down Expand Up @@ -376,7 +389,7 @@ public void run() {
}

try {
currentSerial.set(serial);
currentSerial.set(serial);
execute();
} finally {
/* handle next tasks */
Expand All @@ -391,7 +404,7 @@ private void postExecute() {
/* nothing to do */
return;
}
currentSerial.set(null);
currentSerial.set(null);
synchronized (BackgroundExecutor.class) {
/* execution complete */
tasks.remove(this);
Expand All @@ -411,14 +424,50 @@ private void postExecute() {
}

}
/**
* A callback interface to be notified when current thread, in which method has been invoked,
* is wrong.
* @see #setWrongThreadListener(WrongThreadListener)
*/
public static interface WrongThreadListener {
void onUiExpected();
void onBgExpected(String... expectedSerials);
void onWrongBgSerial(String currentSerial, String... expectedSerials);
}

/**
* A callback interface to be notified when a method invocation is expected from another thread.
*
* @see #setWrongThreadListener(WrongThreadListener)
* @see #checkUiThread()
* @see #checkBgThread(String...)
* @see SupposeUiThread
* @see SupposeBackground
*/
public static interface WrongThreadListener {

/**
* Will be called, if the method is supposed to be called from the UI-thread, but was called from a background

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you mean "but not was called from one".

* thread.
*
* @see SupposeUiThread
* @see #setWrongThreadListener(WrongThreadListener)
* @see #DEFAULT_WRONG_THREAD_LISTENER
*/
void onUiExpected();

/**
* Will be called, if the method is supposed to be called from a background thread, but was called from the
* UI-thread.
*
* @param expectedSerials a list of allowed serials. If any background thread is allowed the list will be empty.
* @see SupposeBackground
* @see #setWrongThreadListener(WrongThreadListener)
* @see #DEFAULT_WRONG_THREAD_LISTENER
*/
void onBgExpected(String... expectedSerials);

/**
* Will be called, if the method is supposed to be called from a background thread with one of
* {@code expectedSerials}, but was called from a {@code currentSerial}. {@code currentSerial} will be null,
* if it is called from a background thread without a serial.
*
* @param currentSerial the serial of caller thread or null if there is no serial
* @param expectedSerials a list of allowed serials
* @see SupposeBackground
* @see #setWrongThreadListener(WrongThreadListener)
* @see #DEFAULT_WRONG_THREAD_LISTENER
*/
void onWrongBgSerial(String currentSerial, String... expectedSerials);
}
}