Безопасно ли хранить статическую ссылку на SharedPreferences и его редактор?

Я собираюсь сделать что-то вроде:

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

но безопасно ли это в смысле утечки контекста?

4 ответов


чтобы ответить на вопрос авторитетно, это безопасное для хранения SharedPreferences экземпляр как статическая ссылка. Согласно javadocs это синглтон, поэтому его источник из getSharedPreferences уже является статической ссылкой.

это не безопасно для хранения SharedPreferences.Editor потому что возможно, что два потока могут одновременно управлять одним и тем же объектом редактора. Конечно, ущерб, который это вызовет, относительно невелик, если у вас уже есть делал это. Вместо этого получите экземпляр редактора в каждом методе редактирования.

я рекомендую использование статической ссылки на ваш Application объект вместо передачи Context объекты для каждого get. Все экземпляры Application класс-это синглеты для каждого процесса в любом случае и передача Context объекты обычно плохая практика, потому что она имеет тенденцию приводить к утечкам памяти через удержание ссылок и излишне многословна.

наконец, ответьте на незаданный вопрос, если вы должны лениво-загрузить или жадно-инициализировать ссылку на ваш static SharedPreferences, вы должны лениво загружать статический метод геттера. Это мая работа жадно-инициализировать ссылку с final static SharedPreferences sReference = YourApplication.getInstance().getSharedPreferences() в зависимости от цепочки импорта классов, но загрузчику классов было бы слишком легко инициализировать ссылку перед Application уже onCreate (где вы инициализируете YourApplication reference), вызывая исключение нулевого указателя. В заключение:

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

затем вы будете использовать свой статически доступный класс настроек как таковой:

YourPreferencesClass.get().setValue(1);

последнее слово о потокобезопасности и наблюдаемости памяти. Некоторые проницательные наблюдатели могут заметить, что YourPreferencesClass.get() не синхронизируется, и, следовательно, опасен, потому что два потока могут инициализировать два разных объекта. Однако синхронизации можно избежать. Как я уже упоминал ранее,getSharedPreferences уже возвращает одну статическую ссылку, поэтому даже в крайне редкий случай sInstance устанавливается дважды, одна и та же базовая ссылка на есть. Относительно статического экземпляра YourApplication.sInstance, это также безопасно без синхронизации или volatile ключевое слово. В вашем приложении нет пользовательских потоков, запущенных до YourApplication.onCreate, и поэтому отношение "происходит до", определенное для вновь созданных потоков, гарантирует, что статическая ссылка будет видна всем будущим потокам, которые могут получить доступ к указанной ссылке.


Я думаю, что это безопасно. Я всегда использую "KeyStoreController" со статической ссылкой на объект SharedPreferences (singleton). Я бы предложил вам использовать контекст приложения вместо передачи контекста каждый раз. Это пример моего кода:

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

если вы проходите вокруг Context лучше всего пройти по ApplicationContext. Было бы проще, если бы вы просто сделали static ApplicationContext для ссылки, а затем просто используйте SharedPreferences когда они вам нужны из ваших классов (если этот подход работает для вас).

если у вас есть аргумент для вызовов a Context тогда вам не придется беспокоиться об утечках, если вы держите на него.

но, я думаю, что вы будете просто прекрасно делать то, что вы делаете концептуально.


почему бы просто не создать статический класс и использовать его в качестве утилиты, чтобы вам никогда не приходилось ссылаться на ваш SharedPreferences на всех. Вам также не нужно инициализировать экземпляр этого класса и можно просто вызвать PreferencesUtil.getUserName (context), если у вас есть контекст для предоставления.

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

}