119

With the release of Gingerbread, I have been experimenting with some of the new API's, one of them being StrictMode.

I noticed that one of the warnings is for getSharedPreferences().

This is the warning:

StrictMode policy violation; ~duration=1949 ms: android.os.StrictMode$StrictModeDiskReadViolation: policy=23 violation=2

and it's being given for a getSharedPreferences() call being made on the UI thread.

Should SharedPreferences access and changes really be made off the UI thread?

biegleux
  • 12,934
  • 11
  • 42
  • 52
cottonBallPaws
  • 20,217
  • 37
  • 119
  • 169
  • I've always made my preference operations on the UI thread. Although I guess it makes sense since it's an IO operation – Falmarri Dec 06 '10 at 21:53

6 Answers6

189

I'm glad you're already playing with it!

Some things to note: (in lazy bullet form)

  • if this is the worst of your problems, your app's probably in a good spot. :) Writes are generally slower than reads, though, so be sure you're using SharedPreferenced$Editor.apply() instead of commit(). apply() is new in GB and async (but always safe, careful of lifecycle transitions). You can use reflection to conditionally call apply() on GB+ and commit() on Froyo or below. I'll be doing a blogpost with sample code of how to do this.

Regarding loading, though...

  • once loaded, SharedPreferences are singletons and cached process-wide. so you want to get it loaded as early as possible so you have it in memory before you need it. (assuming it's small, as it should be if you're using SharedPreferences, a simple XML file...) You don't want to fault it in the future time some user clicks a button.

  • but whenever you call context.getSharedPreferences(...), the backing XML file is stat'd to see if it's changed, so you'll want to avoid those stats during UI events anyway. A stat should normally be fast (and often cached), but yaffs doesn't have much in the way of concurrency (and a lot of Android devices run on yaffs... Droid, Nexus One, etc.) so if you avoid disk, you avoid getting stuck behind other in-flight or pending disk operations.

  • so you'll probably want to load the SharedPreferences during your onCreate() and re-use the same instance, avoiding the stat.

  • but if you don't need your preferences anyway during onCreate(), that loading time is stalling your app's start-up unnecessarily, so it's generally better to have something like a FutureTask<SharedPreferences> subclass that kicks off a new thread to .set() the FutureTask subclasses's value. Then just lookup your FutureTask<SharedPreferences>'s member whenever you need it and .get() it. I plan to make this free behind the scenes in Honeycomb, transparently. I'll try to release some sample code which shows best practices in this area.

Check the Android Developers blog for upcoming posts on StrictMode-related subjects in the coming week(s).

