web-dev-qa-db-ja.com

SharedPreferencesのMODE_MULTI_PROCESSが機能しない

メインのアプリプロセスとは別に独自のプロセスで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とアプリの両方がこのラッパークラスを使用して設定を編集および取得します。これは時々機能しますが、多くの場合、設定へのアクセス時にSyncAdapterが古い/不足している設定を取得します。メインアプリは最近の変更を正しく認識します。

ドキュメントによると、私はMODE_MULTI_PROCESSフラグは期待どおりに機能し、両方のプロセスが最新の変更を確認できるようにしますが、機能しません。

更新:

x90の提案です。静的なSharedPreferencesオブジェクトの使用を控え、代わりに各get/setメソッドでgetSharedPreferencesを呼び出すようにしました。これにより、マルチプロセスの同時アクセスで設定ファイルが削除される(!!!)という新しい問題が発生しました。すなわち、私はlogcatで見ます:

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

その時点から、SharedPreferencesオブジェクトに保存されたすべての設定が削除されました。

これはおそらく、ログに表示される別の警告の結果です。

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

30
marmor

まったく同じ問題があり、私の解決策は、SharedPreferencesの代わりにContentProviderベースの置き換えを作成することでした。 100%マルチプロセスで動作します。

みんなのための図書館にした。これが結果です: https://github.com/grandcentrix/tray

16
passsy

私はGoogleのコードをざっと見ましたが、明らかにContext.MODE_MULTI_PROCESSはSharedPreferencesのプロセスの安全性を保証する実際の方法ではありません。

SharedPreferences自体はプロセスセーフではありません。 (それが、SharedPreferencesのドキュメントで「現在、このクラスは複数のプロセスでの使用をサポートしていないため、後で追加される予定です」と言っているのはこのためです。)

MODE_MULTI_PROCESSは、すべてのContext.getSharedPreferences(String name, int mode)呼び出しと連動して機能します。MODE_MULTI_PROCESSフラグを指定してSharedPreferencesのインスタンスを取得すると、Androidは、設定ファイルを再読み込みして、(最終的な)同時実行で最新の状態にしますそれに発生した変更。その後、そのインスタンスをクラス(静的または非静的)メンバーとして保持すると、設定ファイルは再度読み込まれません。

プリファレンスに書き込んだり読み込んだりするたびにContext.getSharedPreferences(...)を使用することもプロセスセーフではありませんが、おそらく現時点で最も近いと思われます。

異なるプロセスから同じ設定を実際に読み取る必要がない場合、回避策は、異なるプロセスに異なる設定ファイルを使用することです。

26
Soloist

私は同じ問題に出くわしました。別のプロセスでサービスを実行するようにアプリを切り替えましたが、sharedPreferencesがすべて壊れていることに気付きました。

2つのこと:

1)Editor.apply()または.commit()を使用していますか? .apply()を使用していました。アクティビティまたはサービスが設定ファイルを変更した後、設定ファイルのチェックを開始しました。変更を行うと、新しく変更された値のみを含む新しいファイルが作成されることに気付きました。つまり、アクティビティから書き込まれた値は、サービスから新しい値が書き込まれた/変更されたときに消去され、その逆も同様です。 私はどこでも.commit()に切り替えましたが、これはもう当てはまりません!ドキュメントから: "注2人の編集者が同時に設定を変更している場合、最後にapplyを呼び出した方が優先されます。

2)SharedPreferencesListenerは、.commit()に切り替えた後でも、プロセス間で機能しないようです。変更を通知するには、メッセンジャーハンドラーまたはブロードキャストインテントを使用する必要があります。 SharedPreferencesクラスのドキュメントを見ると、とさえ書かれています "注:現在、このクラスは複数のプロセスにわたる使用をサポートしていません。これは後で追加されます。"http://developer.Android.com/reference/Android/content/SharedPreferences.html

その意味で、MODE_MULTI_PROCESSフラグは、異なるプロセス間で同じSharedPreferencesからの読み取り/書き込みに機能します。

6
Flyview

SharedPreferencesのMODE_MULTI_PROCESSが廃止されました(Android M -APIレベル23以降)。プロセスは安全ではありませんでした。

2
Flying Monkey

MODE_MULTI_PROCESSはAPIレベル23で廃止されました。この問題はContentProviderで解決できます。 DPreferenceは、ContentProviderラッパーsharepreferenceを使用します。 sqliteを使用するよりもパフォーマンスが優れています。 https://github.com/DozenWang/DPreference

2
DozenWang

MODE_MULTI_PROCESSは現在サポートされていないため、回避する以外にプロセス間で共有設定を操作する方法はありません。

私は人々がこれに対処するために書いたライブラリを共有していることを知っていますが、私は実際に共有設定の代わりにSQLLiteを実装する another thread で見つけたサードパーティライブラリを使用しました:

https://github.com/hamsterready/dbpreferences

ただし、他のソリューションで対処されていないことが私にとって重要だったのは、設定フラグメントに既に組み込まれている自動UI生成を維持することでした-XMLで要素を指定してaddPreferencesFromResource(R.xml.preferences)を呼び出すことができる方が良い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"/>
1