是否应该在 UI 线程上访问 SharedPreferences?

随着 Gingerbread 的发布,我一直在尝试一些新的 API,其中之一就是 StrictMode

我注意到其中一个警告是针对 getSharedPreferences()的。

这是一个警告:

StrictMode policy violation; ~duration=1949 ms: android.os.StrictMode$StrictModeDiskReadViolation: policy=23 violation=2

并且它是为在 UI 线程上进行的 getSharedPreferences()调用而提供的。

SharedPreferences访问和更改真的应该从 UI 线程中进行吗?

41930 次浏览

访问共享首选项可能需要相当长的时间,因为它们是从闪存中读取的。你经常读书吗?也许您可以使用不同的格式,例如 SQLite 数据库。

但是不要使用 StrictMode 修复您发现的所有问题,或者引用文档:

但是不要强迫自己去修复 StrictMode 找到的所有东西。特别是,在正常活动生命周期中,许多磁盘访问的情况通常是必要的。使用 StrictMode 查找您偶然做的事情。但是,UI 线程上的网络请求几乎总是一个问题。

我很高兴你已经开始玩了!

需要注意的事项: (懒散的子弹形式)

  • 如果这是你最糟糕的问题,你的应用程序可能是在一个很好的位置。:)写操作通常比读操作慢,所以请确保您使用的是 SharedPreferences $Editor.application ()而不是 commit ()。Application ()在 GB 和异步中是新的(但总是安全的,要小心生命周期转换)。可以使用反射在 GB + 上有条件地调用 application () ,在 Froyo 或更低版本上调用 commit ()。我将用示例代码写一篇博客文章介绍如何做到这一点。

至于装货..。

  • 一旦加载,SharedPreferences 就是单例的,并且是进程范围内的缓存。所以你需要尽早加载它,这样在你需要它之前,它就在你的内存中了。(假设它很小,如果您使用的是 SharedPreferences,一个简单的 XML 文件,那么它就应该很小... ...)您不希望在将来某些用户单击按钮时出现错误。

  • 但是无论何时调用 context.getSharedPreferences (...) ,支持的 XML 文件都会被统计以查看它是否发生了更改,所以无论如何,在 UI 事件期间都要避免这些统计。一个属性通常应该是快速的(而且经常是缓存的) ,但 yaffs 并没有太多的并发性(很多 Android 设备都运行在 yaffs 上... Droid,Nexus One 等等) ,所以如果你避免磁盘,你就不会被其他正在运行的或待处理的磁盘操作卡住。

  • 因此,您可能希望在 onCreate ()期间加载 SharedPreferences 并重用相同的实例,从而避免 stat。

  • 但是如果在 onCreate ()期间你不需要你的首选项,那么加载时间就会不必要地延迟你的应用程序的启动,所以通常最好有一个类似 FutureTask < SharedPreferences > 的子类,它会启动一个新的线程。Set () FutureTask 子类的值。然后随时查找 FutureTask < SharedPreferences > 的成员。抓住它。我计划在蜂巢的幕后使这个免费,透明。我将尝试发布一些示例代码 显示了该领域的最佳实践

查看 Android Developers 博客,了解接下来几周 StrictMode 相关主题的即将发布的帖子。

关于 Brad 的回答有一个微妙之处: 即使你在 onCreate ()中加载了 SharedPreferences,你可能仍然应该读取后台线程上的值,因为 getString ()等阻塞,直到读取完成中的共享文件首选项(在后台线程上) :

