Новое API в Gingerbread — StrictMode. Или боремся с ANR-диалогами

в 8:31, , рубрики: debugging, development, optimization, performance, переводы, Разработка под android, метки: , , ,

Недавно открыл для себя StrictMode, прочитав статью на Android Developers Blog. Ниже представляю Вам ее перевод.

image

За сценой

Одна из клевых вещей в Google — это «20% времени»: 20% от своего рабочего времени вы имеете право заниматься проектами, не имеющими никакого отношения к вашему основному проекту. Когда я пришел в Google, я постоянно переключался с проекта на проект и часто шутил по этому поводу, что у меня 7 таких 20%-ных проектов. Один из проектов, к которому я постоянно возвращался, был Android. Мне нравилась открытость платформы, которая давала мне возможность делать все, что я хотел, в том числе открывать двери моего гаража, когда я подъезжал к своему дому на мотоцикле. Я действительно хотел, чтобы этот проект был успешным, но я беспокоился об одном: Android никогда не был быстрым. Подтормаживающие анимации и элементы пользовательского интерфейса, которые не всегда сразу реагируют на ввод данных. Было очевидно, что причина этого — задачи, выполняющиеся не в том потоке.

Я являюсь активным пользователем SMS и одним из моих 20%-ных проектов в ходе подготовки релиза Cupcake (Android 1.5) стала оптимизация приложения обмена сообщениями. Я оптимизировал его и сделал более плавным, а затем продолжил метаться между другими своими 20%-ными проектами. После выхода релиза Donut (Android 1.6), я заметил, что некоторые из моих оптимизаций случайно оказались сломанными. Мне было немного обидно, но затем я понял, что Android действительно всегда не хватало, так это готового к использованию, встроенного, всепроникающего средства мониторинга производительности.

Я присоединился к команде разработчиков Android на полный рабочий день чуть более года назад и провел много времени за исследованиями проблем производительности во Froyo. В частности посвятил много времени борьбе с ANR-диалогами (вы видите эти раздражающие диалоги, когда приложение выполняет длительные операции внутри основного UI потока). Отладка этих диалогов, с помощью имеющихся инструментов, была трудной и утомительной. Их было не достаточно чтобы найти причину, особенно, при взаимодействии нескольких процессов (например, обращения из Binder'ов или ContentResolver'ов к Service'ам или ContentProvider'ам в других процессах). Необходим был более совершенный инструмент для отслеживания притормаживаний интерфейса или ANR-диалогов.

StrictMode

StrictMode это новое API доступное начиная с Gingerbread, которое позволяет вам устанавливать политики на потоки, которые регулируют список операций запрещенных к выполнению в них. Не вдаваясь особо глубоко в детали реализации это просто битовая маска хранящаяся в tread-local переменной.

По умолчанию все операции разрешены, и ничто не стоит у вас на пути, пока вы не захотите этого. Флаги, которыми вы можете управлять через политики, включают:

  • обнаружение записи на диск
  • обнаружение чтения с диска
  • обнаружение сетевого взаимодействия
  • при обнаружении: запись в лог
  • при обнаружении: остановка приложения
  • при обнаружении: запись в Dropbox
  • при обнаружении: показать ANR-диалог

Кроме того, StrictMode имеет несколько хуков в большинстве мест, которые выполняют обращения к диску (в java.io.*, android.database.sqlite.* и т.д.) и сети (java.net.*), которые проверяют соблюдение текущим потоком установленных политик, реагируя соответствующим образом.

Сильной стороной StrictMode является то, что в каждом потоке политики срабатывают всякий раз, когда Binder выполняет IPC запросы к другим Service'ам или ContentProvider'ам, при этом стек трейсы склеиваются вместе несмотря на количество межпроцессных вызовов.

Никто не хочет быть медленным

Вы можете знать все места, где ваше приложение выполняет операции дискового ввода/вывода, но знаете ли вы, все места, где это делают системные сервисы и провайдеры? Я — нет. Я пытаюсь находить такие места, но объем исходного кода платформы слишком велик. Мы постоянно работаем над совершенствованием документации к SDK, добавляя в нее советы, касающиеся производительности, но, обычно, я полагаюсь на StrictMode, чтобы найти коде случайные обращения к диску.

Обращения к диску в мобильных устройствах

Минутку! Что может быть не так с обращениями к диску? Ведь все Android устройства используют флэш-память. Это же практически супер-быстрый SSD, без движущихся частей? Я же не должен беспокоиться об этом? К сожалению — нет.

Вы не можете положиться на то, что все флэш-компоненты или файловые системы, используемые в большинстве Android устройств работают быстро. YAFFS — файловая система, используемая во многих Android устройствах, имеет глобальную блокировку на каждую операцию. Для всего устройства в один момент времени может выполняться только одна операция. Даже простая «stat» операция может занять продолжительное время, если вам не повезло. Другие устройства с более традиционными файловыми системами так же имеют ряд недостатков, например, когда сборщик мусора решает начать выполнять медленные операции по освобождению флэш-памяти. (Более подробно данная проблема описана здесь)

Вывод из всего вышесказанного такой: несмотря на то, что файловая система на мобильных устройствах, как правило, быстрая, тем не менее в 90% случаев, в момент когда случится задержка она будет очень большой. Кроме того, производительность большинства файловых систем падает с уменьшением количества свободного места на диске. (См. слайды с Google I/O Zippy Android App)

