我有一个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
答案 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"/>