Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
This repository was archived by the owner on Feb 26, 2023. It is now read-only.
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,39 @@
*
* </blockquote>
*
* <h2>Cancellation</h2>
* <p>
* You can cancel UiThread tasks if you provide an id (which cannot be an empty
* string) with the {@link #id()} parameter. Please note tasks which use
* <code>REUSE</code> {@link #propagation()} cannot have an id hence cannot be
* cancelled. To cancel all {@link UiThread} tasks with a given id, call
* {@link org.androidannotations.api.UiThreadExecutor#cancelAll(String)
* UiThreadExecutor#cancelAll(String)}.
* </p>
*
* <blockquote> <b>Example</b> :
*
* <pre>
* &#064;EBean
* public class MyBean {
*
* &#064;UiThread(id = &quot;myId&quot;)
* void uiThreadTask() {
* // do sg
* }
* }
*
* ...
*
* UiThreadExecutor.cancelAll(&quot;myId&quot;);
* </pre>
*
* </blockquote>
*
*
* @see Background
* @see android.os.Handler
* @see org.androidannotations.api.UiThreadExecutor#cancelAll(String)
*/
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
Expand Down Expand Up @@ -127,4 +158,17 @@ public enum Propagation {
*/
REUSE
}

/**
* Identifier for cancellation.
*
* To cancel all tasks having a specified id:
*
* <pre>
* UiThreadExecutor.cancelAll(&quot;my_background_id&quot;);
* </pre>
*
* @return the id for cancellation
*/
String id() default "";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/**
* Copyright (C) 2010-2015 eBusiness Information, Excilys Group
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed To in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.androidannotations.api;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;

import java.util.HashMap;
import java.util.Map;

/**
* This class provide operations for
* {@link org.androidannotations.annotations.UiThread UiThread} tasks.
*/
public class UiThreadExecutor {

private static final Handler HANDLER = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
Runnable callback = msg.getCallback();

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.

@Artyomcool i almost forgot... I told you that we should run this in a try-finally block, so we make sure we remove the token even if the client code throws a RuntimeException.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

After you've got an exception here ui thread is effectively dead. Everything after that has no point.

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 do not agree. We store everything in static fields, so they are only cleared if the process is killed. If a RuntimeException happen in the ui thread, the Activity will likely crash, but it does not mean the process is killed. I think.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Sorry, but you a not correct here. There is only one ui thread per process. When exception happens during looping, there is no way to restore it, even if you catch it in some way. Also, components never crash by themselves, only with a whole process. And it is very-very good thing, actually.

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.

No need to be sorry. :) You are right. I may have confused this with something
else, and just wrote down without really thinking it though.
I just tested this, and it goes up to ZygoteInit.main and dies.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

But we could add some parameter to catch and log exceptions. Not sure if we should.

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.

if (callback != null) {
callback.run();
decrementToken((Token)msg.obj);
} else {
super.handleMessage(msg);
}
}
};

private static final Map<String, Token> TOKENS = new HashMap<String, Token>();

private UiThreadExecutor() {
// should not be instantiated
}

/**
* Store a new task in the map for providing cancellation. This method is
* used by AndroidAnnotations and not intended to be called by clients.
*
* @param id
* the identifier of the task
* @param task
* the task itself
* @param delay
* the delay or zero to run immediately
*/
public static void runTask(String id, Runnable task, long delay) {
if ("".equals(id)) {
HANDLER.postDelayed(task, delay);
return;
}
long time = SystemClock.uptimeMillis() + delay;
HANDLER.postAtTime(task, nextToken(id), time);
}

private static Token nextToken(String id) {
synchronized (TOKENS) {
Token token = TOKENS.get(id);
if (token == null) {
token = new Token(id);
TOKENS.put(id, token);
}
token.runnablesCount++;
return token;
}
}

private static void decrementToken(Token token) {
synchronized (TOKENS) {
if (--token.runnablesCount == 0) {
String id = token.id;
Token old = TOKENS.remove(id);
if (old != token) {
//a runnable finished after cancelling, we just removed a wrong token, lets put it back
TOKENS.put(id, old);
}
}
}
}

/**
* Cancel all tasks having the specified <code>id</code>.
*
* @param id
* the cancellation identifier
*/
public static void cancelAll(String id) {
Token token;
synchronized (TOKENS) {
token = TOKENS.remove(id);
}
if (token == null) {
//nothing to cancel
return;
}
HANDLER.removeCallbacksAndMessages(token);
}

private static class Token {
int runnablesCount = 0;
final String id;

private Token(String id) {
this.id = id;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@
import javax.lang.model.element.ExecutableElement;

import org.androidannotations.annotations.UiThread;
import org.androidannotations.api.UiThreadExecutor;
import org.androidannotations.helper.APTCodeModelHelper;
import org.androidannotations.holder.EComponentHolder;
import org.androidannotations.model.AnnotationElements;
import org.androidannotations.process.IsValid;

import com.sun.codemodel.JBlock;
import com.sun.codemodel.JClass;
Expand All @@ -40,13 +43,21 @@ public class UiThreadHandler extends AbstractRunnableHandler {
private static final String METHOD_CUR_THREAD = "currentThread";
private static final String METHOD_MAIN_LOOPER = "getMainLooper";
private static final String METHOD_GET_THREAD = "getThread";
private static final String METHOD_RUN_TASK = "runTask";

private final APTCodeModelHelper codeModelHelper = new APTCodeModelHelper();

public UiThreadHandler(ProcessingEnvironment processingEnvironment) {
super(UiThread.class, processingEnvironment);
}

@Override
public void validate(Element element, AnnotationElements validatedElements, IsValid valid) {
super.validate(element, validatedElements, valid);

validatorHelper.usesEnqueueIfHasId(element, valid);
}

@Override
public void process(Element element, EComponentHolder holder) throws Exception {
ExecutableElement executableElement = (ExecutableElement) element;
Expand All @@ -58,16 +69,14 @@ public void process(Element element, EComponentHolder holder) throws Exception {
long delay = annotation.delay();
UiThread.Propagation propagation = annotation.propagation();

if (delay == 0) {
if (propagation == UiThread.Propagation.REUSE) {
// Put in the check for the UI thread.
addUIThreadCheck(delegatingMethod, previousBody, holder);
}

delegatingMethod.body().invoke(holder.getHandler(), "post").arg(_new(anonymousRunnableClass));
} else {
delegatingMethod.body().invoke(holder.getHandler(), "postDelayed").arg(_new(anonymousRunnableClass)).arg(lit(delay));
if (delay == 0 && propagation == UiThread.Propagation.REUSE) {
// Put in the check for the UI thread.
addUIThreadCheck(delegatingMethod, previousBody, holder);
}
delegatingMethod.body().add(refClass(UiThreadExecutor.class).staticInvoke(METHOD_RUN_TASK)
.arg(annotation.id())
.arg(_new(anonymousRunnableClass))
.arg(lit(delay)));
}

/**
Expand All @@ -91,4 +100,5 @@ private void addUIThreadCheck(JMethod delegatingMethod, JBlock previousBody, ECo
JBlock thenBlock = con._then().add(previousBody);
thenBlock._return();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@
import org.androidannotations.annotations.OnActivityResult;
import org.androidannotations.annotations.Receiver;
import org.androidannotations.annotations.Trace;
import org.androidannotations.annotations.UiThread;
import org.androidannotations.annotations.UiThread.Propagation;
import org.androidannotations.annotations.ViewById;
import org.androidannotations.annotations.WakeLock;
import org.androidannotations.annotations.WakeLock.Level;
Expand Down Expand Up @@ -1498,4 +1500,12 @@ public void hasSupportV4JarIfLocal(Element element, IsValid valid) {
}
}

public void usesEnqueueIfHasId(Element element, IsValid valid) {
UiThread annotation = element.getAnnotation(UiThread.class);

if (!"".equals(annotation.id()) && annotation.propagation() == Propagation.REUSE) {
annotationHelper.printAnnotationError(element, "An id only can be used with Propagation.ENQUEUE");
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,19 @@

import com.sun.codemodel.JBlock;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JExpression;
import com.sun.codemodel.JFieldRef;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JInvocation;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JVar;

public abstract class EComponentHolder extends BaseGeneratedClassHolder {

private static final String METHOD_MAIN_LOOPER = "getMainLooper";

protected JExpression contextRef;
protected JMethod init;
private JVar resourcesRef;
private JFieldVar powerManagerRef;
private Map<TypeMirror, JFieldVar> databaseHelperRefs = new HashMap<TypeMirror, JFieldVar>();
private JVar handler;

public EComponentHolder(ProcessHolder processHolder, TypeElement annotatedElement) throws Exception {
super(processHolder, annotatedElement);
Expand Down Expand Up @@ -124,17 +118,4 @@ protected JFieldVar setDatabaseHelperRef(TypeMirror databaseHelperTypeMirror) {
return databaseHelperRef;
}

public JVar getHandler() {
if (handler == null) {
setHandler();
}
return handler;
}

private void setHandler() {
JClass handlerClass = classes().HANDLER;
JClass looperClass = classes().LOOPER;
JInvocation arg = JExpr._new(handlerClass).arg(looperClass.staticInvoke(METHOD_MAIN_LOOPER));
handler = generatedClass.field(JMod.PRIVATE, handlerClass, "handler_", arg);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public class MyListFragment extends ListFragment {

boolean didExecute;

boolean uiThreadWithIdDidExecute;

@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Expand All @@ -61,6 +63,11 @@ void uiThreadIgnored() {
didExecute = true;
}

@UiThread(id = "id")
void uiThreadWithId() {
uiThreadWithIdDidExecute = true;
}

@Background
void backgroundThread() {
didExecute = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,36 +341,6 @@ public void execute(Runnable command) {
}
}

@Test
public void assertHandlerWithMainThread() throws Exception {
/*
* For this test we need to recreate the activity in a separate thread,
* in order to check the handler is well associated to the main thread.
*/
final ThreadActivity_[] threadActivityHolder = new ThreadActivity_[1];

new Thread(new Runnable() {
@Override
public void run() {
synchronized (threadActivityHolder) {
threadActivityHolder[0] = Robolectric.buildActivity(ThreadActivity_.class).create().get();
threadActivityHolder.notify();
}
}
}).start();
synchronized (threadActivityHolder) {
do {
threadActivityHolder.wait();
} while (threadActivityHolder[0] == null);
}

Field handlerField = ThreadActivity_.class.getDeclaredField("handler_");
handlerField.setAccessible(true);

Handler handler = (Handler) handlerField.get(threadActivityHolder[0]);
Assert.assertTrue("Handler field not associated to the main thread", handler.getLooper() == Looper.getMainLooper());
}

@Test
public void propagateExceptionToGlobalExceptionHandler() {
/* set an executor with 4 threads */
Expand Down
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.