Главный поток приложения

Различные Android-колбэки и события жизненного цикла, обычно обрабатываются в главном потоке приложения (он же UI-thread). В большинстве случаев это делает жизнь проще, но это также заставляет вас быть осторожными, потому что все анимации, скролирования и прочее так же исполняются в этом потоке.

Если вы хотите запустить анимацию со скоростью 60 fps и вдруг срабатывает событие (также в основном потоке), у вас есть 16 мс для запуска кода, реагирующего на это событие. Если вы затратите времени больше чем 16 мс, например, из за операции записи на диск, вы получите подвисание. Обычно обращения к диску занимают значительно меньше времени чем 16 мс, но иногда это может быть не так. Например, в случае YAFFS файловой системы, если вы ждете пока ресурс файловой системы будет освобожден другим процессом, который в данный момент находится в середине процесса записи.

Сетевые операции особенно медленны и не предсказуемы, поэтому вы никогда не должны совершать обращения к сетевым ресурсам из основного потока приложения. По этой же причине, в предстоящем релизе Honeycomb мы сделали так, что бы сетевые запросы в главном потоке приводили к фатальной ошибке (Если ваше приложение имеет targetSdkVersion версии API Honeycomb и старше). Поэтому если вы хотите, подготовить свое приложение к выходу Honeycomb, убедитесь, что вы никогда не делаете сетевых запросов в UI-потоке. (см. «Повышаем плавность» ниже).

Включаем StrictMode

Рекомендуемый способ использования StrictMode — это включить его в процессе разработки, проанализировать вывод, и затем отключить перед релизом приложения.

Например, вы можете включить его в onCreate() приложения или компонента:

 public void onCreate() {
     if (DEVELOPER_MODE) {
         StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                 .detectDiskReads()
                 .detectDiskWrites()
                 .detectNetwork()
                 .penaltyLog()
                 .build());
     }
     super.onCreate();
 }

Или просто:

 public void onCreate() {
     if (DEVELOPER_MODE) {
         StrictMode.enableDefaults();
     }
     super.onCreate();
 }

Последний способ был добавлен специально для того чтобы вы могли продолжать использовать пре-Gingerbread API и при этом имели возможность легко включить StrictMode используя рефлексию или другие техники. Например, вы могли бы указать в качестве targetApi Donut (Android 1.6), но по-прежнему использовать StrictMode если вы тестируете приложение на Gingerbread устройстве или эмуляторе.

Анализ StrictMode

Если вы используете penaltyLog(), по умолчанию, просто запустите adb logcat и читайте вывод терминала. Любые нарушения политик будут попадать в вашу консоль.

Если вы хотите найти подозрительные места в коде, включите penaltyDropbox (), и они StrictMode запишет свой вывод в DropBoxManager, откуда вы сможете его извлечь позже с помощью adb shell dumpsys dropbox data_app_strictmode --print

Повышаем плавность

В дополнение к потокам и java.util.concurrent.* у вас есть возможность использовать некоторое Android API, например, таких как Handler, AsyncTask, AsyncQueryHandler и IntentService.

Наш опыт

Во время работы над кодом самой платформы мы каждый день получали «dogfood»-билд, который использовала вся команда. В процессе разработки Gingerbread, мы каждый день собирали билды со включенным StrictMode и регистрировали все найденные нарушения для дальнейшего анализа. Каждый час запускался MapReduce процесс, который собирал интерактивный отчет содержащий все нарушения, их стек-трейсы (включая межпроцессные вызовы), их продолжительность, процессы и пакеты в которых они произошли и т.д.

Используя данные из StrictMode мы исправили сотни ошибок и увеличили плавность анимации и отзывчивость по всей системе. Мы оптимизировали ядро Android (например, системные службы и провайдеры), так что это пойдет на пользу всем программам в системе, а также исправили кучу ошибок непосредственно в приложениях (как в приложениях AOSP, так и Google). Даже если вы все еще используете Froyo, последние обновления для GMail, Google Maps, YouTube всеравно содержат исправления, повышающие их производительность, полученные путем анализа данных собранных с помощью StrictMode.

Там, где мы не могли автоматически ускорить работу системы, мы добавили новое API, чтобы сделать применение определенных паттернов более простым и эффективным. Например, есть новый метод SharedPreferences.Editor.apply(), который вы должны использовать вместо commit(), если вам не нужно значение возвращаемое commit()'ом (Оказывается, почти никто никогда им не пользуется). Вы даже можете использовать рефлексию для того чтобы определить прямо в рантайме что вам использовать apply() или commit(), в зависимости от версии платформы.

Пользователи, которые перешли с Froyo на Gingerbread были потрясены тем насколько более быстрой стала их система. Наши друзья из команды Chrome, тоже недавно добавили нечто подобное. Конечно, StrictMode не может присвоить себе всю славу связанную с ускорением системы, новый сборщик мусора так же сыграл в этом большую роль.

Что дальше?

StrictMode API и его возможности будут продолжать развиваться. Мы уже добавили некоторые клевые штуки в связанные со StrictMode в Honeycomb, но давайте нам знать, что еще вы бы хотели увидеть в нем! Я буду отвечать на ваши вопросы с тегом «strictmode» на stackoverflow.com. Спасибо!

Автор: TheDimasig

Поделиться

* - обязательные к заполнению поля