Brad Fitzpatrick
  • 3,461
  • 1
  • 17
  • 9
  • Wow, wasn't expecting to get such a clear answer straight from the source! Thanks much! – cottonBallPaws Dec 07 '10 at 01:50
  • 9
    For the benefit of new readers of this wonderful post, find below the link to the blog post mentioned above by @Brad Fitzpatrick : [android developer's blog post on strict mode by Brad](http://android-developers.blogspot.de/2010/12/new-gingerbread-api-strictmode.html) . The post also has a link to sample code for using apply(from gingerbread onwards) or commit(froyo) based on the android version for storing sharedpreferences : [conditionally use apply or commit] (https://code.google.com/p/zippy-android/source/browse/trunk/examples/SharedPreferencesCompat.java) – tony m May 18 '13 at 12:05
  • 4
    Is this still relevant on ICS\JB? – ekatz Jun 06 '13 at 19:48
5

One subtlety about Brad's answer: even if you load the SharedPreferences in onCreate(), you should probably still read values on the background thread because getString() etc. block until reading the shared file preference in finishes (on a background thread):

public String getString(String key, String defValue) {
    synchronized (this) {
        awaitLoadedLocked();
        String v = (String)mMap.get(key);
        return v != null ? v : defValue;
    }
}

edit() also blocks in the same way, although apply() appears to be safe on the foreground thread.

(BTW sorry to put this down here. I would have put this as a comment to Brad's answer, but I just joined and don't have enough reputation to do so.)

Tom O'Neill
  • 51
  • 1
  • 4
  • 1
    Yes, this is definitely a consideration. However, often enough if you are calling `get*()` then you are at a stage where you need the resulting value in order to continue, in which case you have to wait anyway, and don't benefit from the call to `get*()` being on another thread. Just a thought. – pallgeuer Nov 20 '20 at 15:18
5

Accessing the shared preferences can take quite some time because they are read from flash storage. Do you read a lot? Maybe you could use a different format then, e.g. a SQLite database.

But don't fix everything you find using StrictMode. Or to quote the documentation:

But don't feel compelled to fix everything that StrictMode finds. In particular, many cases of disk access are often necessary during the normal activity lifecycle. Use StrictMode to find things you did by accident. Network requests on the UI thread are almost always a problem, though.

mreichelt
  • 11,918
  • 6
  • 51
  • 66
  • 6
    But isn't the SQLite also a file that must be read from flash storage - but a bigger and more complicated one compared to a preferences file. I have been assuming that, for the amount of data associated with preferences, a preferences file will be much faster then an SQLite database. – Tom Oct 12 '12 at 16:41
  • That's correct. As Brad already mentioned, this almost always is no problem - and he also mentions it is a good idea to load the SharedPreferences once (maybe even in a Thread using a FutureTask) and hold it for any possible access to the single instance. – mreichelt Oct 22 '12 at 16:42
1

I know this is an old question but I want to share my approach. I had long reading times and used a combination of shared preferences and the global application class:

ApplicationClass:

public class ApplicationClass extends Application {

    private LocalPreference.Filter filter;

    public LocalPreference.Filter getFilter() {
       return filter;
    }

    public void setFilter(LocalPreference.Filter filter) {
       this.filter = filter;
    }
}

LocalPreference:

public class LocalPreference {

    public static void saveLocalPreferences(Activity activity, int maxDistance, int minAge,
                                            int maxAge, boolean showMale, boolean showFemale) {

        Filter filter = new Filter();
        filter.setMaxDistance(maxDistance);
        filter.setMinAge(minAge);
        filter.setMaxAge(maxAge);
        filter.setShowMale(showMale);
        filter.setShowFemale(showFemale);

        BabysitApplication babysitApplication = (BabysitApplication) activity.getApplication();
        babysitApplication.setFilter(filter);

        SecurePreferences securePreferences = new SecurePreferences(activity.getApplicationContext());
        securePreferences.edit().putInt(Preference.FILER_MAX_DISTANCE.toString(), maxDistance).apply();
        securePreferences.edit().putInt(Preference.FILER_MIN_AGE.toString(), minAge).apply();
        securePreferences.edit().putInt(Preference.FILER_MAX_AGE.toString(), maxAge).apply();
        securePreferences.edit().putBoolean(Preference.FILER_SHOW_MALE.toString(), showMale).apply();
        securePreferences.edit().putBoolean(Preference.FILER_SHOW_FEMALE.toString(), showFemale).apply();
    }

    public static Filter getLocalPreferences(Activity activity) {

        BabysitApplication babysitApplication = (BabysitApplication) activity.getApplication();
        Filter applicationFilter = babysitApplication.getFilter();

        if (applicationFilter != null) {
            return applicationFilter;
        } else {
            Filter filter = new Filter();
            SecurePreferences securePreferences = new SecurePreferences(activity.getApplicationContext());
            filter.setMaxDistance(securePreferences.getInt(Preference.FILER_MAX_DISTANCE.toString(), 20));
            filter.setMinAge(securePreferences.getInt(Preference.FILER_MIN_AGE.toString(), 15));
            filter.setMaxAge(securePreferences.getInt(Preference.FILER_MAX_AGE.toString(), 50));
            filter.setShowMale(securePreferences.getBoolean(Preference.FILER_SHOW_MALE.toString(), true));
            filter.setShowFemale(securePreferences.getBoolean(Preference.FILER_SHOW_FEMALE.toString(), true));
            babysitApplication.setFilter(filter);
            return filter;
        }
    }

    public static class Filter {
        private int maxDistance;
        private int minAge;
        private int maxAge;
        private boolean showMale;
        private boolean showFemale;

        public int getMaxDistance() {
            return maxDistance;
        }

        public void setMaxDistance(int maxDistance) {
            this.maxDistance = maxDistance;
        }

        public int getMinAge() {
            return minAge;
        }

        public void setMinAge(int minAge) {
            this.minAge = minAge;
        }

        public int getMaxAge() {
            return maxAge;
        }

        public void setMaxAge(int maxAge) {
            this.maxAge = maxAge;
        }

        public boolean isShowMale() {
            return showMale;
        }

        public void setShowMale(boolean showMale) {
            this.showMale = showMale;
        }

        public boolean isShowFemale() {
            return showFemale;
        }

        public void setShowFemale(boolean showFemale) {
            this.showFemale = showFemale;
        }
    }

}

MainActivity (activity that get called first in your application):

LocalPreference.getLocalPreferences(this);

Steps explained:

  1. The main activity calls getLocalPreferences(this) -> this will read your preferences, set the filter object in your application class and returns it.
  2. When you call the getLocalPreferences() function again somewhere else in the application it first checks if it's not available in the application class which is a lot faster.

NOTE: ALWAYS check if an application wide variable is different from NULL, reason -> http://www.developerphil.com/dont-store-data-in-the-application-object/

The application object will not stay in memory forever, it will get killed. Contrary to popular belief, the app won’t be restarted from scratch. Android will create a new Application object and start the activity where the user was before to give the illusion that the application was never killed in the first place.

If I didn't check on null I would allow a nullpointer to be thrown when calling for example getMaxDistance() on the filter object (if the application object was swiped from the memory by Android)

Jdruwe
  • 3,170
  • 4
  • 33
  • 53
0

SharedPreferences class does some reads & writes within XML files on disk, so just like any other IO operation it could be blocking. The amount of data currently stored in SharedPreferences affects the time and resource consumed by the API calls. For minimal amounts of data it's a matter of a few milliseconds (sometimes even less than a millisecond) to get/put data. But from the point of view of an expert it could be important to improve the performance by doing the API calls in background. For an asynchronous SharedPreferences I suggest checking out the Datum library.

navid
  • 650
  • 6
  • 13
0

i do not see any reason to read them from a background thread. but to write it i would. at startup time the shared preference file is loaded into memory so its fast to access, but to write things can take a bit of time so we can use apply the write async. that should be the difference between commit and apply methods of shared prefs.

j2emanue
  • 51,417
  • 46
  • 239
  • 380