MODE_MULTI_PROCESS for SharedPreferences isn't working

无人久伴 提交于 2019-12-18 10:20:02

问题


I have a SyncAdapter running on its own process separately from the main app process.

I'm using a static wrapper class around my SharedPreferences that creates a static object on process load (Application's onCreate) like so:

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

The wrapper has get and set methods, like so:

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

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

Both SyncAdapter and app uses this wrapper class to edit and get from the prefs, this works sometimes but a lot of times I see the SyncAdapter getting old/missing prefs on accesses to the prefs, while the main app sees the recent changes properly.

According to the docs I think the MODE_MULTI_PROCESS flag should work as I expect it to, allowing both processes to see latest changes, but it doesn't work.

Update:

Per x90's suggestion, I've tried refraining from using a static SharedPreferences object and instead calling getSharedPreferences on each get/set method. This caused a new issue, where the prefs file gets deleted (!!!) on multi-process simultaneous access. i.e. I see in the logcat:

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

and from that point all the prefs saved on the SharedPreferences object were deleted.

This is probably a result of another warning I see in the log:

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 this is not a deterministic issue, I saw the above logs after a crash happened, but couldn't recreate yet on the same device, and until now it didn't seem to happen on other devices.

ANOTHER UPDATE:

I've filed a bug report on this, after writing a small testing method to confirm this is indeed an Android issue, star it at https://code.google.com/p/android/issues/detail?id=66625


回答1:


Had exactly the same problem and my solution was to write a ContentProvider based replacement for the SharedPreferences. It works 100% multiprocess.

I made it a library for all of us. Here is the result: https://github.com/grandcentrix/tray




回答2:


I gave a very quick look at Google's code and apparently Context.MODE_MULTI_PROCESS is not an actual way to ensure process-safety of SharedPreferences.

SharedPreferences itself is not process-safe. (That's probably why SharedPreferences documentation says "currently this class does not support use across multiple processes. This will be added later.")

MODE_MULTI_PROCESS just works in conjunction with every Context.getSharedPreferences(String name, int mode) call: when you retrieve an instance of SharedPreferences specifying the MODE_MULTI_PROCESS flag android will reload the preferences file to be up to date with any (eventual) concurrent modification that occurred to it. If you then keep that instance as a class (static or not) member, the preference file won't be reloaded again.

Using Context.getSharedPreferences(...) every time you want to write or read into preferences is not process-safe either, but I guess it's probably the closest that you can get to it at the moment.

If you don't actually need to read the same preference from the different processes, then a workaround could be to use different preferences files for the different processes.




回答3:


I just ran into the same problem. I switched my app to run the service in a separate process and realized sharedPreferences was all broken.

Two things:

1) Are you using Editor.apply() or .commit()? I was using .apply(). I started checking my preference file either after the activity or the service made changes to it and realized whenever one would make a change, it would create a new file with only the newly changed value. I.E., a value written from the activity would be erased when a new value was written/changed from the service and vice versa. I switched to .commit() everywhere and this is no longer the case! From the documentation: "Note that when two editors are modifying preferences at the same time, the last one to call apply wins.

2) SharedPreferencesListener doesn't appear to work across processes even after switching to .commit(). You'll have to use Messenger Handlers or Broadcast Intents to notify of a change. When you look at the documentation for the SharedPreferences class it even says "Note: currently this class does not support use across multiple processes. This will be added later." http://developer.android.com/reference/android/content/SharedPreferences.html

In that respect we're lucky we even have the MODE_MULTI_PROCESS flag working to read/write from the same SharedPreferences across different processes.




回答4:


MODE_MULTI_PROCESS for SharedPreferences is depreciated now (android M -API level 23-onward).It was not process safe.




回答5:


MODE_MULTI_PROCESS was deprecated in API level 23. You can solve this problem with ContentProvider. DPreference uses a ContentProvider wrappering sharepreference. It has a better performance than using sqlite implmented. https://github.com/DozenWang/DPreference




回答6:


Because MODE_MULTI_PROCESS is not currently supported, I haven't found any way to work with Shared Preferences between processes other than working around it.

I know people are sharing the libraries they wrote to address this, but I actually used a third-party library I found on another thread that implements SQLLite in lieu of the Shared Preferences:

https://github.com/hamsterready/dbpreferences

However, what was important to me that I haven't found addressed in other solutions was maintaining the automatic UI generation already built into Preference Fragment - better to be able to specify your elements in XML and call addPreferencesFromResource(R.xml.preferences) than have to build your UI from scratch.

So, to make this work, I subclassed each of the Preference elements I needed (in my case just Preference, SwitchPreference, and EditTextPreference), and overrode a few methods from the base classes to include saving to an instance of DatabaseSharedPreferences taken from the above library.

For example, below I subclass EditTextPreference and get the preference key from the base class. I then override the persist and getPersisted methods in Preference base class. I then override onSetInitialValue, setText, and getText in the EditText base class.

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;
}

Then you simply specify the new element in your preferences.xml file, and voila! You now get the process interoperability of SQLLite and the UI auto-generation of 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"/>


来源:https://stackoverflow.com/questions/22129717/mode-multi-process-for-sharedpreferences-isnt-working

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!