public String getString(String key, String defValue) {
synchronized (this) {
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}

Edit ()也以相同的方式阻塞,尽管 application ()在前台线程上似乎是安全的。

(顺便说一句,很抱歉把这个放在这里。我本想把这个作为对布拉德回答的评论,但我刚刚加入,没有足够的名声这样做。)

我知道这是一个老问题,但我想分享我的方法。我有很长的阅读时间,使用了共享偏好和全球应用程序类的组合:

申请类别:

public class ApplicationClass extends Application {


private LocalPreference.Filter filter;


public LocalPreference.Filter getFilter() {
return filter;
}


public void setFilter(LocalPreference.Filter filter) {
this.filter = filter;
}
}

本地选择:

public class LocalPreference {


public static void saveLocalPreferences(Activity activity, int maxDistance, int minAge,
int maxAge, boolean showMale, boolean showFemale) {


Filter filter = new Filter();
filter.setMaxDistance(maxDistance);
filter.setMinAge(minAge);
filter.setMaxAge(maxAge);
filter.setShowMale(showMale);
filter.setShowFemale(showFemale);


BabysitApplication babysitApplication = (BabysitApplication) activity.getApplication();
babysitApplication.setFilter(filter);


SecurePreferences securePreferences = new SecurePreferences(activity.getApplicationContext());
securePreferences.edit().putInt(Preference.FILER_MAX_DISTANCE.toString(), maxDistance).apply();
securePreferences.edit().putInt(Preference.FILER_MIN_AGE.toString(), minAge).apply();
securePreferences.edit().putInt(Preference.FILER_MAX_AGE.toString(), maxAge).apply();
securePreferences.edit().putBoolean(Preference.FILER_SHOW_MALE.toString(), showMale).apply();
securePreferences.edit().putBoolean(Preference.FILER_SHOW_FEMALE.toString(), showFemale).apply();
}


public static Filter getLocalPreferences(Activity activity) {


BabysitApplication babysitApplication = (BabysitApplication) activity.getApplication();
Filter applicationFilter = babysitApplication.getFilter();


if (applicationFilter != null) {
return applicationFilter;
} else {
Filter filter = new Filter();
SecurePreferences securePreferences = new SecurePreferences(activity.getApplicationContext());
filter.setMaxDistance(securePreferences.getInt(Preference.FILER_MAX_DISTANCE.toString(), 20));
filter.setMinAge(securePreferences.getInt(Preference.FILER_MIN_AGE.toString(), 15));
filter.setMaxAge(securePreferences.getInt(Preference.FILER_MAX_AGE.toString(), 50));
filter.setShowMale(securePreferences.getBoolean(Preference.FILER_SHOW_MALE.toString(), true));
filter.setShowFemale(securePreferences.getBoolean(Preference.FILER_SHOW_FEMALE.toString(), true));
babysitApplication.setFilter(filter);
return filter;
}
}


public static class Filter {
private int maxDistance;
private int minAge;
private int maxAge;
private boolean showMale;
private boolean showFemale;


public int getMaxDistance() {
return maxDistance;
}


public void setMaxDistance(int maxDistance) {
this.maxDistance = maxDistance;
}


public int getMinAge() {
return minAge;
}


public void setMinAge(int minAge) {
this.minAge = minAge;
}


public int getMaxAge() {
return maxAge;
}


public void setMaxAge(int maxAge) {
this.maxAge = maxAge;
}


public boolean isShowMale() {
return showMale;
}


public void setShowMale(boolean showMale) {
this.showMale = showMale;
}


public boolean isShowFemale() {
return showFemale;
}


public void setShowFemale(boolean showFemale) {
this.showFemale = showFemale;
}
}


}

MainActivity (应用程序中首先调用的活动) :

LocalPreference.getLocalPreferences(this);

解释步骤:

  1. 主活动调用 getLocalPreferences (this)-> this 将读取您的首选项,设置应用程序类中的 filter 对象并返回它。
  2. 当您在应用程序的其他地方再次调用 getLocalPreferences ()函数时,它首先检查应用程序类中是否没有可用的函数,这样会快得多。

注意: 始终检查应用程序范围的变量是否与 NULL 不同,原因-> http://www.developerphil.com/dont-store-data-in-the-application-object/

应用程序对象不会永远停留在内存中,它将被终止。与流行的看法相反,这个应用程序不会从头开始重新启动。Android 将创建一个新的 Application 对象,并在用户之前所在的位置启动活动,从而给人一种应用程序从未被杀死的错觉。

如果我没有检查 null,我会允许在调用 filter 对象时抛出一个 null 指针(如果应用程序对象被 Android 从内存中刷新的话)

SharedPreferences 类在磁盘上的 XML 文件中执行一些读写操作,所以就像其他 IO 操作一样,它可能会被阻塞。当前存储在 SharedPreferences 中的数据量会影响 API 调用所消耗的时间和资源。对于最小数量的数据,获取/输入数据只需要几毫秒(有时甚至不到一毫秒)。但是从专家的角度来看,通过在后台执行 API 调用来提高性能可能非常重要。对于异步 SharedPreferences,我建议查看 基准库。

我看不出有任何理由从后台线索阅读它们。但是写出来我会。在启动时,共享首选项文件被加载到内存中,因此访问速度很快,但是写操作可能需要一些时间,因此我们可以使用异步写操作。这应该是共享首选项的提交方法和应用方法之间的区别。