Ответ 1
Была такая же проблема, и моим решением было написать замену на основе ContentProvider для SharedPreferences. Он работает на 100% многопроцессе.
Я сделал это библиотекой для всех нас. Вот результат: https://github.com/grandcentrix/tray
У меня есть 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
Была такая же проблема, и моим решением было написать замену на основе ContentProvider для SharedPreferences. Он работает на 100% многопроцессе.
Я сделал это библиотекой для всех нас. Вот результат: https://github.com/grandcentrix/tray
Я очень быстро посмотрел на код Google и, по-видимому, Context.MODE_MULTI_PROCESS
не является реальным способом обеспечения безопасности операций SharedPreferences.
Собственные файлы SharedPreferences не являются безопасными для процесса. (Вероятно, почему в документации SharedPreferences говорится: "В настоящее время этот класс не поддерживает использование нескольких процессов, это будет добавлено позже".)
MODE_MULTI_PROCESS
работает только совместно с каждым вызовом Context.getSharedPreferences(String name, int mode)
: когда вы извлекаете экземпляр SharedPreferences с указанием флага MODE_MULTI_PROCESS
, андроид перезагрузит файл настроек, чтобы быть в курсе любой (возможной) параллельной модификации, которая произошла к нему. Если вы затем сохраните этот экземпляр как член класса (статический или нет), файл настроек не будет перезагружен снова.
Использование Context.getSharedPreferences(...)
каждый раз, когда вы хотите писать или читать в настройках, также не является безопасным процессом, но я думаю, это, вероятно, самое близкое, что вы можете получить к нему в данный момент.
Если вам действительно не нужно читать то же предпочтение от разных процессов, тогда обходным путем может быть использование разных файлов предпочтений для разных процессов.
Я столкнулся с одной и той же проблемой. Я переключил свое приложение, чтобы запустить службу в отдельном процессе, и реализовано, что 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
для разных процессов.
MODE_MULTI_PROCESS для SharedPreferences теперь амортизируется (уровень 23 Android-android). Это не было безопасным процессом.
MODE_MULTI_PROCESS устарел на уровне API 23. Вы можете решить эту проблему с помощью ContentProvider. В DPreference используется sharepreference обтекания ContentProvider. Он имеет лучшую производительность, чем при использовании sqlite. https://github.com/DozenWang/DPreference
Поскольку 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"/>