MODE_MULTI_PROCESS for SharedPreferences isn't working - android

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

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.

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

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.

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

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

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

I know this is an old post, but still a problem that many folks have tried to resolve with a ContentProvider, but that is a problematic approach, as it can cause ANRs, crashes, and general UI performance problems if read/write calls are not done on a background thread.
I don't mean this to be a plug, however, I wrote a library that implements the SharedPreference interface that is process-safe, but doesn't utilize ContentProvider (it uses FileObserver to sync data between processes). https://github.com/pablobaxter/Harmony
My hope is that this solves the issue many folks are having with trying to use SharedPreferences in multiple processes, and still keep their apps performant.

Related

Manage sharedPreference in the AAR library

I am building an AAR Library. Since there is a complex setting in my library. I hope to help users that using this library can easily set/remember their setting. Thus, I try to make my AAR Library to manage sharedPreference for users.
For example, here is the structure of my project
AppAAA (dependent on LibBBB)
| - MainActivity
LibBBB
| - LibSetting
| - LibSettingFragment
| - LibSettingActivity
| - ... - preferences.xml
I try to implement something like:
public class LibSetting() {
static final String KEY_SERVER_PORT = "PREF_KEY_SERVER_PORT";
SharedPreferences pref;
Context context;
public LibSetting(Context context) {
this.context = context;
this.pref = ((Activity)context).getPreferences(Context.MODE_PRIVATE);
}
public int getServerPort() {
return pref.getInt("PREF_KEY_SERVER_PORT", 50005);
}
public void showSettingActivity() {
Intent i = new Intent(context.getApplicationContext(), LibSettingActivity.class);
context.startActivity(i);
}
}
The implementation of LibSettingActivity and LibSettingFragment are trivial. I basically just call "addPreferencesFromResource(R.xml.preferences);" to load my preference to set (NOTE: I use the same key, PREF_KEY_SERVER_PORT, in the xml).
However, it is not working. The preference set by the LibSettingFragment is totally not propagated to the preference loaded by pref.getXXX();
My guess is because the LibSettingFragment has no way to know that I want to set the preference from the "application's context" rather than the lib's context. How can I do it?
The implementation of LibSettingActivity and LibSettingFragment are trivial. I basically just call "addPreferencesFromResource(R.xml.preferences);" to load my preference to set (NOTE: I use the same key, PREF_KEY_SERVER_PORT, in the xml).
I do not recommend this, if you plan on distributing this library. Your library is now polluting the app's main SharedPreferences.
The preference set by the LibSettingFragment is totally not propagated to the preference loaded by pref.getXXX();
That is because you are using the wrong SharedPreferences. A PreferenceFragment uses the app's main SharedPreferences, retrieved via PreferenceManager.getDefaultSharedPreferences().
Using your own "private" SharedPreferences in a library is OK, though it does pose some problems for some apps (e.g., those that need to control all data storage, for encryption).
CommonsWare gives the right answer. But for people still wanna use app's sharedPreference (rather than the DefaultSharedPreference), following hack will help to do so:
#Override
public void onResume() {
super.onResume();
// Set up a listener whenever a key changes
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
}
#Override
public void onPause() {
super.onPause();
// Set up a listener whenever a key changes
getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
}
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
// passed as an input argument from the app
appPref.edit().putInt(key, 99999).commit();
}

Shared Preferences reset data when app crashed. Please guide

