Skip to content
This repository was archived by the owner on Feb 26, 2023. It is now read-only.

Commit 9f75356

Browse files
author
Drozdov Artyom
committed
add: cleaning up the map at cost of synchronization
fix: synchronization for removing tokens refactoring: tests moved to test15 package and UI handlers hacks moved into TestSampleRoboApplication_
1 parent f0478d7 commit 9f75356

3 files changed

Lines changed: 101 additions & 36 deletions

File tree

‎AndroidAnnotations/androidannotations-api/src/main/java/org/androidannotations/api/UiThreadExecutor.java‎

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,32 @@
1717

1818
import android.os.Handler;
1919
import android.os.Looper;
20+
import android.os.Message;
2021
import android.os.SystemClock;
2122

22-
import java.util.concurrent.ConcurrentHashMap;
23+
import java.util.HashMap;
24+
import java.util.Map;
2325

2426
/**
2527
* This class provide operations for
2628
* {@link org.androidannotations.annotations.UiThread UiThread} tasks.
2729
*/
2830
public class UiThreadExecutor {
2931

30-
static final Handler HANDLER = new Handler(Looper.getMainLooper());
32+
private static final Handler HANDLER = new Handler(Looper.getMainLooper()) {
33+
@Override
34+
public void handleMessage(Message msg) {
35+
Runnable callback = msg.getCallback();
36+
if (callback != null) {
37+
callback.run();
38+
decrementToken((Token)msg.obj);
39+
} else {
40+
super.handleMessage(msg);
41+
}
42+
}
43+
};
3144

32-
static final ConcurrentHashMap<String, Object> TOKENS = new ConcurrentHashMap<String, Object>();
45+
private static final Map<String, Token> TOKENS = new HashMap<String, Token>();
3346

3447
private UiThreadExecutor() {
3548
// should not be instantiated
@@ -52,20 +65,32 @@ public static void runTask(String id, Runnable task, long delay) {
5265
return;
5366
}
5467
long time = SystemClock.uptimeMillis() + delay;
55-
HANDLER.postAtTime(task, getToken(id), time);
68+
HANDLER.postAtTime(task, nextToken(id), time);
5669
}
5770

58-
private static Object getToken(String id) {
59-
Object token = TOKENS.get(id);
60-
if (token == null) {
61-
token = new Object();
62-
Object oldObject = TOKENS.putIfAbsent(id, token);
63-
//if a concurrent thread was faster
64-
if (oldObject != null) {
65-
token = oldObject;
71+
private static Token nextToken(String id) {
72+
synchronized (TOKENS) {
73+
Token token = TOKENS.get(id);
74+
if (token == null) {
75+
token = new Token(id);
76+
TOKENS.put(id, token);
77+
}
78+
token.runnablesCount++;
79+
return token;
80+
}
81+
}
82+
83+
private static void decrementToken(Token token) {
84+
synchronized (TOKENS) {
85+
if (--token.runnablesCount == 0) {
86+
String id = token.id;
87+
Token old = TOKENS.remove(id);
88+
if (old != token) {
89+
//a runnable finished after cancelling, we just removed a wrong token, lets put it back
90+
TOKENS.put(id, old);
91+
}
6692
}
6793
}
68-
return token;
6994
}
7095

7196
/**
@@ -75,12 +100,24 @@ private static Object getToken(String id) {
75100
* the cancellation identifier
76101
*/
77102
public static void cancelAll(String id) {
78-
Object token = TOKENS.get(id);
103+
Token token;
104+
synchronized (TOKENS) {
105+
token = TOKENS.remove(id);
106+
}
79107
if (token == null) {
80108
//nothing to cancel
81109
return;
82110
}
83111
HANDLER.removeCallbacksAndMessages(token);
84112
}
85113

114+
private static class Token {
115+
int runnablesCount = 0;
116+
final String id;
117+
118+
private Token(String id) {
119+
this.id = id;
120+
}
121+
}
122+
86123
}

AndroidAnnotations/functional-test-1-5/src/test/java/org/androidannotations/api/UiThreadExecutorTest.java renamed to AndroidAnnotations/functional-test-1-5/src/test/java/org/androidannotations/test15/UiThreadExecutorTest.java

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
1-
package org.androidannotations.api;
1+
package org.androidannotations.test15;
22

3-
import android.os.Handler;
4-
import android.os.Message;
5-
import org.junit.BeforeClass;
3+
import org.androidannotations.api.UiThreadExecutor;
64
import org.junit.Test;
75
import org.junit.runner.RunWith;
86
import org.robolectric.Robolectric;
97
import org.robolectric.RobolectricTestRunner;
10-
import org.robolectric.shadows.ShadowHandler;
118

129
import java.util.concurrent.CountDownLatch;
1310
import java.util.concurrent.TimeUnit;
@@ -18,34 +15,31 @@
1815
@RunWith(RobolectricTestRunner.class)
1916
public class UiThreadExecutorTest {
2017

21-
//remove this after upgrading robolectric to 3+
22-
@BeforeClass
23-
public static void hackOldRobolectric() {
24-
final Handler handler = UiThreadExecutor.HANDLER;
25-
ShadowHandler shadowHandler = Robolectric. shadowOf_(handler);
26-
shadowHandler.__constructor__(new Handler.Callback() {
18+
@Test
19+
public void oneTaskTest() throws Exception {
20+
final AtomicBoolean done = new AtomicBoolean(false);
21+
UiThreadExecutor.runTask("test", new Runnable() {
2722
@Override
28-
public boolean handleMessage(Message msg) {
29-
//in the robolectric 2.4 there is a strange code - they call Handler's handleMessage (it do nothing)
30-
//instead of dispatch, that actually do the job. This is just a dirty work-around. It should be removed
31-
//with newer version of robolectric.
32-
handler.dispatchMessage(msg);
33-
return true;
23+
public void run() {
24+
done.set(true);
3425
}
35-
});
26+
}, 10);
27+
Robolectric.runUiThreadTasksIncludingDelayedTasks();
28+
assertTrue("Task is still under execution", done.get());
3629
}
3730

3831
@Test
39-
public void oneTaskTest() throws Exception {
32+
public void oneTaskCancelTest() throws Exception {
4033
final AtomicBoolean done = new AtomicBoolean(false);
4134
UiThreadExecutor.runTask("test", new Runnable() {
4235
@Override
4336
public void run() {
4437
done.set(true);
4538
}
46-
}, 0);
39+
}, 10);
40+
UiThreadExecutor.cancelAll("test");
4741
Robolectric.runUiThreadTasksIncludingDelayedTasks();
48-
assertTrue("Task is still under execution", done.get());
42+
assertFalse("Task is not cancelled", done.get());
4943
}
5044

5145
@Test
@@ -61,7 +55,7 @@ public void run() {
6155
await(taskStartedLatch);
6256
taskFinishedLatch.countDown();
6357
}
64-
}, 0);
58+
}, 10);
6559
taskStartedLatch.countDown();
6660
Robolectric.runUiThreadTasksIncludingDelayedTasks();
6761
}

