【问题标题】:MODE_MULTI_PROCESS for SharedPreferences isn't workingSharedPreferences 的 MODE_MULTI_PROCESS 不起作用
【发布时间】:2014-03-02 15:46:16
【问题描述】:

我有一个 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。 这导致了一个新问题,即 prefs 文件在多进程同时访问时被删除 (!!!)。 即我在 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)

P.S 这不是一个确定性问题,我在发生崩溃后看到了上面的日志,但还不能在同一设备上重新创建,直到现在它似乎还没有在其他设备上发生。

另一个更新:

我已经对此提交了一个错误报告,在编写了一个小测试方法以确认这确实是一个 Android 问题后,请在 https://code.google.com/p/android/issues/detail?id=66625 上标记它

【问题讨论】:

  • 您是否尝试过按需获取首选项对象(每次您想使用首选项时),而不是始终在 Application 类中保持对 SharedPreferences 对象的引用?
  • 我想过,但是这对整个应用来说会是一个非常大的改变,并且由于代码的大小,目前不是一个选项。我没有在文档中看到任何关于拥有静态首选项对象可能会阻止 MULTI_PROCESS 正常工作的内容......您可能认为这是罪魁祸首的任何理由?
  • 我期待(不确定)根本原因可能是 java 线程层。这个对象可以缓存在不同状态的线程(进程)中,并给出不同的结果。无论如何,我认为您可以尝试使用 sharedpreferences 更改您的工作流程(以我之前描述的方式)。
  • 如果它是一个解决方案 - 您可以创建 api 来处理需要上下文的共享首选项。它可能不太有用(如果您想使用没有上下文的对象的首选项),但我建议您以这种方式查看。

标签: android multiprocessing sharedpreferences


【解决方案1】:

我快速浏览了 Google 的代码,显然 Context.MODE_MULTI_PROCESS 并不是确保 SharedPreferences 进程安全的实际方法。

SharedPreferences 本身不是进程安全的。 (这可能就是为什么 SharedPreferences 文档说“目前这个类不支持跨多个进程使用。这将在以后添加。”)

MODE_MULTI_PROCESS 只与每个Context.getSharedPreferences(String name, int mode) 调用一起工作:当您检索指定MODE_MULTI_PROCESS 标志的SharedPreferences 实例时,android 将重新加载首选项文件以与发生的任何(最终)并发修改保持同步给它。如果您随后将该实例保留为类(静态或非静态)成员,则不会再次重新加载首选项文件。

每次您想写入或读取首选项时使用Context.getSharedPreferences(...) 也不是过程安全的,但我想它可能是您目前最接近它的方式。

如果您实际上不需要从不同的进程中读取相同的首选项,那么解决方法可能是为不同的进程使用不同的首选项文件。

【讨论】:

【解决方案2】:

遇到了完全相同的问题,我的解决方案是为 SharedPreferences 编写基于 ContentProvider 的替换。它可以 100% 多进程运行。

我把它变成了我们所有人的图书馆。结果如下: https://github.com/grandcentrix/tray

【讨论】:

【解决方案3】:

我刚刚遇到了同样的问题。我将我的应用程序切换为在单独的进程中运行该服务,并意识到 sharedPreferences 已全部损坏。

两件事:

1) 您使用的是Editor.apply() 还是.commit()?我正在使用.apply()。在活动或服务对其进行更改后,我开始检查我的首选项文件,并意识到无论何时进行更改,它都会创建一个仅包含新更改值的新文件。即,当从服务写入/更改新值时,从活动写入的值将被删除,反之亦然。 我到处都切换到.commit(),现在情况不再如此!来自文档:“请注意,当两个编辑器同时修改首选项时,最后一个调用 apply 获胜.

2) 即使切换到.commit()SharedPreferencesListener 似乎也无法跨进程工作。您必须使用 Messenger 处理程序或广播意图来通知更改。当您查看 SharedPreferences 类的文档时,它甚至会说 “注意:目前此类不支持跨多个进程使用。稍后将添加。” http://developer.android.com/reference/android/content/SharedPreferences.html

在这方面,我们很幸运,我们甚至拥有 MODE_MULTI_PROCESS 标志,可以跨不同进程从同一个 SharedPreferences 读取/写入。

【讨论】:

  • 不,我只使用了“commit”,正如我所说的,这可能工作正常,但几天后经常使用我的 2 进程应用程序,并且首选项会被重置。所以我写了上面的循环来检查它,它表明问题出现在广泛的写作和阅读的一两分钟内。您可以尝试从您的服务和您的主应用程序中运行一个随机数的读取和写入循环,看看您是否可以重新创建我的发现?
  • objPreferences.contains(key); 这在服务中返回 false,即使数据被优先存储。如何克服这个问题?
【解决方案4】:

SharedPreferences 的 MODE_MULTI_PROCESS 现在已弃用(android M -API 级别 23 起)。它不是进程安全的。

【讨论】:

    【解决方案5】:

    MODE_MULTI_PROCESS 在 API 级别 23 中已弃用。您可以使用 ContentProvider 解决此问题。 DPreference 使用 ContentProvider 包装 sharepreference。它比使用 sqlite 实现的性能更好。 https://github.com/DozenWang/DPreference

    【讨论】:

      【解决方案6】:

      由于当前不支持 MODE_MULTI_PROCESS,除了解决它之外,我还没有找到任何方法来处理进程之间的共享首选项。

      我知道人们正在共享他们为解决此问题而编写的库,但我实际上使用了我在 another thread 上找到的第三方库,它实现了 SQLLite 来代替共享首选项:

      https://github.com/hamsterready/dbpreferences

      但是,对我来说重要的是我在其他解决方案中没有找到解决方案,即维护已内置在 Preference Fragment 中的自动 UI 生成 - 最好能够在 XML 中指定您的元素并调用 addPreferencesFromResource(R.xml.偏好)而不是必须从头开始构建您的 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"/>
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-12-03
        • 2016-10-11
        • 1970-01-01
        • 2015-02-09
        • 2012-01-07
        • 2014-02-13
        • 1970-01-01
        相关资源
        最近更新 更多