Harmony — Multiprocess SharedPreferences
Android developers are familiar with SharedPreferences. It provides a convenient way to store primitive key-value pairs. There’s no need to create a database or write boilerplate code to do I/O for files to store data. It’s as simple as:
The Multiprocess Problem with SharedPreferences
For most apps, the above code works perfectly fine. The following is a simple animation demonstrating what is happening in the code above.
However, if the app has multiple processes, reading/writing the same SharedPreferences would become problematic. Assume we have a service in another process:
On launch of this second process along with the start of “SimpleService”, there is nothing out of the ordinary, until we look at what happens in the “onStartCommand()” method.
Walk-through of the Problem
In the “onStartCommand()” method, we first call to get the SharedPreferences, which either creates the underlying file if none existed, or loads the data from it into memory if it did exist. In our case, the file exists, so it is loaded into memory.
The following animation shows what happens when we update a value in a second process.
We’ve just run into one of the first problems with SharedPreferences being used across multiple processes. “Process_B” shows the correct value that is reflected in the SharedPreferences file, however, “Process_A” is still showing “my_counter” with the old value.
Regardless how often the value is updated in “Process_B”, the memory in “Process_A” will not update unless the SharedPreferences file is re-loaded. Also, due to how SharedPreferences writes data to the file (data in memory is flushed to the file), it’s entirely possible to write the wrong data back into the SharedPreferences file.
(Assume we are calling a new function that adds value “foo” to key “id” in “Process_A”)
As you can see in this last animation, not only is the in-memory SharedPreferences of “Process_B” out of date, we have also added old data back into the SharedPreferences file.
In the worst case scenario, because there is nothing preventing multiple processes from writing this same file, it’s also possible to corrupt the file being written, rendering it unreadable, and forcing SharedPreferences to load nothing, essentially recreating the file as empty.
The Problem with Suggested Solutions
MODE_MULTI_PROCESS
If you go through the Android documentation, you may have seen MODE_MULTI_PROCESS as a parameter to pass in when calling getSharedPreferences()
, but it would be hard to miss this:
If that wasn’t enough to get the point across, Google even added this note to the SharedPreferences class documentation:
Note: This class does not support use across multiple processes.
Basically…
ContentProviders
ContentProviders will work and it’s actually something that many developers have used to solve this problem (example: https://github.com/grandcentrix/tray). The basic reason this solution works is that the underlying data file is being written by a single process that owns the ContentProvider. Any calls for reads/writes is done via the interprocess communication layer provided by Android, which is handled by the ContentProvider. Unfortunately, using ContentProviders as a SharedPreferences replacement is not without it’s own problems. For example, having to wait for another process to start in order to read/write, or that another process is taking up resources (memory, CPU usage, etc) just for a simple key-value library.
What Makes Harmony Different?
Harmony is a process-safe implementation of the SharedPreferences interface. That means that anywhere you would use SharedPreferences, you can use Harmony. Also, Harmony doesn’t require any other process to run in order for it to work. It works without using ContentProviders, Bound Services, Messengers, AIDL, or BroadcastReceivers. Harmony listens for file changes internally, and ensures that the in-memory map of the file data is synced each time it is changed. It also ensures that any changes that are applied or committed are properly ordered. This allows for multiple calls to apply()
or commit()
in multiple processes simultaneously without writing back old data.
Currently, Harmony honors most of the documentation listed in the SharedPreferences docs, with the following exception: It is multi-process safe!
How to Use Harmony
For Kotlin, getHarmonySharedPreferences()
is an extension of Context.
For Java, Harmony provides a static function for getting the SharedPreferences.
From there, you can treat the returned SharedPreferences object as you normally would. Changes made to any key in one process will reflect in all other processes that have this SharedPreferences.
Harmony is completely open-source and available on Maven Central.