Pull to refresh

Comments 9

Текущая реализация SharedPreferences очень плохая.
Из проблем:
1. Загрузка данных из файла может происходить в UI потоке при создании инстанса SharedPreferences. В новых версиях SDK это уже вроде как починили.
2. apply() добавляет таску сохранения в очередь, которая общая для всех Preferences. И самое печальное, что пока эта очередь не обработана, активити залипает при закрытии. Для чего это сделано — непонятно. Уж лучше commit() в бэкграунде.
3. Каждый экземпляр SharedPreferences работает со своей копией данных. Надо следить, чтобы на каждый файл был один инстанс.
4. На больших данных работает очень медленно.
3. Каждый экземпляр SharedPreferences работает со своей копией данных. Надо следить, чтобы на каждый файл был один инстанс.

Это не так, в документации чётко прописано:
Interface for accessing and modifying preference data returned by Context#getSharedPreferences. For any particular set of preferences, there is a single instance of this class that all clients share.
4 На больших данных работает очень медленно.

SharedPrefernces сами по себе никогда не были предназначены для хранения больших данных, иначе в них бы были методы такие как: putBundle и putObject

Чтобы не делать для каждого типа данных отдельный делегат воспользуемся обобщениями

Потерять типобезопасность — чего ради? Уж лучше накопипастить правильных классов.

Рекомендации по предложенному делегату:
1. preferences.edit() можно создать один раз и хранить ссылку в классе.
2. apply() желательно вызывать отдельно, так как если будет много вызовов setValue(), то на каждый apply() будет отдельное сохранение в файл. Общий Editor может частично решить эту проблему.
3. В preferences для каждого значения сохраняется также и его тип. Можно предусмотреть прозрачную. конвертацию. Иначе, возможны конфликты, например, Integer <-> Long.
1 preferences.edit() можно создать один раз и хранить ссылку в классе.

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


2 apply() желательно вызывать отдельно, так как если будет много вызовов setValue(), то на каждый apply() будет отдельное сохранение в файл. Общий Editor может частично решить эту проблему.

Тогда нужен дополнительный механизм сохранения, который будет работать внутри класса, что повышает сложность.
По моему опыту в приложении с 30+ экранами таких делегатов штук 10-15, и они вызываются реже чем раз в 5 секунд, так что сильно задумываться о скорости работы не приходилось

Я бы это сделал проще
interface SharedPreferencesHolder {
    val sharedPreferences: SharedPreferences
}

abstract class CommonPreferenceDelegate<T>(val name: String? = null) : ReadWriteProperty<SharedPreferencesHolder, T> {
    private fun getPreferenceKey(property: KProperty<*>) = name ?: property.name

    final override fun getValue(thisRef: SharedPreferencesHolder, property: KProperty<*>): T {
        return getValue(thisRef.sharedPreferences, getPreferenceKey(property))
    }

    final override fun setValue(thisRef: SharedPreferencesHolder, property: KProperty<*>, value: T) {
        setValue(thisRef.sharedPreferences, getPreferenceKey(property), value)
    }

    abstract fun getValue(prefs: SharedPreferences, key: String): T
    abstract fun setValue(prefs: SharedPreferences, key: String, value: T)
}

class BooleanPreferenceDelegate(private val defValue: Boolean, name: String? = null) : CommonPreferenceDelegate<Boolean>(name) {
    override fun getValue(prefs: SharedPreferences, key: String) = prefs.getBoolean(key, defValue)
    override fun setValue(prefs: SharedPreferences, key: String, value: Boolean) {
        prefs.edit().putBoolean(key, value).commit()
    }
}

fun SharedPreferencesHolder.boolean(defValue: Boolean = false, name: String? = null) = BooleanPreferenceDelegate(defValue, name)

class Foo(override val sharedPreferences: SharedPreferences) : SharedPreferencesHolder {
    val isUserLogged by boolean(false)
}

Ну и нафаршировать сверху классов для остальных типов.


NotFoundRealizationException

Реализация по-бассурмански будет implementation

Да implementation суда подходит лучше.


Считаю плохой идей давать возможность не задавать key ключа, при изменении названия переменной необходимо помнить, что нужно будет указать name прошлым именем, чтобы не потерять данные.
На мой взгляд получилось не особо проще, но это уже риторический вопрос

Ну key можно и за'require'ить, не суть дело.
Просто Ваше решение очень теряет в типобезопасности — попробуйте туда добавить Set<String> без unchecked cast'a.
Ну и хранит инстанс SharedPreferences в каждом делегате.

Sign up to leave a comment.

Articles