SharedPreferences的MODE_MULTI_PROCESS不起作用

我有一个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和app都使用这个包装类来编辑和从prefs中获取,这有时会起作用,但很多时候我看到SyncAdapter在访问prefs时获得旧的/缺少的prefs,而主应用程序正确地看到了最近的更改。

根据文档,我认为MODE_MULTI_PROCESS标志应该按照我的预期工作,允许两个进程看到最新的更改,但它不起作用。

更新:

根据x90的建议,我试图避免使用静态SharedPreferences对象,而是在每个get / set方法上调用getSharedPreferences 。 这导致了一个新问题,即prefs文件在多进程同时访问时被删除(!!!)。 即我在logcat中看到:

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

从那时起,保存在SharedPreferences对象上的所有prefs都被删除了。

这可能是我在日志中看到的另一个警告的结果:

 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) 

PS这不是一个确定性的问题,我在发生崩溃后看到了上面的日志,但是无法在同一设备上重新创建,直到现在它似乎没有在其他设备上发生。

另一个更新:

我已经提交了一个关于此的错误报告,在编写了一个小的测试方法来确认这确实是一个Android问题之后, 请点击https://code.google.com/p/android/issues/detail?id=66625

    有完全相同的问题,我的解决方案是为SharedPreferences写一个基于ContentProvider的替换。 它可以100%多进程工作。

    我把它变成了我们所有人的图书馆。 结果如下: https : //github.com/grandcentrix/tray

    我快速浏览了一下Google的代码,显然Context.MODE_MULTI_PROCESS并不是确保SharedPreferences流程安全的实际方法。

    SharedPreferences本身不是过程安全的。 (这可能就是为什么SharedPreferences文档说“目前这个类不支持跨多个进程使用。这将在以后添加。”)

    MODE_MULTI_PROCESS只与每个Context.getSharedPreferences(String name, int mode)调用一起使用:当你检索指定MODE_MULTI_PROCESS标志的SharedPreferences实例时,android将重新加载首选项文件,使其与发生的任何(最终)并发修改保持同步它。 如果您将该实例保留为类(静态或非静态)成员,则不会再次重新加载首选项文件。

    每次想要写入或读入首选项时使用Context.getSharedPreferences(...)也不是过程安全的,但我想它可能是目前最接近它的。

    如果您实际上不需要从不同的进程中读取相同的首选项,那么解决方法可能是为不同的进程使用不同的首选项文件。

    我刚遇到同样的问题。 我将我的应用程序切换为在单独的进程中运行服务,并实现了sharedPreferences全部被破坏。

    两件事情:

    1)您使用的是Editor.apply()还是.commit() ? 我正在使用.apply() 。 我在活动或服务对其进行更改后开始检查我的首选项文件,并且无论何时进行更改都会实现,它将创建一个仅包含新更改值的新文件。 IE,当从服务写入/更改新值时,将从活动写入的值被擦除,反之亦然。 我到处都切换到.commit() ,现在不再是这样了! 从文档中可以看出“请注意,当两个编辑器同时修改首选项时,最后一个编辑器应用获胜。

    2)即使在切换到.commit()之后, SharedPreferencesListener似乎也不能跨进程工作。 您必须使用Messenger Handlers或Broadcast Intents来通知更改。 当你查看SharedPreferences类的文档时,它甚至会说“注意:目前这个类不支持跨多个进程使用。这将在以后添加。” http://developer.android.com/reference/android/content/SharedPreferences.html

    在这方面,我们很幸运,我们甚至让MODE_MULTI_PROCESS标志在不同进程中从相同的SharedPreferences读取/写入。

    现在折旧了SharedPreferences的MODE_MULTI_PROCESS(android M -API 23级以上)。它不是进程安全的。

    在API级别23中不推荐使用MODE_MULTI_PROCESS。您可以使用ContentProvider解决此问题。 DPreference使用ContentProvider包装共享首选项。 它比使用sqlite实现的性能更好。 https://github.com/DozenWang/DPreference

    因为当前不支持MODE_MULTI_PROCESS,所以我没有find任何方法在除了解决它之外的进程之间使用共享首选项。

    我知道人们正在共享他们编写的库以解决这个问题,但我实际上使用了我在另一个实现SQLLite的线程上find的第三方库来代替共享首选项:

    https://github.com/hamsterready/dbpreferences

    但是,我在其他解决方案中找不到的重要内容是维护已经内置到Preference Fragment中的自动UI生成 – 更好的是能够在XML中指定元素并调用addPreferencesFromResource(R.xml.preferences)。必须从头开始构建您的UI。

    因此,为了使这项工作,我将我需要的每个Preference元素(在我的例子中只是Preference,SwitchPreference和EditTextPreference)子类化,并从基类中覆盖了一些方法,以包括保存到从上面获取的DatabaseSharedPreferences的实例图书馆。

    例如,在I下面inheritanceEditTextPreference并从基类中获取首选项键。 然后我覆盖Preference基类中的persist和getPersisted方法。 然后我在EditText基类中重写onSetInitialValue,setText和getText。

     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文件中指定新元素,瞧! 您现在可以获得SQLLite的流程互操作性和PreferenceFragment的UI自动生成!