Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it safe to keep a static reference to a SharedPreferences and its Editor?

Im going to make something like:

private static SharedPreferences sharedPreferencesInstance;
public static SharedPreferences getSharedPreferences(final Context context){
    if (context==null)
        return sharedPreferencesInstance;
    if (sharedPreferencesInstance == null)
        sharedPreferencesInstance = context.getApplicationContext().getSharedPreferences("prefs", Context.MODE_PRIVATE);
    return sharedPreferencesInstance;
}

private static SharedPreferences.Editor sharedPreferencesEditorInstance;
public static SharedPreferences.Editor getSharedPreferencesEditor(final Context context){
    if (context==null)
        return sharedPreferencesEditorInstance;
    if (sharedPreferencesEditorInstance == null)
        sharedPreferencesEditorInstance = context.getApplicationContext().getSharedPreferences("prefs", Context.MODE_PRIVATE).edit();
    return sharedPreferencesEditorInstance;
}

but is it safe in meaning of Context leaks?

like image 594
Stan Avatar asked Mar 20 '14 20:03

Stan


People also ask

Is SharedPreferences thread safe?

Android's SharedPreferences class is used in many sample apps to store small amounts of non-relational data, and avoids doing extra disk I/O with an in-memory cache. You should not use it, for a whole host of reasons: Synchronous API encourages StrictMode violations.

What does the SharedPreferences editor Clear () method do?

clear(), It delete only values (means that keys are available) or it delete key value pair. check the following code. SharedPreferences userlogindetails = getSharedPreferences("userdetails", MODE_PRIVATE); SharedPreferences.

Are SharedPreferences persistent?

Android's built-in SharedPreferences storage mechanism allows us to store information that persists throughout the entire app.


2 Answers

To answer the question authoritatively, it is safe to store the SharedPreferences instance as a static reference. According to the javadocs it is a singleton, so its source from getSharedPreferences is already a static reference.

It is not safe to store the SharedPreferences.Editor because it is possible two threads may be manipulating the same editor object at the same time. Granted, the damage this would cause is relatively minor if you happen to have already been doing it. Instead, get an instance of an editor in each editing method.

I highly recommend using a static reference to your Application object instead of passing in Context objects for every get. All instances of your Application class are singletons per process anyways, and passing around Context objects is usually bad practice because it tends to lead to memory leaks via reference holding, and is unnecessarily verbose.

Finally, to answer the unasked question if you should lazily-load or greedily-initialize the reference to your static SharedPreferences, you should lazily load in a static getter method. It may work to greedily-initialize a reference with final static SharedPreferences sReference = YourApplication.getInstance().getSharedPreferences() depending on the chain of class imports, but it would be too easy for the class loader to initialize the reference before the Application has already called onCreate (where you would initialize the YourApplication reference), causing a null-pointer exception. In summary:

class YourApplication {
    private static YourApplication sInstance;

    public void onCreate() {
        super.onCreate();
        sInstance = this;
    }
    public static YourApplication get() {
        return sInstance;
    }
}

class YourPreferencesClass {
    private static YourPreferencesClass sInstance;
    private final SharedPreferences mPrefs;

    public static YourPreferencesClass get() {
        if (sInstance == null)
            sInstance = new YourPreferencesClass();
        return sInstance;
    }

    private final YourPreferencesClass() {
        mPrefs = YourApplication.get().getSharedPreferences("Prefs", 0);
    }

    public void setValue(int value) {
        mPrefs.edit().putInt("value", value).apply();
    }

    public int getValue() {
        return mPrefs.getInt("value", 0);
    }
}

You will then use your statically available preferences class as such:

YourPreferencesClass.get().setValue(1);

A final word about the thread-safety and memory observability. Some astute observers may notice that YourPreferencesClass.get() isn't synchronized, and hence dangerous because two threads may initialize two different objects. However, you can safely avoid synchronization. As I mentioned earlier, getSharedPreferences already returns a single static reference, so even in the extremely rare case of sInstance being set twice, the same underlying reference to SharedPreferences is used. Regarding the static instance of YourApplication.sInstance, it is also safe without synchronization or the volatile keyword. There are no user threads in your application running before YourApplication.onCreate, and therefore the happens-before relationship defined for newly created threads ensures that the static reference will be visible to all future threads that may access said reference.

like image 172
Matt Quigley Avatar answered Nov 15 '22 20:11

Matt Quigley


I think it is safe. I always use a "KeyStoreController" with a static reference to a SharedPreferences object (singleton). I would suggest you to use an Application context instead of passing a context every time. This is an example of my code:

public class KeyStoreController{


private static KeyStoreController singleton = null;
private SharedPreferences preferences = null;

private KeyStoreController(Context c){
    preferences = PreferenceManager.getDefaultSharedPreferences(c);
}

public static KeyStoreController getKeyStore(){
    if( singleton == null){
        singleton = new KeyStoreController(MainApplication.getContext());
    }
    return singleton;
}

public void setPreference(String key, Object value) {
    // The SharedPreferences editor - must use commit() to submit changes
    SharedPreferences.Editor editor = preferences.edit();
    if(value instanceof Integer )
        editor.putInt(key, ((Integer) value).intValue());
    else if (value instanceof String)
        editor.putString(key, (String)value);
    else if (value instanceof Boolean)
        editor.putBoolean(key, (Boolean)value);
    else if (value instanceof Long)
        editor.putLong(key, (Long)value);
    editor.commit();
}

public int getInt(String key, int defaultValue) {
    return preferences.getInt(key, defaultValue);
}

public String getString(String key, String defaultValue) {
    return preferences.getString(key, defaultValue);
}

public boolean getBoolean(String key, boolean defaultValue) {
    return preferences.getBoolean(key, defaultValue);
}

public long getLong(String key, long defaultValue) {
    return preferences.getLong(key, defaultValue);
}
like image 39
Sebastian Breit Avatar answered Nov 15 '22 19:11

Sebastian Breit