Должен ли доступ к SharedPreferences осуществляться из потока пользовательского интерфейса?

113

С выпуском Gingerbread я экспериментировал с некоторыми новыми API, одним из которых был StrictMode .

Заметил, что одно из предупреждений для getSharedPreferences().

Это предупреждение:

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

и он предоставляется для getSharedPreferences()вызова, выполняемого в потоке пользовательского интерфейса.

Должен ли SharedPreferencesдоступ и изменения действительно производиться вне потока пользовательского интерфейса?

хлопокШарикЛапы
источник
Я всегда выполнял свои операции с предпочтениями в потоке пользовательского интерфейса. Хотя я думаю, это имеет смысл, поскольку это операция ввода-вывода
Фалмарри

Ответы:

184

Я рада, что вы уже играете с этим!

Несколько замечаний: (в форме ленивого маркера)

  • если это самая большая из ваших проблем, вероятно, ваше приложение в хорошем состоянии. :) Запись обычно медленнее, чем чтение, поэтому убедитесь, что вы используете SharedPreferenced $ Editor.apply () вместо commit (). apply () - это новинка в GB и async (но всегда безопасна, осторожна с переходами жизненного цикла). Вы можете использовать отражение для условного вызова apply () на GB + и commit () на Froyo или ниже. Я напишу в блоге пример кода, как это сделать.

Что касается загрузки ...

  • после загрузки SharedPreferences являются одиночными и кэшируются для всего процесса. поэтому вы хотите, чтобы он был загружен как можно раньше, чтобы он был у вас в памяти до того, как он вам понадобится. (при условии, что он небольшой, как и должно быть, если вы используете SharedPreferences, простой XML-файл ...) Вы не хотите винить его в будущем, когда какой-то пользователь нажимает кнопку.

  • но всякий раз, когда вы вызываете context.getSharedPreferences (...), резервный XML-файл проверяется, изменился ли он, так что вы все равно захотите избежать этой статистики во время событий пользовательского интерфейса. Статистика обычно должна быть быстрой (и часто кешированной), но yaffs не имеет много возможностей для параллелизма (и многие устройства Android работают на yaffs ... Droid, Nexus One и т. Д.), Поэтому, если вы избегаете диска , вы избежите застревания за другими текущими или ожидающими дисковыми операциями.

  • поэтому вы, вероятно, захотите загрузить SharedPreferences во время onCreate () и повторно использовать тот же экземпляр, избегая статистики.

  • но если вам все равно не нужны ваши предпочтения во время onCreate (), это время загрузки излишне задерживает запуск вашего приложения, поэтому обычно лучше иметь что-то вроде подкласса FutureTask <SharedPreferences>, который запускает новый поток для .set () значение подклассов FutureTask. Затем просто ищите свой член FutureTask <SharedPreferences>, когда он вам нужен, и .get () его. Я планирую сделать это незаметно в Honeycomb бесплатно, прозрачно. Я постараюсь выпустить образец кода, который показывает лучшие практики в этой области.

Посетите блог разработчиков Android, чтобы узнать о предстоящих публикациях по темам, связанным со StrictMode, в ближайшие недели.

Брэд Фицпатрик
источник
Ух ты, не ожидал получить такой четкий ответ прямо из первоисточника! Большое спасибо!
cottonBallPaws
9
Для новых читателей этого замечательного поста найдите ниже ссылку на сообщение в блоге, упомянутое выше @Brad Fitzpatrick: сообщение в блоге разработчика Android о строгом режиме от Брэда . В сообщении также есть ссылка на пример кода для использования apply (начиная с gingerbread и далее) или commit (froyo) на основе версии Android для хранения общих настроек: [условно используйте apply или commit] ( code.google.com/p/zippy-android / source / browse / trunk / examples /… )
tony m
4
Это все еще актуально для ICS \ JB?
ekatz 06
5

Доступ к общим предпочтениям может занять некоторое время, поскольку они считываются из флэш-памяти. Вы много читаете? Возможно, тогда вы могли бы использовать другой формат, например, базу данных SQLite.

Но не исправляйте все, что вы найдете, с помощью StrictMode. Или процитируем документацию:

Но не чувствуйте себя обязанным исправлять все, что обнаруживает StrictMode. В частности, во многих случаях доступ к диску часто необходим в течение обычного жизненного цикла деятельности. Используйте StrictMode, чтобы найти то, что вы сделали случайно. Однако сетевые запросы в потоке пользовательского интерфейса почти всегда являются проблемой.

Mreichelt
источник
6
Но SQLite - это не еще и файл, который нужно читать из флеш-хранилища, а более крупный и сложный файл по сравнению с файлом настроек. Я предполагал, что для объема данных, связанных с настройками, файл настроек будет намного быстрее, чем база данных SQLite.
Том
Это правильно. Как уже упоминал Брэд, это почти всегда не проблема - и он также упоминает, что это хорошая идея - загрузить SharedPreferences один раз (возможно, даже в потоке с использованием FutureTask) и сохранить его для любого возможного доступа к единственному экземпляру.
mreichelt
5

Одна тонкость в ответе Брэда: даже если вы загружаете SharedPreferences в onCreate (), вам, вероятно, все равно следует читать значения в фоновом потоке, потому что getString () и т. Д. Блокируется до тех пор, пока не прочитает предпочтения общего файла в конце (в фоновом потоке):

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

edit () также блокируется таким же образом, хотя apply () кажется безопасным в потоке переднего плана.

(Кстати, извините, что записываю это здесь. Я бы поставил это как комментарий к ответу Брэда, но я только что присоединился и у меня недостаточно репутации для этого.)

Том О'Нил
источник
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) -> this прочитает ваши предпочтения, установит объект фильтра в вашем классе приложения и вернет его.
  2. Когда вы снова вызываете функцию getLocalPreferences () где-то в другом месте приложения, она сначала проверяет, недоступна ли она в классе приложения, что намного быстрее.

ПРИМЕЧАНИЕ. ВСЕГДА проверяйте, не отличается ли переменная всего приложения от NULL, причина -> http://www.developerphil.com/dont-store-data-in-the-application-object/

Объект приложения не останется в памяти навсегда, он будет убит. Вопреки распространенному мнению, приложение не будет перезапущено с нуля. Android создаст новый объект Application и запустит действие там, где раньше был пользователь, чтобы создать иллюзию того, что приложение никогда не было убито.

Если бы я не проверял нулевое значение, я бы разрешил бросать нулевой указатель при вызове, например, getMaxDistance () объекта фильтра (если объект приложения был удален из памяти Android)

Jdruwe
источник
0

Класс SharedPreferences выполняет некоторые операции чтения и записи в файлах XML на диске, поэтому, как и любая другая операция ввода-вывода, он может блокировать. Объем данных, хранящихся в настоящее время в SharedPreferences, влияет на время и ресурсы, потребляемые вызовами API. Для минимальных объемов данных получение / размещение данных занимает несколько миллисекунд (иногда даже меньше миллисекунды). Но с точки зрения эксперта может быть важно улучшить производительность, выполняя вызовы API в фоновом режиме. Для асинхронных SharedPreferences я предлагаю проверить библиотеку Datum .

Навид
источник