‎AndroidAnnotations/functional-test-1-5/src/test/java/org/androidannotations/test15/roboguice/TestSampleRoboApplication_.java‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,54 @@
11
package org.androidannotations.test15.roboguice;
22

3+
import java.lang.reflect.Field;
34
import java.lang.reflect.Method;
45

6+
import android.os.Handler;
7+
import android.os.Message;
8+
import org.androidannotations.api.UiThreadExecutor;
59
import org.robolectric.Robolectric;
610
import org.robolectric.TestLifecycleApplication;
711

12+
import org.robolectric.shadows.ShadowHandler;
813
import roboguice.RoboGuice;
914
import android.app.Application;
1015

1116
// CHECKSTYLE:OFF
1217
public class TestSampleRoboApplication_ extends Application implements TestLifecycleApplication {
18+
1319
@Override
1420
public void onCreate() {
1521
super.onCreate();
1622

1723
RoboGuice.overrideApplicationInjector(this, RoboGuice.newDefaultRoboModule(this), new RobolectricSampleModule());
24+
hackHandler();
25+
26+
27+
}
28+
29+
//TODO remove this after upgrading robolectric to 3+
30+
private void hackHandler() {
31+
final Handler handler;
32+
try {
33+
Field handlerField = UiThreadExecutor.class.getDeclaredField("HANDLER");
34+
handlerField.setAccessible(true);
35+
handler = (Handler) handlerField.get(null);
36+
} catch (IllegalAccessException e) {
37+
throw new RuntimeException(e);
38+
} catch (NoSuchFieldException e) {
39+
throw new RuntimeException(e);
40+
}
41+
ShadowHandler shadowHandler = Robolectric.shadowOf_(handler);
42+
shadowHandler.__constructor__(new Handler.Callback() {
43+
@Override
44+
public boolean handleMessage(Message msg) {
45+
//in the robolectric 2.4 there is a strange code - they call Handler's handleMessage (it do nothing)
46+
//instead of dispatch, that actually do the job. This is just a dirty work-around. It should be removed
47+
//with newer version of robolectric.
48+
handler.dispatchMessage(msg);
49+
return true;
50+
}
51+
});
1852
}
1953

2054
@Override

0 commit comments

Comments
 (0)