1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
|
# Executors
go/sysui-executors
[TOC]
## TLDR
In SystemUI, we are encouraging the use of Java's [Executor][Executor] over
Android's [Handler][Handler] when shuffling a [Runnable][Runnable] between
threads or delaying the execution of a Runnable. We have an implementation of
Executor available, as well as our own sub-interface,
[DelayableExecutor][DelayableExecutor] available. For test,
[FakeExecutor][FakeExecutor] is available.
[Executor]: https://developer.android.com/reference/java/util/concurrent/Executor.html
[Handler]: https://developer.android.com/reference/android/os/Handler
[Runnable]: https://developer.android.com/reference/java/lang/Runnable.html
[DelayableExecutor]: /packages/SystemUI/src/com/android/systemui/util/concurrency/DelayableExecutor.java
[FakeExecutor]: /packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java
## Rationale
Executors make testing easier and are generally more flexible than Handlers.
They are defined as an interface, making it easy to swap in fake implementations
for testing. This also makes it easier to supply alternate implementations
generally speaking - shared thread pools; priority queues; etc.
For testing, whereas a handler involves trying to directly control its
underlying Looper (using things like `Thread.sleep()` as well as overriding
internal behaviors), an Executor implementation can be made to be directly
controllable and inspectable.
See also go/executors-for-the-android-engineer
## Available Executors
At present, there are two interfaces of Executor avaiable, each implemented, and
each with two instances - `@Background` and `@Main`.
### Executor
The simplest Executor available implements the interface directly, making
available one method: `Executor.execute()`. You can access an implementation of
this Executor through Dependency Injection:
```java
public class Foobar {
@Inject
public Foobar(@Background Executor bgExecutor) {
bgExecutor.execute(new Runnable() {
// ...
});
}
}
```
`@Main` will give you an Executor that runs on the ui thread. `@Background` will
give you one that runs on a _shared_ non-ui thread. If you ask for an
non-annotated Executor, you will get the `@Background` Executor.
We do not currently have support for creating an Executor on a new, virgin
thread. We do not currently support any sort of shared pooling of threads. If
you require either of these, please reach out.
### DelayableExecutor
[DelayableExecutor][DelayableExecutor] is the closest analogue we provide to
Handler. It adds `executeDelayed(Runnable r, long delayMillis)` and
`executeAtTime(Runnable r, long uptimeMillis)` to the interface, just like
Handler's [postDelayed][postDelayed] and [postAtTime][postAttime]. It also adds
the option to supply a [TimeUnit][TimeUnit] as a third argument.
A DelayableExecutor can be accessed via Injection just like a standard Executor.
In fact, at this time, it shares the same underlying thread as our basic
Executor.
```java
public class Foobar {
@Inject
public Foobar(@Background DelayableExecutor bgExecutor) {
bgExecutor.executeDelayed(new Runnable() {
// ...
}, 1, TimeUnit.MINUTES);
}
}
```
Unlike Handler, the added methods return a Runnable that, when run, cancels the
originally supplied Runnable if it has not yet started execution:
```java
public class Foobar {
@Inject
public Foobar(@Background DelayableExecutor bgExecutor) {
Runnable cancel = bgExecutor.executeDelayed(new Runnable() {
// ...
}, 1, TimeUnit.MINUTES);
cancel.run(); // The supplied Runnable will (probably) not run.
}
}
```
[postDelayed]: https://developer.android.com/reference/android/os/Handler#postDelayed(java.lang.Runnable,%20long)
[postAttime]: https://developer.android.com/reference/android/os/Handler#postAtTime(java.lang.Runnable,%20long)
[TimeUnit]: https://developer.android.com/reference/java/util/concurrent/TimeUnit
## Moving From Handler
Most use cases of Handlers can easily be handled by the above two interfaces
above. A minor refactor makes the switch:
Handler | Executor | DelayableExecutor
------------- | --------- | -----------------
post() | execute() | execute()
postDelayed() | `none` | executeDelayed()
postAtTime() | `none` | executeAtTime()
There is one notable gap in this implementation: `Handler.postAtFrontOfQueue()`.
If you require this method, or similar, please reach out. The idea of a
PriorityQueueExecutor has been floated, but will not be implemented until there
is a clear need.
Note also that "canceling" semantics are different. Instead of passing a `token`
object to `Handler.postDelayed()`, you receive a Runnable that, when run,
cancels the originally supplied Runnable.
### Message Handling
Executors have no concept of message handling. This is an oft used feature of
Handlers. There are (as of 2019-12-05) 37 places where we subclass Handler to
take advantage of this. However, by-and-large, these subclases take the
following form:
```Java
mHandler = new Handler(looper) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_A:
handleMessageA();
break;
case MSG_B:
handleMessageB((String) msg.obj);
break;
case MSG_C:
handleMessageC((Foobar) msg.obj);
break;
// ...
}
}
};
// Elsewhere in the class
void doSomething() {
mHandler.obtainMessage(MSG_B, "some string");
mHandler.sendMessage(msg);
}
```
This could easily be replaced by equivalent, more direct Executor code:
```Java
void doSomething() {
mExecutor.execute(() -> handleMessageB("some string"));
}
```
If you are posting Runnables frequently and you worry that the cost of creating
anonymous Runnables is too high, consider creating pre-defined Runnables as
fields in your class.
If you feel that you have a use case that this does not cover, please reach out.
### Handlers Are Still Necessary
Handlers aren't going away. There are Android APIs that still require them (even
if future API development discourages them). A simple example is
[ContentObserver][ContentObserver]. Use them where necessary.
[ContentObserver]: https://developer.android.com/reference/android/database/ContentObserver
## Testing (FakeExecutor)
We have a [FakeExecutor][FakeExecutor] available. It implements
DelayableExecutor (which in turn is an Executor). It takes a FakeSystemClock in
its constructor that allows you to control the flow of time, executing supplied
Runnables in a deterministic manner.
The implementation is well documented and tested. You are encouraged to read and
reference it, but here is a quick overview:
<table>
<tr>
<th>Method</th>
<th>Description</th>
</tr>
<tr>
<td>execute()</td>
<td>
Queues a Runnable so that it is "ready"
to run. (A Runnable is "ready" when its
scheduled time is less than or equal to
the clock.)
</td>
</tr>
<tr>
<td>postDelayed() & postAtTime()</td>
<td>
Queues a runnable to be run at some
point in the future.
</td>
</tr>
<tr>
<td>runNextReady()</td>
<td>
Run one runnable if it is ready to run
according to the supplied clock.
</td>
</tr>
<tr>
<td>runAllReady()</td>
<td>
Calls runNextReady() in a loop until
there are no more "ready" runnables.
</td>
</tr>
<tr>
<td>advanceClockToNext()</td>
<td>
Move the internal clock to the item at
the front of the queue, making it
"ready".
</td>
</tr>
<tr>
<td>advanceClockToLast()</td>
<td>
Makes all currently queued items ready.
</td>
</tr>
<tr>
<td>numPending()</td>
<td>
The number of runnables waiting to be run
They are not necessarily "ready".
</td>
</tr>
<tr>
<td>(static method) exhaustExecutors()</td>
<td>
Given a number of FakeExecutors, it
calls runAllReady() repeated on them
until none of them have ready work.
Useful if you have Executors that post
work to one another back and forth.
</td>
</tr>
</table>
_If you advance the supplied FakeSystemClock directly, the FakeExecutor will
execute pending Runnables accordingly._ If you use the FakeExecutors
`advanceClockToNext()` and `advanceClockToLast()`, this behavior will not be
seen. You will need to tell the Executor to run its ready items. A quick example
shows the difference:
Here we advance the clock directly:
```java
FakeSystemClock clock = new FakeSystemClock();
FakeExecutor executor = new FakeExecutor(clock);
executor.execute(() -> {}); // Nothing run yet. Runs at time-0
executor.executeDelayed(() -> {}, 100); // Nothing run yet. Runs at time-100.
executor.executeDelayed(() -> {}, 500); // Nothing run yet. Runs at time-500.
clock.synchronizeListeners(); // The clock just told the Executor it's time-0.
// One thing run.
clock.setUptimeMillis(500); // The clock just told the Executor it's time-500.
// Two more items run.
```
Here we have more fine-grained control:
```java
FakeSystemClock clock = new FakeSystemClock();
FakeExecutor executor = new FakeExecutor(clock);
executor.execute(() -> {}); // Nothing run yet. Runs at time-0
executor.executeDelayed(() -> {}, 100); // Nothing run yet. Runs at time-100.
executor.executeDelayed(() -> {}, 500); // Nothing run yet. Runs at time-500.
executor.runNextReady(); // One thing run.
executor.advanceClockToNext(); // One more thing ready to run.
executor.runNextReady(); // One thing run.
executor.runNextReady(); // Extra calls do nothing. (Returns false).
executor.advanceClockToNext(); // One more thing ready to run.
executor.runNextReady(); // Last item run.
```
One gotcha of direct-clock-advancement: If you have interleaved Runnables split
between two executors like the following:
```java
FakeSystemClock clock = new FakeSystemClock();
FakeExecutor executorA = new FakeExecutor(clock);
FakeExecutor executorB = new FakeExecutor(clock);
executorA.executeDelayed(() -> {}, 100);
executorB.executeDelayed(() -> {}, 200);
executorA.executeDelayed(() -> {}, 300);
executorB.executeDelayed(() -> {}, 400);
clock.setUptimeMillis(500);
```
The Runnables _will not_ interleave. All of one Executor's callbacks will run,
then all of the other's.
### TestableLooper.RunWithLooper
As long as you're using FakeExecutors in all the code under test (and no
Handlers or Loopers) you don't need it. Get rid of it. No more TestableLooper;
no more Looper at all, for that matter.
|