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

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

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

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

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

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

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

Ответ 1

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

Некоторые примечания: (в ленивой форме пули)

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

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

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

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

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

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

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

Ответ 2

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

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

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

Ответ 3

Одна тонкость ответа Брэда: даже если вы загружаете 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() кажется безопасным в потоке переднего плана.

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

Ответ 4

Я знаю, что это старый вопрос, но я хочу поделиться своим подходом. У меня было длительное время чтения и использовалось сочетание общих настроек и глобального класса приложений:

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

Шаги объяснены:

  • Основное действие вызывает getLocalPreferences (this) → , это будет читать ваши настройки, задавать объект фильтра в вашем классе приложения и возвращать его.
  • Когда вы снова вызываете функцию getLocalPreferences() где-то еще в приложении, она сначала проверяет, не является ли это в классе приложения намного быстрее.

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

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

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