Подтвердить что ты не робот

MODE_MULTI_PROCESS для SharedPreferences не работает

У меня есть SyncAdapter, работающий на собственном процессе отдельно от основного процесса приложения.

Я использую статический класс-оболочку вокруг моего SharedPreferences, который создает статический объект при загрузке процесса (Application onCreate) следующим образом:

myPrefs = context.getSharedPreferences(MY_FILE_NAME, Context.MODE_MULTI_PROCESS | Context.MODE_PRIVATE);

Обертка имеет методы get и set, например:

public static String getSomeString() {
    return myPrefs.getString(SOME_KEY, null);
}

public static void setSomeString(String str) {
    myPrefs.edit().putString(SOME_KEY, str).commit();
}

Оба SyncAdapter и приложение используют этот класс-оболочку для редактирования и получения из prefs, это работает иногда, но много раз я вижу, что SyncAdapter получает старые/отсутствующие префы при обращении к префайлам, в то время как основное приложение правильно видит последние изменения.

Согласно документам, я думаю, что флаг MODE_MULTI_PROCESS должен работать так, как я ожидаю, чтобы оба процесса могли видеть последние изменения, но это не работает.

Update:

Per x90, я попытался воздержаться от использования статического объекта SharedPreferences и вместо этого вызывать getSharedPreferences для каждого метода get/set. Это вызвало новую проблему, когда файл prefs удаляется (!!!) при одновременном доступе к нескольким процессам. то есть в логарифме:

(process 1): getName => "Name"
(process 2): getName => null
(process 1): getName => null

и с этой точки все префы, сохраненные на объекте SharedPreferences, были удалены.

Это, вероятно, результат другого предупреждения, которое я вижу в журнале:

W/FileUtils(21552): Failed to chmod(/data/data/com.my_company/shared_prefs/prefs_filename.xml): libcore.io.ErrnoException: chmod failed: ENOENT (No such file or directory)

P.S это не детерминированная проблема, я видел вышеописанные журналы после аварии, но не смог воссоздать еще на одном устройстве, и до сих пор это не похоже на другие устройства.

ДРУГОЕ ОБНОВЛЕНИЕ:

Я написал отчет об ошибке после написания небольшого метода тестирования, чтобы подтвердить, что это действительно проблема с Android, запустите его https://code.google.com/p/android/issues/detail?id=66625

4b9b3361

Ответ 1

Была такая же проблема, и моим решением было написать замену на основе ContentProvider для SharedPreferences. Он работает на 100% многопроцессе.

Я сделал это библиотекой для всех нас. Вот результат: https://github.com/grandcentrix/tray

Ответ 2

Я очень быстро посмотрел на код Google и, по-видимому, Context.MODE_MULTI_PROCESS не является реальным способом обеспечения безопасности операций SharedPreferences.

Собственные файлы SharedPreferences не являются безопасными для процесса. (Вероятно, почему в документации SharedPreferences говорится: "В настоящее время этот класс не поддерживает использование нескольких процессов, это будет добавлено позже".)

MODE_MULTI_PROCESS работает только совместно с каждым вызовом Context.getSharedPreferences(String name, int mode): когда вы извлекаете экземпляр SharedPreferences с указанием флага MODE_MULTI_PROCESS, андроид перезагрузит файл настроек, чтобы быть в курсе любой (возможной) параллельной модификации, которая произошла к нему. Если вы затем сохраните этот экземпляр как член класса (статический или нет), файл настроек не будет перезагружен снова.

Использование Context.getSharedPreferences(...) каждый раз, когда вы хотите писать или читать в настройках, также не является безопасным процессом, но я думаю, это, вероятно, самое близкое, что вы можете получить к нему в данный момент.

Если вам действительно не нужно читать то же предпочтение от разных процессов, тогда обходным путем может быть использование разных файлов предпочтений для разных процессов.

Ответ 3

Я столкнулся с одной и той же проблемой. Я переключил свое приложение, чтобы запустить службу в отдельном процессе, и реализовано, что sharedPreferences все сломалось.

Две вещи:

1) Используете ли вы Editor.apply() или .commit()? Я использовал .apply(). Я начал проверять свой файл предпочтений либо после того, как активность, либо служба внесла в него изменения, и поняла, когда кто-то внесет изменения, он создаст новый файл только с новым измененным значением. I.E., значение, записанное в результате операции, будет стерто, когда новое значение было записано/изменено из службы и наоборот. Я перешел на .commit() всюду, и это уже не так! Из документации: "Обратите внимание, что, когда два редактора изменяют предпочтения в одно и то же время, последний, который вызывается, применяет выигрыши.

2) SharedPreferencesListener, похоже, не работает через процессы даже после переключения на .commit(). Вам нужно будет использовать Messenger Handlers или Broadcast Intents для уведомления об изменении. Когда вы смотрите на документацию для класса SharedPreferences, он даже говорит "Примечание: в настоящее время этот класс не поддерживает использование нескольких процессов. Это будет добавлено позже". http://developer.android.com/reference/android/content/SharedPreferences.html

В этом отношении нам повезло, что мы даже имеем флаг MODE_MULTI_PROCESS, который работает для чтения/записи из одного и того же SharedPreferences для разных процессов.

Ответ 4

MODE_MULTI_PROCESS для SharedPreferences теперь амортизируется (уровень 23 Android-android). Это не было безопасным процессом.

