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

发布于 2024-10-06 11:37:10 字数 500 浏览 4 评论 0原文

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

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

这是警告:

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

在 UI 线程上进行 getSharedPreferences() 调用时发出该警告。

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

With the release of Gingerbread, I have been experimenting with some of the new API's, one of them being StrictMode.

I noticed that one of the warnings is for getSharedPreferences().

This is the warning:

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

and it's being given for a getSharedPreferences() call being made on the UI thread.

Should SharedPreferences access and changes really be made off the UI thread?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

喜爱皱眉﹌ 2024-10-13 11:37:10

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

需要注意的一些事项:(以懒惰的项目符号形式)

  • 如果这是您的问题中最糟糕的,那么您的应用程序可能处于一个好的位置。 :) 不过,写入通常比读取慢,因此请确保使用 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 并重复使用同一实例,避免统计数据。

  • 但是,如果您在 onCreate() 期间不需要您的首选项,则该加载时间会不必要地拖延您的应用程序的启动,因此通常最好使用像 FutureTask这样的东西。启动一个新线程以 .set() FutureTask 子类的值的子类。然后只需在需要时查找 FutureTask的成员并 .get() 它即可。我计划在 Honeycomb 中以透明的方式在幕后免费提供此服务。我将尝试发布一些示例代码
    显示了该领域的最佳实践。

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

I'm glad you're already playing with it!

Some things to note: (in lazy bullet form)

  • if this is the worst of your problems, your app's probably in a good spot. :) Writes are generally slower than reads, though, so be sure you're using SharedPreferenced$Editor.apply() instead of commit(). apply() is new in GB and async (but always safe, careful of lifecycle transitions). You can use reflection to conditionally call apply() on GB+ and commit() on Froyo or below. I'll be doing a blogpost with sample code of how to do this.

Regarding loading, though...

  • once loaded, SharedPreferences are singletons and cached process-wide. so you want to get it loaded as early as possible so you have it in memory before you need it. (assuming it's small, as it should be if you're using SharedPreferences, a simple XML file...) You don't want to fault it in the future time some user clicks a button.

  • but whenever you call context.getSharedPreferences(...), the backing XML file is stat'd to see if it's changed, so you'll want to avoid those stats during UI events anyway. A stat should normally be fast (and often cached), but yaffs doesn't have much in the way of concurrency (and a lot of Android devices run on yaffs... Droid, Nexus One, etc.) so if you avoid disk, you avoid getting stuck behind other in-flight or pending disk operations.

  • so you'll probably want to load the SharedPreferences during your onCreate() and re-use the same instance, avoiding the stat.

  • but if you don't need your preferences anyway during onCreate(), that loading time is stalling your app's start-up unnecessarily, so it's generally better to have something like a FutureTask<SharedPreferences> subclass that kicks off a new thread to .set() the FutureTask subclasses's value. Then just lookup your FutureTask<SharedPreferences>'s member whenever you need it and .get() it. I plan to make this free behind the scenes in Honeycomb, transparently. I'll try to release some sample code which
    shows best practices in this area.

Check the Android Developers blog for upcoming posts on StrictMode-related subjects in the coming week(s).

回梦 2024-10-13 11:37:10

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

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

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

Accessing the shared preferences can take quite some time because they are read from flash storage. Do you read a lot? Maybe you could use a different format then, e.g. a SQLite database.

But don't fix everything you find using StrictMode. Or to quote the documentation:

But don't feel compelled to fix everything that StrictMode finds. In particular, many cases of disk access are often necessary during the normal activity lifecycle. Use StrictMode to find things you did by accident. Network requests on the UI thread are almost always a problem, though.

人海汹涌 2024-10-13 11:37:10

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

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

One subtlety about Brad's answer: even if you load the SharedPreferences in onCreate(), you should probably still read values on the background thread because getString() etc. block until reading the shared file preference in finishes (on a background thread):

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

edit() also blocks in the same way, although apply() appears to be safe on the foreground thread.

(BTW sorry to put this down here. I would have put this as a comment to Brad's answer, but I just joined and don't have enough reputation to do so.)

夜巴黎 2024-10-13 11:37:10

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

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-应用程序对象中的数据/

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

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

I know this is an old question but I want to share my approach. I had long reading times and used a combination of shared preferences and the global application class:

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 (activity that get called first in your application):

LocalPreference.getLocalPreferences(this);

Steps explained:

  1. The main activity calls getLocalPreferences(this) -> this will read your preferences, set the filter object in your application class and returns it.
  2. When you call the getLocalPreferences() function again somewhere else in the application it first checks if it's not available in the application class which is a lot faster.

NOTE: ALWAYS check if an application wide variable is different from NULL, reason -> http://www.developerphil.com/dont-store-data-in-the-application-object/

The application object will not stay in memory forever, it will get killed. Contrary to popular belief, the app won’t be restarted from scratch. Android will create a new Application object and start the activity where the user was before to give the illusion that the application was never killed in the first place.

If I didn't check on null I would allow a nullpointer to be thrown when calling for example getMaxDistance() on the filter object (if the application object was swiped from the memory by Android)

夏有森光若流苏 2024-10-13 11:37:10

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

SharedPreferences class does some reads & writes within XML files on disk, so just like any other IO operation it could be blocking. The amount of data currently stored in SharedPreferences affects the time and resource consumed by the API calls. For minimal amounts of data it's a matter of a few milliseconds (sometimes even less than a millisecond) to get/put data. But from the point of view of an expert it could be important to improve the performance by doing the API calls in background. For an asynchronous SharedPreferences I suggest checking out the Datum library.

帅哥哥的热头脑 2024-10-13 11:37:10

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

i do not see any reason to read them from a background thread. but to write it i would. at startup time the shared preference file is loaded into memory so its fast to access, but to write things can take a bit of time so we can use apply the write async. that should be the difference between commit and apply methods of shared prefs.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文