是否应该通过UI线程访问SharedPreferences?


112

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

我注意到警告之一是针对getSharedPreferences()

这是警告:

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

并给出getSharedPreferences()了在UI线程上进行的调用。

是否应该SharedPreferences真正从UI线程进行访问和更改?


我一直在UI线程上进行偏好设置操作。尽管我认为这是
合理的

Answers:


184

很高兴您已经在玩它!

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

  • 如果这是您遇到的最严重的问题,则您的应用程序可能很合适。:)写操作通常比读操作慢,因此请确保您使用的是SharedPreferenced $ Editor.apply()而不是commit()。apply()是GB和异步的新功能(但始终安全,请小心生命周期转换)。您可以使用反射来有条件地调用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 Developers博客上有关StrictMode相关主题的新帖子。


哇,没想到直接从源头获得如此明确的答案!非常感谢!
cottonBallPaws

9
为了使这个精彩的文章的新读者受益,请在下面找到@Brad Fitzpatrick所提到的博客文章的链接:Brad在严格模式下的Android开发人员博客文章。该帖子还提供了指向示例代码的链接,该示例代码用于基于android版本存储共享首选项使用apply(从gingerbread开始)或commit(froyo):[有条件地使用apply或commit](code.google.com/p/zippy-android / source / browse / trunk / examples /…
tony m

4
在ICS \ JB上仍然有用吗?
ekatz

5

访问共享首选项可能要花费一些时间,因为它们是从闪存中读取的。你读很多吗?也许您可以使用其他格式,例如SQLite数据库。

但是,不要修复使用StrictMode找到的所有内容。或引用文档:

但是不要强迫修复StrictMode找到的所有内容。特别是,在正常活动生命周期中,经常需要磁盘访问的许多情况。使用StrictMode可以意外找到您所做的事情。但是,UI线程上的网络请求几乎总是一个问题。


6
但是SQLite也不是必须从闪存中读取的文件-而是与首选项文件相比更大,更复杂的文件。我一直认为,对于与首选项关联的数据量而言,首选项文件将比SQLite数据库快得多。
汤姆(Tom)

没错 正如Brad已经提到的那样,这几乎总是没有问题-他还提到最好一次加载SharedPreferences(甚至在使用FutureTask的线程中)并保存它以便对单个实例的任何访问。
mreichelt 2012年

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;
    }
}

尽管apply()在前台线程上似乎是安全的,但edit()也以相同的方式进行阻止。

(顺便说一句,很抱歉把这个放在这里。我会以此作为对布拉德的回答的评论,但我只是加入而没有足够的声誉来这样做。)


1

我知道这是一个老问题,但我想分享我的方法。我的阅读时间很长,并且使用了共享首选项和全局应​​用程序类的组合:

ApplicationClass:

public class ApplicationClass extends Application {

    private LocalPreference.Filter filter;

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

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

LocalPreference:

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对象,并在用户之前所在的位置开始活动,以使人们产生一种幻想,即该应用程序从未被杀死。

如果我不检查null,则在过滤器对象上调用例如getMaxDistance()时,将允许引发null指针(如果应用程序对象是由Android从内存中擦除的)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.