My app got crashed and all the data in shared preference got cleared.
I am saving some flags and maintaining user session in shared preference.
One of the flag is IsFirstLaunch, which tells me whether app is launching for first time or not, if returns true then I am downloading some data from server and storing in SQLite database.
Please guide, thanks in advance.
So after the crash when it went to load the Preferences there was a blank in the preferences xml file which caused the preferences to reset.
To avoid this you could put all preference modifications in synchronized blocks or even use one synchronized static method for all preference writing.
I think - you need a better way of managing and storing the data you're saving.
The next time the shared preferences were accessed however, the xml file was cleared and started new.
for example :
private static final class SharedPreferencesImpl implements SharedPreferences {
...
public String getString(String key, String defValue) {
synchronized (this) {
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
...
public final class EditorImpl implements Editor {
public Editor putString(String key, String value) {
synchronized (this) {
mModified.put(key, value);
return this;
}
}
...
}
}

Prevent PreferenceFragment from writing default preferences to SharedPreferences

Current behavior of PreferenceFragment: Upon first display on screen PreferenceFragment writes to associated SharedPreferences all default values defined in PreferenceScreen XML resource. I tested this couple times and PreferenceFragment as well as PreferenceActivity writes all preferences defaults to SharedPreferences when user opens Settings activity, even if he immediately close it without touching anything.
Problem: When in the next version of my app I decide to change some default user preferences, they will not apply to the devices where user at least once opened app preferences, because PreferenceFragment wrote all default values to SharedPreferences. I know that I can reapply new default values by overwriting all values in SharedPreferences, not only default, but user chosen too. But resetting user preferences in app update is completely unacceptable. So the problem is that we cant distinguish when some particular preference was set explicity by user or its just default preference written by PreferenceFragment upon first display on screen.
What I want: If the user explicity set some preference, whatever he has chosen, I should not touch this with my updated app defaults, even if user choice is coincides with my old default. But if user was not explicity chose preference I want that my new default preferences start working for him with app update.
So: How to prevent write of default preferences values by PreferenceFragment to associated SharedPreferences?
After studying sources I found a way to achieve requested behavior.
The only place where real write to SharedPreferences occurs it's bunch of persist[Type] methods in Preference class. And subclasses of Preference usually call persist[Type] method only in single internal method, that has similar structure across all subclasses. For example, method from TwoStatePreference, that is superclass of ChekBoxPreference and SwitchPreference:
public void setChecked(boolean checked) {
boolean changed = this.mChecked != checked;
if(changed || !this.mCheckedSet) {
this.mChecked = checked;
this.mCheckedSet = true;
this.persistBoolean(checked);
if(changed) {
this.notifyDependencyChange(this.shouldDisableDependents());
this.notifyChanged();
}
}
}
Next, setChecked method of TwoStatePreference is called in about five other methods, and two of these calls can produce default value to be committed to SharedPreferences. Here is first:
#Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
setChecked(restoreValue ? getPersistedBoolean(mChecked)
: (Boolean) defaultValue);
}
And second:
#Override
protected void onRestoreInstanceState(Parcelable state) {
if (state == null || !state.getClass().equals(SavedState.class)) {
// Didn't save state for us in onSaveInstanceState
super.onRestoreInstanceState(state);
return;
}
SavedState myState = (SavedState) state;
super.onRestoreInstanceState(myState.getSuperState());
setChecked(myState.checked);
}
And here is the solution, custom class, that subclasses SwitchPreferenceCompat and preventing commit in two above calls:
public class MySwitchPref extends SwitchPreferenceCompat
{
private boolean mAllowPersist;
#Override
protected boolean persistBoolean(boolean value) {
if (mAllowPersist) {
return super.persistBoolean(value);
}
return false;
}
#Override
protected void onSetInitialValue(boolean restoreValue,
Object defaultValue) {
mAllowPersist = false;
super.onSetInitialValue(restoreValue, defaultValue);
mAllowPersist = true;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
mAllowPersist = false;
super.onRestoreInstanceState(state);
mAllowPersist = true;
}
public MySwitchPref(Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public MySwitchPref(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public MySwitchPref(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MySwitchPref(Context context) {
super(context);
}
}
You should replace your SwitchPreferenceCompat declaration in xml PreferenceScreen to this subclass and all should work, I have tested it. And if you use other then SwitchPreference types of preferences, you as well should similarly subclass and override their behavior.
Beware: This solution relies upon internal realization of current, com.android.support:preference-v7:23.4.0 library. It might change with future releases, so if you use other library version, or use non support implementation you should look in to source, and ensure there is no other calls to persist values in SharedPreferences except that two, that I overriden. As well if you use other subclasses of Preference, not only SwitchPreference, you should check for other calls to persist values in SharedPreferences.

android new activity can't load singleton

I am trying to use a Singleton to share a large data object between Activities. But when I open the new Activity, the singleton comes up as empty. It seems to me that the Singleton should be the same no matter where in the Application I call if from.
It seems like the Scope of the Singleton is being limited to the individual Activity. Working around this is making my App very complicated. I must be doing something wrong. I even tried instantiating them in an extended Application class... Google says I should not have to use that though...
Can someone please point out where I am going wrong? i.e. Why does this singletom not contain the same data in each Activity?
I call it from an Activity with...
DataLog dataLog = DataLog.getInstance(this);
I have...
public class DataLog extends ArrayList<String> implements Serializable {
private static final long serialVersionUID = 0L;
private static DataLog sInstance;
private static Context mContext;
public static DataLog getInstance(Context context) {
mContext = context.getApplicationContext();
prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
if (sInstance == null) {
sInstance = new DataLog();
}
return sInstance;
}
private DataLog() {
}
public boolean add(String entry) {
super.add(entry);
return true;
}
public void add(int index, String entry) {
if (index > 0)
super.add(index, entry);
else
super.add(entry);
}
public void clear() {
super.clear();
}
...
}
Its highly advisable to avoid singleton for sharing large data sets in android.
Singletons are used for short life-cycle objects.
Switch to SharedPrefferences, SQLite DB's or file storing. You are not the only to have experienced this behavior, and the reason lies in the nature of android Activities and the system itself(managing activities and its data).
Here is an example why singleton is bad for your case:
You stored important data in it. The user knows that he can close the app on home button to call someone or whatever)maybe someone called him when he was in your app), and that when he opens your app he will come back at the same place with everything in order. (this is expected behavior from users and android apps). The system can easily kill your process and all static variables in it for memory maintenance, app inactivity etc...result=data lost. Thus its not safe to use it.

Store Objects in ApplicationContext

When my application goes to background , my (static and singleton) objects are cleared.
So I tried to store these objects in Applicaton Context . I am using the following code.
Accounts.create(getApplicationContext()) will be called once to store the accounts instance.
Is that possible(reliable) to store objects in Application Context ? I am not sure the following way is correct or not . please guide ..
public class Init extends Application {
private Hashtable<Object, Object> globalStore = new Hashtable<Object, Object>();
public void putToGlobalStore(Object key, Object value) {
globalStore.put(key, value);
}
public Object takeFromGlobalStore(Object key) {
return this.globalStore.get(key);
}
public void removeFromGlobalStore(Object key) {
this.globalStore.remove(key);
}
public boolean containsInGlobalStore(Object key) {
return this.globalStore.containsKey(key);
}
}
public class Accounts {
protected Accounts(String name, Context context) {
Init init = (Init) applicationContext;
init.putToGlobalStore(name, this);
}
private static Init applicationContext;
public static void create(Context context) {
if (context instanceof Application)
applicationContext = (Init) context;
else
applicationContext = (Init) context.getApplicationContext();
if (applicationContext.containsInGlobalStore(GLOBAL_NAME))
Logger.log("Warning " + GLOBAL_NAME
+ " is already created. This will remove all old datas");
new Accounts(GLOBAL_NAME, applicationContext);
}
private static final String GLOBAL_NAME = "accounts";
public static Accounts getInstance() {
try {
return (Accounts) applicationContext
.takeFromGlobalStore(GLOBAL_NAME);
} catch (Exception e) {
Logger.log("GLOBAL_NAME Lost");
return null;
}
}
Please help.
You should know that the application context itself gets destroyed if left unused for a long time in the background. So there is no guarantee that your static and singleton objects will not be cleared when the app is in background. Instead what you can do is persist your objects from time to time (either in a flat-file or shared preference or database) and restore them in the onCreate method of the Application class
I have been using this method in my application and i didn't see any problem unless my process gets killed by the OS or if there is a crash in my application and my app gets restarted.
If you think whatever data you are storing is valid for only life time of a program why don't you override OnCreate of Application object and create all your singletons there. This way you can always make sure your application has all singletons before your app starts functioning.
Application class is not permanent.
If App process killed, Application class private member variable data loss.
Using Shared Preferences.
I know this question was asked a long time ago, but here's a good article that suggests using the Application object to store data is generally not a sound design methodology.

Categories

Resources