SharedPreferences的MODE_MULTI_PROCESS不起作用

时间:2014-03-02 15:46:17

标签: android multiprocessing sharedpreferences

我有一个SyncAdapter在自己的进程上独立于主应用程序进程运行。

我在我的SharedPreferences周围使用了一个静态包装类,它在进程加载时创建了一个静态对象(应用程序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

6 个答案:

答案 0 :(得分:26)

我快速查看了Google的代码,显然Context.MODE_MULTI_PROCESS并不是确保SharedPreferences过程安全的实际方法。

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

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

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

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

答案 1 :(得分:16)

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

我为我们所有人建了一个图书馆。结果如下: https://github.com/grandcentrix/tray

答案 2 :(得分:6)

我遇到了同样的问题。我将我的应用程序切换到一个单独的进程中运行服务,并实现了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读取/写入。

答案 3 :(得分:2)

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

答案 4 :(得分:2)

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

答案 5 :(得分:1)

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

我知道人们正在共享他们为解决这个问题而编写的库,但我实际上使用了我在another thread上找到的第三方库来实现SQLLite而不是共享首选项:

https://github.com/hamsterready/dbpreferences

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

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

例如,下面我是子类EditTextPreference并从基类中获取首选项键。然后我覆盖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自动生成!

<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"/>