Лучшие рекомендации для Android SharedPreferences
В приложении, которое я создаю, мы немного полагаемся на SharedPreferences, это заставило меня задуматься о том, что лучше всего подходит для доступа к SharedPreferences. Например, многие люди говорят, что подходящий способ получить доступ к нему осуществляется через этот вызов:
PreferenceManager.getDefaultSharedPreferences(Context context)
Однако похоже, что это может быть опасно. Если у вас есть большое приложение, которое полагается на SharedPreferences, у вас может быть ключевое дублирование, особенно в случае использования сторонней библиотеки, которая также использует SharedPreferences. Мне кажется, что лучший призыв к использованию:
Context.getSharedPreferences(String name, int mode)
Таким образом, если у вас есть класс, который сильно зависит от SharedPreferences, вы можете создать файл настроек, который используется только вашим классом. Вы можете использовать полное имя класса, чтобы убедиться, что файл, скорее всего, не будет дублироваться кем-то другим.
Также на основе этого вопроса SO: Должен ли доступ к SharedPreferences сделать из потока пользовательского интерфейса?, кажется, что доступ к SharedPreferences должен выполняться из потока пользовательского интерфейса, что имеет смысл.
Есть ли другие рекомендации, которые разработчики Android должны знать при использовании SharedPreferences в своих приложениях?
Ответы
Ответ 1
Если у вас есть большое приложение, которое полагается на SharedPreferences, у вас может быть ключевое дублирование, особенно в случае использования какой-либо сторонней библиотеки, которая также использует SharedPreferences.
Библиотеки не должны использовать этот конкретный SharedPreferences
. Значение по умолчанию SharedPreferences
должно использоваться только приложением.
Таким образом, если у вас есть класс, который сильно зависит от SharedPreferences, вы можете создать файл настроек, который используется только вашим классом.
Вы, безусловно, можете это сделать. Я бы не стал на уровне приложения, поскольку основной причиной для SharedPreferences
является их совместное использование среди компонентов в приложении. У команды разработчиков не должно быть проблем с управлением этим пространством имен, так же как у них не должно быть проблем с управлением именами классов, пакетов, ресурсов или других материалов на уровне проекта. Кроме того, по умолчанию SharedPreferences
будет использоваться ваш PreferenceActivity
.
Однако, возвращаясь к точке ваших библиотек, библиотеки многократного использования должны использовать отдельный SharedPreferences
только для своей библиотеки. Я бы не основывал его на имени класса, потому что тогда вы один рефакторинг от взлома приложения. Вместо этого выберите уникальное имя (например, на основе имени библиотеки, например "com.commonsware.cwac.wakeful.WakefulIntentService"
), но стабильного.
кажется, что доступ к SharedPreferences должен выполняться из потока пользовательского интерфейса, что имеет смысл.
В идеале, да. Недавно я выпустил SharedPreferencesLoader
, который помогает с этим.
Есть ли другие рекомендации, которые разработчики Android должны знать при использовании SharedPreferences в своих приложениях?
Не переусердствуйте на них. Они хранятся в файлах XML и не являются транзакционными. База данных должна быть вашим основным хранилищем данных, особенно для данных, которые вы действительно не хотите потерять.
Ответ 2
Я написал небольшую статью, которую также можно найти здесь. Он описывает, что такое SharedPreferences
:
Лучшая практика: SharedPreferences
Android предоставляет множество способов хранения данных приложения. Один из этих способов приводит нас к объекту SharedPreferences, который используется для хранения личных примитивных данных в парах ключ-значение.
Вся логика основана только на трех простых классах:
SharedPreferences
SharedPreferences
является основным из них. Он отвечает за получение (анализ) хранимых данных, предоставляет интерфейс для получения объекта Editor
и интерфейсы для добавления и удаления OnSharedPreferenceChangeListener
- Для создания
SharedPreferences
вам понадобится объект Context
(может быть приложением Context
) - Метод
getSharedPreferences
анализирует файл настроек и создает для него объект Map
-
Вы можете создать его в нескольких режимах, предоставляемых Context. Настоятельно рекомендуется использовать MODE_PRIVATE, поскольку создание общедоступных/доступных для записи файлов очень опасно и может привести к дырам в безопасности приложений.
// parse Preference file
SharedPreferences preferences = context.getSharedPreferences("com.example.app", Context.MODE_PRIVATE);
// get values from Map
preferences.getBoolean("key", defaultValue)
preferences.get..("key", defaultValue)
// you can get all Map but be careful you must not modify the collection returned by this
// method, or alter any of its contents.
Map<String, ?> all = preferences.getAll();
// get Editor object
SharedPreferences.Editor editor = preferences.edit();
//add on Change Listener
preferences.registerOnSharedPreferenceChangeListener(mListener);
//remove on Change Listener
preferences.unregisterOnSharedPreferenceChangeListener(mListener);
// listener example
SharedPreferences.OnSharedPreferenceChangeListener mOnSharedPreferenceChangeListener
= new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
}
};
редактор
SharedPreferences.Editor
- это интерфейс, используемый для изменения значений в объекте SharedPreferences
. Все изменения, которые вы делаете в редакторе, пакетируются и не копируются обратно в исходные SharedPreferences
пока вы не SharedPreferences
commit() или apply()
- Используйте простой интерфейс для ввода значений в
Editor
- Сохраняйте значения синхронно с
commit()
или асинхронно с apply
что быстрее. На самом деле использование различных потоков с использованием commit()
безопаснее. Вот почему я предпочитаю использовать commit()
. -
Удалите одно значение с помощью remove()
или удалите все значения с помощью clear()
// get Editor object
SharedPreferences.Editor editor = preferences.edit();
// put values in editor
editor.putBoolean("key", value);
editor.put..("key", value);
// remove single value by key
editor.remove("key");
// remove all values
editor.clear();
// commit your putted values to the SharedPreferences object synchronously
// returns true if success
boolean result = editor.commit();
// do the same as commit() but asynchronously (faster but not safely)
// returns nothing
editor.apply();
Производительность и советы
-
SharedPreferences
- это объект Singleton, поэтому вы можете легко получить столько ссылок, сколько хотите, он открывает файл только при первом вызове getSharedPreferences
или создает только одну ссылку для него.
// There are 1000 String values in preferences
SharedPreferences first = context.getSharedPreferences("com.example.app", Context.MODE_PRIVATE);
// call time = 4 milliseconds
SharedPreferences second = context.getSharedPreferences("com.example.app", Context.MODE_PRIVATE);
// call time = 0 milliseconds
SharedPreferences third = context.getSharedPreferences("com.example.app", Context.MODE_PRIVATE);
// call time = 0 milliseconds
-
Поскольку SharedPreferences
является объектом Singleton, вы можете изменить любой из его экземпляров и не бояться, что их данные будут другими.
first.edit().putInt("key",15).commit();
int firstValue = first.getInt("key",0)); // firstValue is 15
int secondValue = second.getInt("key",0)); // secondValue is also 15
-
Помните, что чем больше объект Preference, тем дольше будут операции get
, commit
, apply
, remove
и clear
. Поэтому настоятельно рекомендуется разделить ваши данные на разные мелкие объекты.
-
Ваши настройки не будут удалены после обновления приложения. Так что бывают случаи, когда вам нужно создать какую-то схему миграции. Например, у вас есть приложение, которое анализирует локальный JSON при запуске приложения, чтобы сделать это только после первого запуска, вы решили сохранить логический флаг wasLocalDataLoaded
. Через некоторое время вы обновили этот JSON и выпустили новую версию приложения. Пользователи будут обновлять свои приложения, но они не будут загружать новый JSON, потому что они уже сделали это в первой версии приложения.
public class MigrationManager {
private final static String KEY_PREFERENCES_VERSION = "key_preferences_version";
private final static int PREFERENCES_VERSION = 2;
public static void migrate(Context context) {
SharedPreferences preferences = context.getSharedPreferences("pref", Context.MODE_PRIVATE);
checkPreferences(preferences);
}
private static void checkPreferences(SharedPreferences thePreferences) {
final double oldVersion = thePreferences.getInt(KEY_PREFERENCES_VERSION, 1);
if (oldVersion < PREFERENCES_VERSION) {
final SharedPreferences.Editor edit = thePreferences.edit();
edit.clear();
edit.putInt(KEY_PREFERENCES_VERSION, currentVersion);
edit.commit();
}
}
}
-
SharedPreferences
хранятся в файле XML в папке данных приложения
// yours preferences
/data/data/YOUR_PACKAGE_NAME/shared_prefs/YOUR_PREFS_NAME.xml
// default preferences
/data/data/YOUR_PACKAGE_NAME/shared_prefs/YOUR_PACKAGE_NAME_preferences.xml
Руководство по Android.
Образец кода
public class PreferencesManager {
private static final String PREF_NAME = "com.example.app.PREF_NAME";
private static final String KEY_VALUE = "com.example.app.KEY_VALUE";
private static PreferencesManager sInstance;
private final SharedPreferences mPref;
private PreferencesManager(Context context) {
mPref = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
}
public static synchronized void initializeInstance(Context context) {
if (sInstance == null) {
sInstance = new PreferencesManager(context);
}
}
public static synchronized PreferencesManager getInstance() {
if (sInstance == null) {
throw new IllegalStateException(PreferencesManager.class.getSimpleName() +
" is not initialized, call initializeInstance(..) method first.");
}
return sInstance;
}
public void setValue(long value) {
mPref.edit()
.putLong(KEY_VALUE, value)
.commit();
}
public long getValue() {
return mPref.getLong(KEY_VALUE, 0);
}
public void remove(String key) {
mPref.edit()
.remove(key)
.commit();
}
public boolean clear() {
return mPref.edit()
.clear()
.commit();
}
}
Ответ 3
Это мой путь
для записи
SharedPreferences settings = context.getSharedPreferences("prefs", 0);
SharedPreferences.Editor editore = settings.edit();
editore.putString("key", "some value");
editore.apply();
читать
SharedPreferences settings = getSharedPreferences("prefs", 0);
Strings value = settings.getString("key", "");
Ответ 4
Существует несколько библиотек, которые помогут вам справиться с SharedPreferences. Недавно я обнаружил Hawk (https://github.com/orhanobut/hawk) и StoreBox (https://github.com/martino2k6/StoreBox).
Ответ 5
Предположим, что в проекте с несколькими разработчиками, работающими над ним, они определяют SharedPreference в Деятельности следующим образом:
SharedPreferences sharedPref = context.getSharedPreferences("prefName", 0);
В тот или иной момент два разработчика могут определить SharedPreference с тем же именем или вставить эквивалентные пары ключ - значение, что приведет к проблемам при использовании ключей.
Решение опирается на два варианта, будь то использовать;
-
SharedPreferences Singleton, использующий строковые ключи.
-
SharedPreferences Singleton, использующий ключи Enum.
Лично и согласно этой Документации Sharepreference, я предпочитаю использовать ключи Enum, так как это обеспечивает более строгий контроль, когда над проектом работают несколько программистов. У программиста нет другого выбора, кроме как объявить новый ключ в соответствующем классе enum, и все ключи находятся в одном месте.
И чтобы избежать написания стандартного кода, создайте синглтон SharedPreference. Этот одноэлементный класс SharedPreferences помогает централизовать и упростить чтение и запись SharedPreferences в вашем приложении для Android.
Исходный код для двух предоставленных решений можно найти в GitHub
Ответ 6
В kotlin использование SharedPreferences
можно упростить следующим образом.
class Prefs(context: Context) {
companion object {
private const val PREFS_FILENAME = "app_prefs"
private const val KEY_MY_STRING = "my_string"
private const val KEY_MY_BOOLEAN = "my_boolean"
private const val KEY_MY_ARRAY = "string_array"
}
private val sharedPrefs: SharedPreferences =
context.getSharedPreferences(PREFS_FILENAME, Context.MODE_PRIVATE)
var myString: String
get() = sharedPrefs.getString(KEY_MY_STRING, "") ?: ""
set(value) = sharedPrefs.edit { putString(KEY_MY_STRING, value) }
var myBoolean: Boolean
get() = sharedPrefs.getBoolean(KEY_MY_BOOLEAN, false)
set(value) = sharedPrefs.edit { putBoolean(KEY_MY_BOOLEAN, value) }
var myStringArray: Array<String>
get() = sharedPrefs.getStringSet(KEY_MY_ARRAY, emptySet())?.toTypedArray()
?: emptyArray()
set(value) = sharedPrefs.edit { putStringSet(KEY_MY_ARRAY, value.toSet()) }
Здесь sharedPrefs.edit{...}
предоставляется библиотекой ktx для ядра android и должен быть реализован путем добавления implementation "androidx.core:core-ktx:1.0.2"
зависимостей implementation "androidx.core:core-ktx:1.0.2"
на уровне приложения build.gradle
.
Вы можете получить экземпляр SharedPreferences
с помощью кода:
val prefs = Prefs(context)
Кроме того, вы можете создать объект Prefs
Singleton
и использовать его в любом месте приложения.
val prefs: Prefs by lazy {
Prefs(App.instance)
}
где App
расширяет Application
и должно быть включено в AndroidManifest.xml
App.kt
class App:Application() {
companion object {
lateinit var instance: App
}
override fun onCreate() {
super.onCreate()
instance = this
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest .....
<application
android:name=".App"
....
Пример использования:
// get stored value
val myString = prefs.myString
// store value
prefs.myString = "My String Value"
// get stored array
val myStringArray = prefs.myStringArray
// store array
prefs.myStringArray = arrayOf("String 1","String 2","String 3")