web-dev-qa-db-fra.com

MODE_MULTI_PROCESS pour SharedPreferences ne fonctionne pas

J'ai un SyncAdapter fonctionnant sur son propre processus séparément du processus de l'application principale.

J'utilise une classe wrapper statique autour de mon SharedPreferences qui crée un objet statique lors du chargement du processus (onCreate de l'application) comme ceci:

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

L'encapsuleur a des méthodes get et set, comme ceci:

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

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

SyncAdapter et l'application utilisent cette classe wrapper pour modifier et obtenir à partir des préférences, cela fonctionne parfois mais je vois souvent le SyncAdapter obtenir des préférences anciennes/manquantes sur les accès aux préférences, tandis que l'application principale voit correctement les changements récents.

Selon les documents, je pense que le MODE_MULTI_PROCESS L'indicateur devrait fonctionner comme je m'y attendais, permettant aux deux processus de voir les dernières modifications, mais cela ne fonctionne pas.

Mise à jour:

Par x90, j'ai essayé de m'abstenir d'utiliser un objet statique SharedPreferences et d'appeler à la place getSharedPreferences sur chaque méthode get/set. Cela a provoqué un nouveau problème, où le fichier prefs est supprimé (!!!) sur l'accès simultané multi-processus. c'est-à-dire que je vois dans le logcat:

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

et à partir de là, toutes les préférences enregistrées sur l'objet SharedPreferences ont été supprimées.

C'est probablement le résultat d'un autre avertissement que je vois dans le journal:

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 ce n'est pas un problème déterministe, j'ai vu les journaux ci-dessus après un crash, mais je ne pouvais pas encore les recréer sur le même appareil, et jusqu'à présent, cela ne semblait pas se produire sur d'autres appareils.

UNE AUTRE MISE À JOUR:

J'ai déposé un rapport de bogue à ce sujet, après avoir écrit une petite méthode de test pour confirmer qu'il s'agit bien d'un problème Android, regardez-le https://code.google.com/p/Android/issues/detail? id = 66625

30
marmor

J'ai eu exactement le même problème et ma solution a été d'écrire un remplacement basé sur ContentProvider pour les SharedPreferences. Il fonctionne à 100% multiprocessus.

J'en ai fait une bibliothèque pour nous tous. Voici le résultat: https://github.com/grandcentrix/tray

16
passsy

J'ai donné un aperçu très rapide du code de Google et, apparemment, Context.MODE_MULTI_PROCESS N'est pas un moyen réel d'assurer la sécurité des processus de SharedPreferences.

SharedPreferences lui-même n'est pas sécurisé pour les processus. (C'est probablement la raison pour laquelle la documentation SharedPreferences indique "actuellement, cette classe ne prend pas en charge l'utilisation sur plusieurs processus. Cela sera ajouté plus tard.")

MODE_MULTI_PROCESS Fonctionne uniquement avec chaque Context.getSharedPreferences(String name, int mode) appel: lorsque vous récupérez une instance de SharedPreferences en spécifiant l'indicateur MODE_MULTI_PROCESS Android rechargera le fichier de préférences pour être à jour avec toute modification simultanée (éventuelle) qui s’est produite. Si vous conservez ensuite cette instance en tant que membre de classe (statique ou non), le fichier de préférences ne sera pas rechargé à nouveau.

Utiliser Context.getSharedPreferences(...) chaque fois que vous voulez écrire ou lire dans les préférences n'est pas non plus sûr pour le processus, mais je suppose que c'est probablement le plus proche que vous pouvez y accéder pour le moment.

Si vous n'avez pas réellement besoin de lire la même préférence dans les différents processus, une solution de contournement pourrait être d'utiliser des fichiers de préférences différents pour les différents processus.

26
Soloist

Je viens de rencontrer le même problème. J'ai changé mon application pour exécuter le service dans un processus distinct et j'ai réalisé que sharedPreferences était tout cassé.

Deux choses:

1) Utilisez-vous Editor.apply() ou .commit()? J'utilisais .apply(). J'ai commencé à vérifier mon fichier de préférences soit après l'activité, soit après que le service y ait apporté des modifications et j'ai réalisé que chaque fois que l'on apportait une modification, il créerait un nouveau fichier avec uniquement la valeur nouvellement modifiée. C'est-à-dire qu'une valeur écrite de l'activité serait effacée lorsqu'une nouvelle valeur a été écrite/modifiée à partir du service et vice versa. Je suis passé partout à .commit() et ce n'est plus le cas! De la documentation: "Notez que lorsque deux les éditeurs modifient les préférences en même temps, le dernier à appeler s'applique gagne.

2) SharedPreferencesListener ne semble pas fonctionner sur tous les processus même après être passé à .commit(). Vous devrez utiliser Messenger Handlers ou Broadcast Intents pour notifier un changement. Lorsque vous consultez la documentation de la classe SharedPreferences, elle indique même "Remarque: actuellement, cette classe ne prend pas en charge l'utilisation sur plusieurs processus. Cela sera ajouté plus tard."http://developer.Android.com/reference/Android/content/SharedPreferences.html

À cet égard, nous avons de la chance d'avoir même le MODE_MULTI_PROCESS indicateur fonctionnant pour lire/écrire à partir du même SharedPreferences sur différents processus.

6
Flyview

MODE_MULTI_PROCESS pour SharedPreferences est désormais déprécié (Android M -API niveau 23 et suivants). Il n'était pas sûr du processus.

2
Flying Monkey

MODE_MULTI_PROCESS a été déconseillé au niveau de l'API 23. Vous pouvez résoudre ce problème avec ContentProvider. DPreference utilise une préférence de partage d'encapsulation ContentProvider. Il a de meilleures performances que l'utilisation de sqlite implémenté. https://github.com/DozenWang/DPreference

2
DozenWang

Parce que MODE_MULTI_PROCESS n'est pas actuellement pris en charge, je n'ai trouvé aucun moyen de travailler avec des préférences partagées entre des processus autres que de le contourner.

Je sais que les gens partagent les bibliothèques qu'ils ont écrites pour résoudre ce problème, mais j'ai en fait utilisé une bibliothèque tierce que j'ai trouvée sur n autre fil qui implémente SQLLite au lieu des préférences partagées:

https://github.com/hamsterready/dbpreferences

Cependant, ce qui était important pour moi que je n'ai pas trouvé de solution dans d'autres solutions était de maintenir la génération d'interface utilisateur automatique déjà intégrée dans le fragment de préférence - mieux pour pouvoir spécifier vos éléments en XML et appeler addPreferencesFromResource (R.xml.preferences) que devez construire votre interface utilisateur à partir de zéro.

Donc, pour que cela fonctionne, j'ai sous-classé chacun des éléments de préférence dont j'avais besoin (dans mon cas, juste Preference, SwitchPreference et EditTextPreference), et j'ai remplacé quelques méthodes des classes de base pour inclure l'enregistrement dans une instance de DatabaseSharedPreferences tirée de ce qui précède. bibliothèque.

Par exemple, ci-dessous, je sous-classe EditTextPreference et j'obtiens la clé de préférence de la classe de base. J'ai ensuite outrepassé les méthodes persist et getPersisted dans la classe de base Preference. Je remplace ensuite onSetInitialValue, setText et getText dans la classe de 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;
}

Ensuite, vous spécifiez simplement le nouvel élément dans votre fichier preferences.xml, et le tour est joué! Vous bénéficiez désormais de l'interopérabilité des processus de SQLLite et de la génération automatique de l'interface utilisateur 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"/>
1
DicreetAndDiscrete