Booster: SharedPreferences Optimization
Background
Booster v0.1.5 actually already included an optimization for SharedPreferences, though the scope was modest. Since SharedPreferences is so pervasive in Android, we were extremely cautious with it – it took several production releases to validate before we rolled out the latest optimization.
As for why SharedPreferences needs optimization at all, anyone who has done Android development knows its design has long been criticized. In truth, SharedPreferences was never designed by Google’s engineers to be used the way it is today. It simply got pushed far beyond its intended use case, leading to all sorts of jank and ANR issues.
The v0.1.5 Optimization
The SharedPreferences optimization in booster v0.1.5 replaced Editor.apply() with Editor.commit() executed on a background thread:
1 | public class ShadowEditor { |
For why we replace Editor.apply() with asynchronous Editor.commit(), see this article: http://www.cloudchou.com/android/post-988.html
The v0.2.0 Optimization
Booster v0.2.0 took the SharedPreferences optimization further. When Editor.commit() is called but its return value is unused, the commit is moved to a background thread:
1 | override fun transform(context: TransformContext, klass: ClassNode): ClassNode { |
The Data Consistency Problem
While the first two optimizations addressed jank and ANR to some extent, they actually introduced a bug. Consider this code:
1 | SharedPreferences sp = context.getSharedPreferences("config", Context.MODE_PRIVATE); |
See the problem? A put followed immediately by a get – the value might not be there yet, because Editor.commit() could still be queued in the thread pool. While code like this is uncommon, it can still happen. So we introduced a new optimization with the following goals:
- Fix jank and ANR caused by SharedPreferences.
- Fix cross-process data sharing for SharedPreferences.
- Fix the data consistency issue left over from previous versions.
The Ultimate Solution
To truly solve the SharedPreferences problem, we need to avoid all the pitfalls of the native implementation:
- ANR caused by Editor.apply().
- Main thread jank caused by Editor.commit().
- Main thread jank or even ANR from frequent asynchronous Editor.commit() calls.
- Inability to synchronize data across processes in a timely manner.
Booster’s solution is BoosterSharedPreferences. Through SharedPreferencesTransformer, all calls to Context.getSharedPreferences(String, int) are replaced with ShadowSharedPreferences.getSharedPreferences(Context, String, int):
1 | public class ShadowSharedPreferences { |
In BoosterSharedPreferences, SharedPreferences instances are cached, which significantly improves performance:
1 | public static SharedPreferences getSharedPreferences(final String name) { |
- Blog Link: https://johnsonlee.io/2019/11/12/booster-transform-shared-preferences.en/
- Copyright Declaration: 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
