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