Чи повинен доступ до 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 () на ГБ + та фіксації () на Froyo або нижче. Я буду займатися блогом із зразковим кодом, як це зробити.

Щодо завантаження, хоча ...

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

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

  • тому ви, ймовірно, захочете завантажити SharedPreferences під час свого onCreate () та повторно використовувати той самий екземпляр, уникаючи stat.

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

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


Нічого собі, не сподівався отримати таку чітку відповідь прямо від джерела! Велике спасибі!
cottonBallPaws

9
Для користі нових читачів цього чудового допису знайдіть нижче посилання на згадану вище публікацію в блозі від @Brad Fitzpatrick: блог андроїдського розробника в суворому режимі Бредом . У публікації також є посилання на зразок коду для використання застосовувати (від пряників далі) або виконувати (froyo) на основі версії для Android для зберігання спільних налаштувань: [умовно використовувати застосувати або вчинити] ( code.google.com/p/zippy-android / джерело / перегляд / багажник / приклади /… )
tony m

4
Це все ще актуально для ICS \ JB?
ekatz

5

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

Але не виправляйте все, що ви знайдете за допомогою StrictMode. Або цитувати документацію:

Але не відчувайте вимушеність виправляти все, що знайде StrictMode. Зокрема, багато випадків доступу до дисків часто необхідні протягом звичайного життєвого циклу діяльності. Використовуйте StrictMode, щоб знайти речі, які ви зробили випадково. Хоча мережеві запити на потоці інтерфейсу майже завжди є проблемою.


6
Але не SQLite - це також файл, який потрібно читати з флеш-пам’яті, але більший і складніший у порівнянні з файлом налаштувань. Я припускав, що для кількості даних, пов’язаних з уподобаннями, файл налаштувань буде набагато швидшим, ніж база даних SQLite.
Том

Це правильно. Як уже згадував Бред, це майже завжди не є проблемою - і він також зазначає, що добре б один раз завантажити SharedPreferences (можливо, навіть у Thread, використовуючи 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;
    }
}

редагування () також блокується аналогічно, хоча видача () видається безпечною у потоці переднього плану.

(BTW вибачте, що виклали це тут. Я б сказав це як коментар до відповіді Бреда, але я просто приєднався і не маю достатньої репутації для цього.)


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 (це) -> це прочитає ваші налаштування, встановить об’єкт фільтра у вашому класі додатків та поверне його.
  2. Коли ви викликаєте функцію getLocalPreferences () знову десь в іншому додатку, вона спочатку перевіряє, чи вона недоступна в класі додатків, що набагато швидше.

ПРИМІТКА: ЗАВЖДИ перевірте, чи величина змінної програми не відрізняється від NULL, причина -> http://www.developerphil.com/dont-store-data-in-the-application-object/

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

Якщо я не перевірив на null, я б дозволив перекинути nullpointer при виклику, наприклад, getMaxDistance () на об’єкт фільтру (якщо об’єкт програми був перенесений з пам'яті Android)


0

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.