126

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

我注意到其中一个警告是 for getSharedPreferences()

这是警告:

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

它是为getSharedPreferences()在 UI 线程上进行的调用而给出的。

真的应该SharedPreferences在 UI 线程之外进行访问和更改吗?

4

6 回答 6

190

我很高兴你已经在玩它了!

需要注意的一些事情:( 以惰性子弹形式)

  • 如果这是您遇到的最严重的问题,那么您的应用程序可能处于一个好位置。:) 但是,写入通常比读取慢,因此请确保您使用的是 SharedPreferenced$Editor.apply() 而不是 commit()。apply() 在 GB 和 async 中是新的(但总是安全的,小心生命周期转换)。您可以使用反射有条件地调用 GB+ 上的 apply() 和 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> 的成员并 .get() 即可。我计划在 Honeycomb 的幕后透明地免费提供这个功能。我将尝试发布一些示例代码,展示该领域的最佳实践。

查看 Android 开发者博客,了解下周即将发布的有关 StrictMode 相关主题的帖子。

于 2010-12-06T23:07:54.387 回答
5

关于 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() 也以相同的方式阻塞,尽管 apply() 在前台线程上似乎是安全的。

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

于 2014-11-11T01:40:17.287 回答
5

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

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

但不要觉得有必要修复 StrictMode 找到的所有内容。特别是,在正常的活动生命周期中,许多磁盘访问情况通常是必要的。使用 StrictMode 查找您不小心做的事情。不过,UI 线程上的网络请求几乎总是一个问题。

于 2010-12-06T21:54:39.000 回答
1

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

应用类:

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) -> 这将读取您的首选项,在您的应用程序类中设置过滤器对象并返回它。
  2. 当您在应用程序的其他地方再次调用 getLocalPreferences() 函数时,它首先检查它是否在应用程序类中不可用,这要快得多。

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

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

如果我不检查 null 我将允许在调用过滤器对象上的 getMaxDistance() 时抛出一个空指针(如果应用程序对象被 Android 从内存中刷过)

于 2015-05-20T12:20:21.007 回答
0

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

于 2020-08-10T08:02:55.810 回答
0

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

于 2020-08-26T11:01:10.290 回答