| # 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.html |
| [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/utils/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 are some notable gaps 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. |
| |
| ### ContentObserver |
| |
| One notable place where Handlers have been a requirement in the past is with |
| [ContentObserver], which takes a Handler as an argument. However, we have created |
| [ExecutorContentObserver], which is a hidden API that accepts an [Executor] in its |
| constructor instead of a [Handler], and is otherwise identical. |
| |
| [ContentObserver]: https://developer.android.com/reference/android/database/ContentObserver.html |
| [ExecutorContentObserver]: /core/java/android/database/ExecutorContentObserver.java |
| |
| ### Handlers Are Still Necessary |
| |
| Handlers aren't going away. There are other Android APIs that still require them. |
| Avoid Handlers when possible, but use them where necessary. |
| |
| ## 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. |
| |
| ### Testing Handlers without Loopers |
| |
| If a [Handler] is required because it is used by Android APIs, but is only |
| used in simple ways (i.e. just `Handler.post(Runnable)`), you may still |
| want the benefits of [FakeExecutor] when writing your tests, which |
| you can get by wrapping the [Executor] in a mock for testing. This can be |
| done with `com.android.systemui.util.concurrency.mockExecutorHandler` in |
| `MockExecutorHandler.kt`. |
| |
| ### 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. |