RxAndroid’s New Async API
Zac Sweers
Dimon’s Program Basement
Translator: Mr Dimon
RxAndroid 2.1.0 has a new API:
AndroidSchedulers#from(Looper looper, boolean async)
This new async parameter will affect Android APIs 16 and above. If your APP relies heavily on RxJava+RxAndroid, setting this parameter to True will significantly improve UI performance.
Since the major version of RxAndroid is bound to RxJava, we don’t want to silently introduce important behavior changes in the minor version, so this API is not enabled by default.
To install it, you can use RxAndroidPlugins to set the API’s custom scheduler:
EDIT: as the class loading problems, the following code on setInitMainThreadSchedulerHandler has some mistakes. To avoid initializing defaults, you should call AndroidSchedulers.from(…) in the incoming lambda/callable (inside). Instead of calling it before;
Kotlin:
//Before
val asyncMainThreadScheduler = AndroidSchedulers.from(Looper.getMainLooper(), true)
RxAndroidPlugins.setInitMainThreadSchedulerHandler { asyncMainThreadScheduler }
//Or if the default scheduler is already initialiazed
RxAndroidPlugins.setMainThreadSchedulerHandler { asyncMainThreadScheduler }
Copy the code
//Correct usage
RxAndroidPlugins.setInitMainThreadSchedulerHandler {
AndroidSchedulers.from(Looper.getMainLooper(), true)
}
//Or if the default scheduler is already initialiazed
RxAndroidPlugins.setMainThreadSchedulerHandler {
AndroidSchedulers.from(Looper.getMainLooper(), true)
}
Copy the code
Java:
//Before
Scheduler asyncMainThreadScheduler = AndroidSchedulers.from(Looper.getMainLooper(), true);
RxAndroidPlugins.setInitMainThreadSchedulerHandler(callable -> asyncMainThreadScheduler);
//Or if the default scheduler is already initialiazed
RxAndroidPlugins.setMainThreadSchedulerHandler(scheduler -> asyncMainThreadScheduler);
Copy the code
//Correct usage
RxAndroidPlugins.setInitMainThreadSchedulerHandler(callable -> {
Scheduler asyncMainThreadScheduler = AndroidSchedulers.from(Looper.getMainLooper(), true);
return asyncMainThreadScheduler;
});
//Or if the default scheduler is already initialiazed
RxAndroidPlugins.setMainThreadSchedulerHandler(scheduler -> {
Scheduler asyncMainThreadScheduler = AndroidSchedulers.from(Looper.getMainLooper(), true);
return asyncMainThreadScheduler;
});
Copy the code
The body of the
That’s a long time. The main thread used in RxAndroid has traditionally been to use Handler#post() to schedule new messages. This usually comes at a cost: by default this follows a VSYNC lock and will result in waiting until the next frame is run. This would be a delay of up to 16ms for each launch through post(). This is exacerbated if you are already on the main thread (Ray Ryan once talked about this in an excellent talk).
Thus, in 2015, there was a discussion in Jake Wharton’s RxBinding project about “fastpath” : the main thread scheduler could run immediately if it was already on the main thread and avoided VSYNC costs. The discussion then moved on to a proposal for RxAndroid, which received good community feedback, but never reached a consensus due to concerns that it might compete with the system by running events directly and risking deadlocks; As a result, it has come under fire, but remains a point of friction for users, and has submitted several issues in rxAndroid2.x;
Very quickly, in early 2017, at Uber we decided to try re-hash internally and found that it was basically stable. We do see deadlocks from time to time, but they are hard to spot and seem rare enough to be worth the performance boost. A year after going live, we decided to try to upstream our implementation and reopen the discussion. This time, Android framework engineer (Adam Powell) saw the discussion and pointed out that we were using the asynchronous API of Message and Handler. This API allows messages to bypass VSYNC locking while still letting the framework handle all scheduling safely in its Looper, which is exactly what we’ve been wanting!
Attachment API
While asynchronous apis have been around since API 16, they are already hidden in the SDK via @hide. In API22, the Message#setAsynchronous() method becomes public. In API28, there is a new handler.createAsync () factory API that sets all messages it processes to asynchronous by default. Because these apis power some of the most critical parts of the operating system, they are unlikely to be changed and should be safe to access. It’s kind of fun here.
API 22+
Use the public setAsynchronous () method above.
API[16-21]
SetAsynchronous () is still used, but we suppress the Lint error to indicate that it is only 22+. To avoid any (unlikely) OEM situation of deleting/changing the internal API, we try/catch from () a fast Message# setAsynchronous () method call in the scheduler factory to make sure it exists at runtime, catch NoSuchMethodError, If it is lost and gracefully returns to standard non-asynchronous messaging. It’s not reflection because we know this will be at run time because the method does exist at run time.
API < 16
There are no behavior changes because the asynchronous API does not exist, so standard non-asynchronous messaging is used.
That’s it! I hope this gives developers more peace of mind when using the main thread scheduler. Please try it and report any problems.