Ответ 5

MODE_MULTI_PROCESS устарел на уровне API 23. Вы можете решить эту проблему с помощью ContentProvider. В DPreference используется sharepreference обтекания ContentProvider. Он имеет лучшую производительность, чем при использовании sqlite.  https://github.com/DozenWang/DPreference

Ответ 6

Поскольку MODE_MULTI_PROCESS в настоящее время не поддерживается, я не нашел способ работать с Shared Preferences между процессами, отличными от работы вокруг него.

Я знаю, что люди делят библиотеки, которые они пишут, чтобы решить эту проблему, но я фактически использовал стороннюю библиотеку, которую я нашел в еще один поток, который реализует SQLLite вместо общих настроек:

https://github.com/hamsterready/dbpreferences

Однако для меня было важно, чтобы я не нашел решения в других решениях, поддерживая автоматическое создание пользовательского интерфейса, уже встроенное в Preference Fragment, - лучше иметь возможность указывать ваши элементы в XML и вызывать addPreferencesFromResource (R.xml. предпочтения), чем создавать свой пользовательский интерфейс с нуля.

Итак, чтобы сделать эту работу, я подклассифицировал каждый из элементов Preference, которые мне нужны (в моем случае просто Preference, SwitchPreference и EditTextPreference), и переопределил несколько методов из базовых классов, чтобы включить сохранение в экземпляр DatabaseSharedPreferences, сделанный из указанной библиотеки.

Например, ниже подкласса EditTextPreference и получить ключ предпочтения из базового класса. Затем я переопределяю методы persist и getPersisted в базовом классе Preference. Затем я переопределяю onSetInitialValue, setText и getText в базовом классе EditText.

public class EditTextDBPreference extends EditTextPreference {
private DatabaseBasedSharedPreferences mDBPrefs;
private String mKey;
private String mText;

public EditTextDBPreference(Context context) {
    super(context);
    init(context);
}

public EditTextDBPreference(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(context);
}

public EditTextDBPreference(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init(context);
}

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public EditTextDBPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
    init(context);
}

private void init(Context context)
{
    mDBPrefs = new DatabaseBasedSharedPreferences(context);
    mKey = super.getKey();
}

public DatabaseBasedSharedPreferences getSharedDBPreferences()
{
    if (mDBPrefs == null) {
        return null;
    }
    return mDBPrefs;
}

@Override
protected boolean persistBoolean(boolean value) {
    if (mKey != null)
        mDBPrefs.putBoolean(mKey,value);
    return super.persistBoolean(value);
}

@Override
protected boolean persistFloat(float value) {
    if (mKey != null)
        mDBPrefs.putFloat(mKey, value);
    return super.persistFloat(value);
}

@Override
protected boolean persistInt(int value) {
    if (mKey != null)
        mDBPrefs.putInt(mKey, value);
    return super.persistInt(value);
}

@Override
protected boolean persistLong(long value) {
    if (mKey != null)
        mDBPrefs.putLong(mKey, value);
    return super.persistLong(value);
}

@Override
protected boolean persistString(String value) {
    if (mKey != null)
        mDBPrefs.putString(mKey, value);
    return super.persistString(value);
}

@Override
protected boolean getPersistedBoolean(boolean defaultReturnValue) {
    if (mKey == null)
        return false;
    return mDBPrefs.getBoolean(mKey, defaultReturnValue);
}

@Override
protected float getPersistedFloat(float defaultReturnValue) {
    if (mKey == null)
        return -1f;
    return mDBPrefs.getFloat(mKey, defaultReturnValue);
}

@Override
protected int getPersistedInt(int defaultReturnValue) {
    if (mKey == null)
        return -1;
    return mDBPrefs.getInt(mKey, defaultReturnValue);
}

@Override
protected long getPersistedLong(long defaultReturnValue) {
    if (mKey == null)
        return (long)-1.0;
    return mDBPrefs.getLong(mKey, defaultReturnValue);
}

@Override
protected String getPersistedString(String defaultReturnValue) {
    if (mKey == null)
        return null;
    return mDBPrefs.getString(mKey, defaultReturnValue);
}

@Override
public void setKey(String key) {
    super.setKey(key);
    mKey = key;
}

@Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
    setText(restoreValue ? getPersistedString(mText) : (String) defaultValue);
}

@Override
public void setText(String text) {
    final boolean wasBlocking = shouldDisableDependents();
    boolean textChanged = false;
    if (mText != null && !mText.equals(text))
        textChanged = true;
    mText = text;

    persistString(text);
    if (textChanged) {
        // NOTE: This is a an external class in my app that I use to send a broadcast to other processes that preference settings have changed
        BASettingsActivity.SendSettingsUpdate(getContext());
    }
    final boolean isBlocking = shouldDisableDependents();
    if (isBlocking != wasBlocking) {
        notifyDependencyChange(isBlocking);
    }
}

@Override
public String getText() {
    return mText;
}

Затем вы просто указываете новый элемент в файле preferences.xml и voila! Теперь вы получаете возможность взаимодействия процессов SQLLite и автогенерации пользовательского интерфейса PreferenceFragment!

<com.sampleproject.EditTextDBPreference
        android:key="@string/pref_key_build_number"
        android:title="@string/build_number"
        android:enabled="false"
        android:selectable="false"
        android:persistent="false"
        android:shouldDisableView="false"/>