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

时间:2010-12-06 21:47:56

标签: android sharedpreferences android-strictmode

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

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

这是警告:

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

正在为UI线程进行getSharedPreferences()调用。

是否真的要从UI线程SharedPreferences访问和更改?

6 个答案:

答案 0 :(得分:174)

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

有些注意事项:(以懒惰的子弹形式)

  • 如果这是你遇到的最糟糕的问题,你的应用可能就是一个好位置。 :)但是,写入通常比读取慢,因此请确保使用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()期间无论如何都不需要你的偏好,那么加载时间会不必要地拖延你的app的启动,所以通常更好的方法是使用FutureTask< SharedPreferences>这样的东西。将新线程启动到.set()FutureTask子类的值的子类。然后只需在需要时查找FutureTask< SharedPreferences>的成员,然后查找.get()它。我打算透明地在Honeycomb的幕后制作这个。我将尝试发布一些示例代码 展示了该领域的最佳实践。

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

答案 1 :(得分:5)

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

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

  

但是不要觉得有必要修复StrictMode找到的所有东西。特别是,在正常的活动生命周期中,通常需要许多磁盘访问的情况。使用StrictMode查找您意外执行的操作。但是,UI线程上的网络请求几乎总是一个问题。

答案 2 :(得分:4)

关于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()似乎在前台线程上是安全的。

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

答案 3 :(得分: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()函数时,它首先会检查它是否在应用程序类中不可用,而且速度要快得多。
  3. 注意:始终检查应用程序范围变量是否与NULL不同,原因 - > http://www.developerphil.com/dont-store-data-in-the-application-object/

      

    应用程序对象永远不会留在内存中,它会被杀死。与流行的看法相反,该应用程序将不会从头开始重新启动。 Android将创建一个新的Application对象并启动用户之前的活动,以便首先假定该应用程序从未被杀死。

    如果我没有检查null,我会在过滤器对象上调用例如getMaxDistance()时允许抛出nullpointer(如果应用程序对象是由Android从内存中刷过的话)

答案 4 :(得分:0)

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

答案 5 :(得分:0)

我看不到从后台线程读取它们的任何理由。但是我会写。在启动时,共享的首选项文件已加载到内存中,因此可以快速访问它,但是写东西可能会花费一些时间,因此我们可以使用apply write async。这应该是共享首选项的commit和apply方法之间的区别。