質問

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?

役に立ちましたか?

解決

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.

他のヒント

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);
}

If you are passing around the Context it is best to be passing along an ApplicationContext. It might be easier if you just make a static ApplicationContext to reference and then just use the SharedPreferences when you need them from within your classes (if that approach works for you).

If you have the argument for the calls a Context then you shouldn't have to worry about leaks unless you are holding on to it.

But, I think that you will be just fine doing what you are doing conceptually.

Why not just create a static class and use it as a utility so you never have to keep a reference to your SharedPreferences at all. You also never have to initialize an instance of this class and can just call PreferencesUtil.getUserName(context) so long as you have a context to supply.

public static class PreferencesUtil{

    private static final String USER_NAME_KEY = "uname";

    public static void setUserName(String name, Context c){
        SharedPreferences sharedPref = getPreferences(c);
        SharedPreferences.Editor editor = sharedPref.edit();
        editor.putString(USER_NAME_KEY, name);
        editor.commit();
    }


    public static String getUserName(Context c){
        return getPreferences(c).getString(USER_NAME_KEY, "");
    }

    private SharedPreferences getPreferences(Context context){
        return context.getPreferences(Context.MODE_PRIVATE);
    }

}
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top