android multiprocessing sharedpreferences

android - MODE_MULTI_PROCESS para SharedPreferences no funciona



multiprocessing (6)

Tengo un SyncAdapter ejecuta en su propio proceso por separado del proceso principal de la aplicación.

Estoy usando una clase de envoltura estática alrededor de mis SharedPreferences que crea un objeto estático en la carga del proceso ( onCreate la onCreate ) de esta manera:

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

La envoltura tiene métodos get y set, así:

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

Tanto SyncAdapter como la aplicación usan esta clase de envoltura para editar y obtener de las preferencias, esto funciona a veces, pero muchas veces veo que SyncAdapter está perdiendo preferencias en los accesos a las preferencias, mientras que la aplicación principal ve los cambios recientes correctamente.

De acuerdo con los documentos, creo que el indicador MODE_MULTI_PROCESS debería funcionar como espero, permitiendo que ambos procesos vean los últimos cambios, pero no funciona.

Actualizar:

Según la sugerencia de x90 , he intentado abstenerme de usar un objeto SharedPreferences estático y en su lugar llamar a getSharedPreferences en cada método get / set. Esto causó un nuevo problema, donde el archivo de preferencias se elimina (!!!) en el acceso simultáneo de varios procesos. Es decir, veo en el logcat:

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

y desde ese momento se eliminaron todas las preferencias guardadas en el objeto SharedPreferences .

Este es probablemente el resultado de otra advertencia que veo en el registro:

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)

PD: este no es un problema determinista, vi los registros anteriores después de que se produjo un bloqueo, pero aún no se podía recrear en el mismo dispositivo, y hasta ahora no parecía ocurrir en otros dispositivos.

OTRA ACTUALIZACIÓN:

He presentado un informe de error sobre esto, después de escribir un pequeño método de prueba para confirmar que se trata de un problema de Android, coméntelo en https://code.google.com/p/android/issues/detail?id=66625


Acabo de encontrarme con el mismo problema. Cambié mi aplicación para ejecutar el servicio en un proceso separado y me di cuenta de que SharedPreferences estaba roto.

Dos cosas:

1) ¿Está utilizando Editor.apply() o .commit() ? Estaba usando .apply() . Comencé a revisar mi archivo de preferencias ya sea después de que la actividad o el servicio le hiciera cambios y me di cuenta de que cada vez que uno haría un cambio, crearía un nuevo archivo con solo el valor recién cambiado. IE, un valor escrito de la actividad se borraría cuando un nuevo valor se escribiera / cambiara desde el servicio y viceversa. .commit() a .commit() todas partes y ¡este ya no es el caso! De la documentación: "Tenga en cuenta que cuando dos editores modifican las preferencias al mismo tiempo, el último en llamar aplica gana.

2) SharedPreferencesListener no parece funcionar en todos los procesos, incluso después de cambiar a .commit() . Tendrá que usar los Manejadores de mensajes o los Intentos de transmisión para notificar un cambio. Cuando mira la documentación de la clase SharedPreferences, incluso dice "Nota: actualmente esta clase no admite el uso en múltiples procesos. Esto se agregará más adelante". http://developer.android.com/reference/android/content/SharedPreferences.html

En este sentido, tenemos suerte de que tengamos el indicador MODE_MULTI_PROCESS trabajando para leer / escribir desde las mismas SharedPreferences en diferentes procesos.


Debido a que MODE_MULTI_PROCESS no es compatible actualmente, no he encontrado ninguna forma de trabajar con Preferencias Compartidas entre procesos que no sean los de trabajar alrededor de él.

Sé que la gente está compartiendo las bibliotecas que escribieron para abordar esto, pero en realidad usé una biblioteca de terceros que encontré en otro hilo que implementa SQLLite en lugar de las Preferencias Compartidas:

https://github.com/hamsterready/dbpreferences

Sin embargo, lo que era importante para mí que no encontré abordado en otras soluciones era mantener la generación de IU automática ya integrada en Fragmento de Preferencias: mejor poder especificar sus elementos en XML y llamar a addPreferencesFromResource (R.xml.preferences) que Tienes que construir tu interfaz de usuario desde cero.

Por lo tanto, para hacer que esto funcione, subclasificé cada uno de los elementos de Preferencia que necesitaba (en mi caso simplemente Preference, SwitchPreference y EditTextPreference), y sobrescribí algunos métodos de las clases básicas para incluir el guardado en una instancia de DatabaseSharedPreferences tomado de lo anterior. biblioteca.

Por ejemplo, debajo de I subclase EditTextPreference y obtengo la clave de preferencia de la clase base. Luego anulo los métodos persist y getPersisted en la clase base de Preferencia. Luego anulo onSetInitialValue, setText y getText en la clase base EditText.

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

Luego, simplemente especifique el nuevo elemento en su archivo preferences.xml, ¡y listo! ¡Ahora obtiene la interoperabilidad de procesos de SQLLite y la autogeneración UI de 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"/>


Le di una mirada muy rápida al código de Google y aparentemente Context.MODE_MULTI_PROCESS no es una forma real de garantizar la seguridad del proceso de SharedPreferences.

SharedPreferences en sí no es seguro para el proceso. (Es probable que esa sea la razón por la que la documentación de SharedPreferences dice que "actualmente esta clase no admite el uso en varios procesos. Esto se agregará más adelante").

MODE_MULTI_PROCESS simplemente funciona en conjunto con cada Context.getSharedPreferences(String name, int mode) : cuando recuperas una instancia de SharedPreferences que especifica el MODE_MULTI_PROCESS , Android volverá a cargar el archivo de preferencias para que esté actualizado con cualquier modificación concurrente (eventual) que haya ocurrido. lo. Si luego mantiene esa instancia como miembro de clase (estática o no), el archivo de preferencias no se volverá a cargar.

El uso de Context.getSharedPreferences(...) cada vez que desee escribir o leer en las preferencias tampoco es seguro para el proceso, pero creo que es probablemente lo más cerca que puede llegar a él en este momento.

Si realmente no necesita leer la misma preferencia de los diferentes procesos, entonces una solución alternativa podría ser usar diferentes archivos de preferencias para los diferentes procesos.


MODE_MULTI_PROCESS fue obsoleto en el nivel de API 23. Puede resolver este problema con ContentProvider. DPreference utiliza un recurso compartido de contenedor ContentProvider. Tiene un mejor rendimiento que el uso de sqlite implementado. https://github.com/DozenWang/DPreference


MODE_MULTI_PROCESS para SharedPreferences se deprecia ahora (Android M -API nivel 23-en adelante). No fue seguro para el proceso.


Tenía exactamente el mismo problema y mi solución era escribir un reemplazo basado en ContentProvider para las SharedPreferences. Funciona 100% multiproceso.

Lo hice una biblioteca para todos nosotros. Aquí está el resultado: https://github.com/grandcentrix/tray