Accessing Android Shared Preferences Across Multiple Processes

February 2, 2016

Accessing Android Shared Preferences Across Multiple Processes

Shared preferences are the primary way for an app to persist small bits of information such as user-selected settings. You can persist simple Java primitives like ints, booleans and floats or complex objects like Strings, Parcelables, and Serializables. Many developers are introduced to shared preferences with a simple example showing how to store and retrieve values through the default shared preferences.

// persist the preference
PreferenceManager.getDefaultSharedPreferences(context)
 .edit()
 .putBoolean("PREF_USER_LIKES_COOKIES", true)
 .commit();

// retrieve the preference
boolean userLikesCookies =
 PreferenceManager.getDefaultSharedPreferences(context)
 .getBoolean("PREF_USER_LIKES_COOKIES", false);

Pretty simple, right?

Accessing a Shared Preference From Another Process

Now let’s say that we want to edit this preference in one process and access it from another process.

Let’s set up a LocaleChangeReceiver:


 android:name=".LocaleChangeReceiver">
 
  android:name="android.intent.action.LOCALE_CHANGED"/>
 

public class LocaleChangeReceiver extends BroadcastReceiver {
 @Override
 public void onReceive(Context context, Intent intent) {
 PreferenceManager.getDefaultSharedPreferences(context)
 .edit()
 .putBoolean("PREF_USER_LIKES_COOKIES", new Random().nextBoolean())
 .commit();
 }

}

This receiver runs whenever the device’s locale or language is changed but it runs in a separate process from our app’s main process. When we edit and commit the shared preference in LocaleChangeReceiver’s process, the change may or may not have persisted by the time we attempt to read it in the main process. Each process has their own instance of the app’s shared preferences, and when one process edits a shared preference, the changes may not be reflected in another process’s instance for an indeterminate amount of time.

MODE_MULTI_PROCESS

Up to and including Android API level 9 (OS version 2.3), shared preference instances between processes were checked to ensure that the latest changes were reflected amongst all processes. However after API level 9, the MODE_MULTI_PROCESS flag must be set when accessing shared preferences.

In order to use this mode flag you will have to read/write your shared preferences using Context.getSharedPreferences() where you select a file name and various mode flags.

context.getSharedPreferences(
 "shared pref file name",
 Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS
);

When you access default shared preferences with

PreferenceManager.getDefaultSharedPreferences(context)

you’re ultimately calling this under the hood:

context.getSharedPreferences(
 context.getPackageName() + "_preferences",
 Context.MODE_PRIVATE
);

This means that the file name and mode of your existing shared preferences are already set in stone and are unchangeable. To use Context.MODE_MULTI_PROCESS you will have to migrate your app’s existing shared preferences to a new shared preferences file.

public static void migrateSharedPreferences(Context context) {
 // retrieve the preference from the default shared preferences...
 boolean oldDefaultSharedPrefUserLikesCookies =
 PreferenceManager.getDefaultSharedPreferences(context)
 .getBoolean("PREF_USER_LIKES_COOKIES", false);

 // ...and set it in a new shared preferences file that uses Context.MODE_MULTI_PROCESS
 context.getSharedPreferences(
 "{user prefs file name}",
 Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS
 )
 .edit()
 .putBoolean("PREF_USER_LIKES_COOKIES", oldDefaultSharedPrefUserLikesCookies)
 .commit();
}

Now you can access your migrated shared preferences.

boolean userLikesCookies =
 context.getSharedPreferences(
 "{user prefs file name}",
 Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS
 ).getBoolean("PREF_USER_LIKES_COOKIES", false);

Once you’ve migrated your default shared preferences to a non-default shared preferences file that uses Context.MODE_MULTI_PROCESS, your shared preference instances amongst processes are written and read immediately as you would expect.

API Level 23 (6.0) Deprecation

However, as of API Level 23 (OS version 6.0), Context.MODE_MULTI_PROCESS is deprecated as it has been deemed unreliable in some versions of Android. The documentation recommends that you instead use a ContentProvider (i.e. the system’s underlying SQLite database) to manage shared preferences that need to be shared amongst multiple processes. Again, if you were previously using Context.MODE_MULTI_PROCESS this means you will need to migrate your existing shared preferences over to being managed by a ContentProvider.

Setting up a ContentProvider and its accompanying boilerplate code feels like overkill just to store your simple shared preferences. You don’t have to do it for all your shared preferences but you may not be able to predict which ones will need to be accessed from multiple processes in the future.

The Solution

An elegant solution to this dilemma is Tray, an Android library that uses a ContentProvider underneath its hood to allow you to read and write shared preferences with an API similar to the Android SharedPreference API. It even provides a mechanism for migrating over existing preferences to be managed by the library.

Moving forward, it seems prudent to manage all shared preferences by either this library or manually through a ContentProvider, as one can not predict how shared preferences will need to be accessed in the future, and we can avoid having to migrate old shared preferences. We welcome other developers who have come across this problem to share their experiences and thoughts on the matter. What other methods have you found to be useful for inter-process sharing of shared preferences?

Ready to get started?

Call us at 616-594-0269 or send us a note below.
Visit our office @ 452 Ada Drive SE Suite 300 Ada, Michigan 49301
Send us an e-mail @ info@michiganlabs.com