访问 SharedPreferences 是不是应该在 UI 线程外完成?

Posted

技术标签:

【中文标题】访问 SharedPreferences 是不是应该在 UI 线程外完成?【英文标题】:Should accessing SharedPreferences be done off the UI Thread?访问 SharedPreferences 是否应该在 UI 线程外完成? 【发布时间】:2011-05-21 06:05:00 【问题描述】:

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

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

这是警告:

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

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

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

【问题讨论】:

我一直在 UI 线程上进行我的偏好操作。虽然我认为这是有道理的,因为它是一个 IO 操作 【参考方案1】:

很高兴你已经在玩它了!

注意事项:(以惰性子弹形式)

如果这是您遇到的最严重的问题,那么您的应用程序可能处于一个好位置。 :) 但是,写入通常比读取慢,因此请确保您使用的是 SharedPreferenced$Editor.apply() 而不是 commit()。 apply() 在 GB 和 async 中是新的(但总是安全的,小心生命周期转换)。您可以使用反射有条件地调用 GB+ 上的 apply() 和 Froyo 或更低版本上的 commit()。我将写一篇博文,其中包含如何执行此操作的示例代码。

关于加载,不过...

一旦加载,SharedPreferences 就是单例并在进程范围内缓存。因此,您希望尽早加载它,以便在需要之前将其保存在内存中。 (假设它很小,如果您使用 SharedPreferences,它应该是一个简单的 XML 文件...)您不想在将来某些用户单击按钮时出错。

1234563统计数据通常应该很快(并且经常被缓存),但是 yaffs 并没有太多的并发方式(很多 Android 设备在 yaffs 上运行...... Droid、Nexus One 等)所以如果你避免使用磁盘,您可以避免卡在其他进行中或挂起的磁盘操作之后。

因此您可能希望在 onCreate() 期间加载 SharedPreferences 并重复使用同一个实例,避免使用统计数据。

1234563 .set() FutureTask 子类的值的新线程。然后只需在需要时查找 FutureTask 的成员并 .get() 即可。我计划在 Honeycomb 的幕后透明地免费提供这个功能。我会尝试发布一些示例代码 展示了该领域的最佳实践。

查看 Android 开发者博客,了解未来几周内有关 StrictMode 相关主题的帖子。

【讨论】:

哇,没想到会直接从源头得到如此明确的答案!非常感谢! 为了这篇精彩文章的新读者的利益,请在下面找到@Brad Fitzpatrick 提到的上述博客文章的链接:android developer's blog post on strict mode by Brad。该帖子还有一个示例代码的链接,用于使用基于 android 版本的应用(从姜饼开始)或提交(froyo)来存储共享首选项:[有条件地使用应用或提交](code.google.com/p/zippy-android/source/browse/trunk/examples/…) 这在 ICS\JB 上是否仍然相关?【参考方案2】:

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

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

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

【讨论】:

但是 SQLite 不也是一个必须从闪存中读取的文件,而是一个比首选项文件更大、更复杂的文件。我一直假设,对于与首选项相关的数据量,首选项文件将比 SQLite 数据库快得多。 没错。正如 Brad 已经提到的,这几乎总是没有问题 - 他还提到加载 SharedPreferences 一次(甚至可能在使用 FutureTask 的线程中)并保留它以对单个实例进行任何可能的访问是一个好主意。跨度> 【参考方案3】:

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

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

【讨论】:

是的,这绝对是一个考虑因素。但是,如果您经常调用 get*(),那么您正处于需要结果值才能继续的阶段,在这种情况下,您无论如何都必须等待,并且不会从对 get*() 的调用中受益在另一个线程上。只是一个想法。【参考方案4】:

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

应用类:

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

步骤说明:

    主要活动调用 getLocalPreferences(this) -> 这将读取您的首选项,在您的应用程序类中设置过滤器对象并返回它。 当您在应用程序的其他位置再次调用 getLocalPreferences() 函数时,它首先会检查它是否在应用程序类中不可用,这样会快很多。

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

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

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

【讨论】:

【参考方案5】:

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

【讨论】:

【参考方案6】:

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

【讨论】:

以上是关于访问 SharedPreferences 是不是应该在 UI 线程外完成?的主要内容,如果未能解决你的问题,请参考以下文章

Android开发之SharedPreferences的封装

如何在 ViewModel 中访问 SharedPreferences?

SharedUserId 访问 SharedPreferences

Android/Kotlin:在 Retrofit Interceptor 中访问 sharedPreferences

SharedPreferences 不保存强制关闭应用程序

Android 数据存储-SharedPreferences or MMKV