Skip to content

Make Android UI dispatcher asynchronous by default #427

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
qwwdfsad opened this issue Jul 8, 2018 · 12 comments
Closed

Make Android UI dispatcher asynchronous by default #427

qwwdfsad opened this issue Jul 8, 2018 · 12 comments

Comments

@qwwdfsad
Copy link
Collaborator

qwwdfsad commented Jul 8, 2018

See discussion in #381

We can make UI asynchronous by default and provide a fallback (system property?) for sync behaviour and something like SyncUI.

Asynchronous handlers are the new feature in Android and it's unclear for us (as we're not Android devs) whether it's sane default which is safe to expose, so we'd like to hear community opinion on this.

@qwwdfsad
Copy link
Collaborator Author

qwwdfsad commented Jul 8, 2018

cc @JakeWharton

@LouisCAD
Copy link
Contributor

LouisCAD commented Jul 8, 2018

I personally never rely on dispatch and waiting layout passes or frames in coroutines. I just rely on which thread/thread pool they will run.
One exception is a place where I use UI.awaitFrame() for imperative animation in a for loop.

@adamp
Copy link

adamp commented Jul 9, 2018

If we want to be super precise there are two axes at play here: UI vs. Main thread and sync vs. async. I'm not sure it's worth slicing all of them into official API since it'll only lead to, "which should I use?" questions where the answer will almost always be, "async/main thread."

We do make distinctions in Android APIs around main vs. UI thread - they aren't the same since you can create a window with UI thread that is a looper thread other than the app's main ActivityThread. This is uncommon and usually only comes up in system code, but we do try to keep API docs and thread hint annotations precise around this.

Then you have the sync/async distinction here. Ever since Jellybean introduced vsync barriers that delay posted messages until after a pending measure/layout/draw operation when the looper thread is otherwise idle there have been APIs to bypass it and get lower latency behavior if you don't need the results of the pending view tree traversal. The functionality itself is not a new feature; the only new feature is the Handler.createAsync API that does some boilerplate work for you. We plan to have a backport available for this API soonish. (Just a matter of library release schedules.)

Given that:

  • You can easily create a coroutine context from any Handler
  • If you're being precise the only way to correctly reference a UI thread is from an attached View's getHandler method in that specific window, which isn't particularly practical for many use cases
  • Nearly all app UIs use the main thread as their UI thread
  • Nearly all code that isn't part of a custom View implementation doesn't need the vsync barrier for correctness

The only looper thread that you can offer a global coroutine context for is the app's main thread, and offering two options out of the box probably isn't worth the, "which should I use?" speed bump. The async variant is almost certainly what people want outside of some special cases.

The UI dispatcher is kind of unfortunately named; Main or MainThread would be more accurate and consistent with Android documentation/API.

@adamp
Copy link

adamp commented Jul 9, 2018

Regarding UI.awaitFrame(), with its current implementation it suspends until the frame callback, which occurs immediately before the next measure/layout/draw pass - unless it hasn't cached the Choreographer reference yet, in which case the handler post it uses to fetch that reference will incur the 1-frame latency that async handlers would otherwise skip.

In other words, awaitFrame's correctness wouldn't be altered by the proposed change, but the first use would save a frame.

awaitFrame is also a bit of a foot gun in concept; a frame callback happens at a performance-critical time so any expensive operation performed there before another suspend could lead to a delayed or dropped frame. Using it for imperative animations is also dangerous, as you still need to cancel/clean up the handler loop that it causes when the associated view/window is no longer visible. We've had quite a few bug reports from apps that have their process stuck in one of these message loops draining battery when the app isn't even visible. Giving it such high visibility on HandlerContext could lead to more of these kinds of bugs.

@LouisCAD
Copy link
Contributor

LouisCAD commented Jul 12, 2018

Could be nice to have a prototype of such a CoroutineDispatcher so we can try in our projects and see if there's any impact on stability, and see how the performances are different.

@adamp
Copy link

adamp commented Jul 12, 2018

Using the snippet in #381 for compatibility, createAsyncHandler(Looper.getMainLooper()).asCoroutineDispatcher() should be sufficient for prototyping.

@LouisCAD
Copy link
Contributor

I'd even link to the comment containing the snippet here.

I'll try, thanks!

@ZakTaccardi
Copy link

this article may be of interest https://medium.com/@sweers/rxandroids-new-async-api-4ab5b3ad3e93

@adamp
Copy link

adamp commented Sep 17, 2018

Giving this a bump now that UI has become Dispatchers.Main. Now that this is generalized beyond UI into all other main looper tasks it would be great to get this default behavior change in for 1.0 so that coroutines-based code doesn't start relying on the compatibility vsync barrier behavior out of the gate.

@LouisCAD
Copy link
Contributor

LouisCAD commented Sep 17, 2018 via email

@JakeWharton
Copy link
Contributor

JakeWharton commented Sep 17, 2018 via email

@JakeWharton
Copy link
Contributor

This can be closed now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants