1515 */
1616package org .androidannotations .api ;
1717
18- import java .util .ArrayList ;
19- import java .util .HashMap ;
20- import java .util .List ;
21- import java .util .Map ;
18+ import java .util .Collections ;
19+ import java .util .Set ;
20+ import java .util .concurrent .ConcurrentHashMap ;
21+ import java .util .concurrent .ConcurrentLinkedQueue ;
22+ import java .util .concurrent .atomic .AtomicInteger ;
2223
2324import android .os .Handler ;
25+ import android .os .Looper ;
2426
2527/**
2628 * This class provide operations for
2729 * {@link org.androidannotations.annotations.UiThread UiThread} tasks.
2830 */
2931public class UiThreadExecutor {
3032
31- private static final Map <String , List <Runnable >> TASKS = new HashMap <String , List <Runnable >>();
32- private static final Map <Runnable , Handler > HANDLERS = new HashMap <Runnable , Handler >();
33+ private static final Handler HANDLER = new Handler (Looper .getMainLooper ());
34+
35+ private static final ConcurrentHashMap <String , Set <Runnable >> TASKS = new ConcurrentHashMap <String , Set <Runnable >>();
36+
37+ private static final Set <Runnable > JUST_POSTED_TASKS = Collections .newSetFromMap (new ConcurrentHashMap <Runnable , Boolean >());
38+
39+ private static final ConcurrentLinkedQueue <Set <Runnable >> ALLOCATION_STASH = new ConcurrentLinkedQueue <Set <Runnable >>();
40+ private static final AtomicInteger ALLOCATION_STASH_SIZE = new AtomicInteger (0 );
41+ private static final int ALLOCATION_STASH_MAX_SIZE = 16 ;
3342
3443 private UiThreadExecutor () {
3544 // should not be instantiated
@@ -43,17 +52,65 @@ private UiThreadExecutor() {
4352 * the identifier of the task
4453 * @param task
4554 * the task itself
46- * @param handler
47- * the {@link Handler} which runs the task
55+ * @param delay
56+ * the delay or zero to run immediately
4857 */
49- public static synchronized void addTask (String id , Runnable task , Handler handler ) {
50- List <Runnable > runnables = TASKS .get (id );
58+ public static void runTask (String id , Runnable task , long delay ) {
59+ if ("" .equals (id )) {
60+ HANDLER .postDelayed (task , delay );
61+ return ;
62+ }
63+ JUST_POSTED_TASKS .add (task );
64+ HANDLER .postDelayed (task , delay );
65+
66+ Set <Runnable > runnables = TASKS .get (id );
5167 if (runnables == null ) {
52- runnables = new ArrayList <Runnable >();
53- TASKS .put (id , runnables );
68+ runnables = allocateRunnables ();
69+ runnables .add (task );
70+
71+ while (true ) {
72+ Set <Runnable > oldRunnables = TASKS .putIfAbsent (id , runnables );
73+ if (oldRunnables != null ) {
74+ if (oldRunnables .isEmpty ()) {
75+ // it is about to be removed - lets try to replace it
76+ if (!TASKS .replace (id , oldRunnables , runnables )) {
77+ // it is already has bean replaced by different
78+ // thread - lets try again
79+ continue ;
80+ }
81+ } else {
82+ recycle (runnables );
83+ oldRunnables .add (task );
84+ }
85+ }
86+ break ;
87+ }
88+ } else {
89+ runnables .add (task );
90+ }
91+
92+ // if the task has already been completed - make sure to clean up
93+ if (!JUST_POSTED_TASKS .remove (task )) {
94+ done (id , task );
95+ }
96+ }
97+
98+ private static Set <Runnable > allocateRunnables () {
99+ Set <Runnable > runnables = ALLOCATION_STASH .poll ();
100+ if (runnables != null ) {
101+ ALLOCATION_STASH_SIZE .getAndDecrement ();
102+ return runnables ;
103+ }
104+ return Collections .newSetFromMap (new ConcurrentHashMap <Runnable , Boolean >());
105+ }
106+
107+ private static void recycle (Set <Runnable > runnables ) {
108+ if (ALLOCATION_STASH_SIZE .getAndIncrement () < ALLOCATION_STASH_MAX_SIZE ) {
109+ runnables .clear ();
110+ ALLOCATION_STASH .offer (runnables );
111+ } else {
112+ ALLOCATION_STASH_SIZE .getAndDecrement ();
54113 }
55- runnables .add (task );
56- HANDLERS .put (task , handler );
57114 }
58115
59116 /**
@@ -62,24 +119,35 @@ public static synchronized void addTask(String id, Runnable task, Handler handle
62119 * @param id
63120 * the cancellation identifier
64121 */
65- public static synchronized void cancelAll (String id ) {
66- List <Runnable > runnables = TASKS .remove (id );
122+ public static void cancelAll (String id ) {
123+ Set <Runnable > runnables = TASKS .remove (id );
67124 if (runnables != null ) {
68125 for (Runnable runnable : runnables ) {
69- HANDLERS . remove ( runnable ) .removeCallbacks (runnable );
126+ HANDLER .removeCallbacks (runnable );
70127 }
128+ recycle (runnables );
71129 }
72130 }
73131
74- public static synchronized void done (String id , Runnable runnable ) {
75- Handler handler = HANDLERS .remove (runnable );
76- if (handler != null ) {
77- List <Runnable > runnables = TASKS .get (id );
78- runnables .remove (runnable );
79- // potentially empty array stays in map for reducing garbage
80- // collecting -
81- // it is highly possible, that array will be reused at the next
82- // addTask call
132+ /**
133+ * Should be called after the task has been executed. It is ok to call it
134+ * more then once for the case of cleaning up.
135+ *
136+ * @param id
137+ * the task id
138+ * @param runnable
139+ * the task itself
140+ */
141+ public static void done (String id , Runnable runnable ) {
142+ JUST_POSTED_TASKS .remove (runnable );
143+ Set <Runnable > runnables = TASKS .get (id );
144+ if (runnables != null ) {
145+ if (runnables .remove (runnable )) {
146+ if (runnables .isEmpty ()) { // if it is empty
147+ TASKS .remove (id , runnables );
148+ recycle (runnables );
149+ }
150+ }
83151 }
84152 }
85153
0 commit comments