Содержание статьи
Как появляются лаги
Перед тем как перейти к обсуждению инструментов и техник повышения производительности, уделим немного времени тому, как вообще появляются лаги и почему приложение может быть медленным. Основные проблемы современных приложений:
- слишком долгая загрузка приложения и отдельных экранов;
- фризы, когда приложение просто зависает, а через некоторое время появляется сообщение с предложением его убить;
- проседания FPS, когда вместо плавной прокрутки и анимации пользователь видит слайд-шоу.
Причины всех этих проблем просты: слишком долгое выполнение каких-либо операций, вычислительных или операций ввода-вывода, а вот способы оптимизации разные.
Холодный старт
Запуск приложения состоит из нескольких стадий: это инициализация нового процесса, подготовка окна для вывода интерфейса приложения, показ окна на экране и передача управления коду приложения. Далее приложение должно сформировать интерфейс на основе описания в XML-файле, подгрузить с «диска» или из интернета необходимые для корректного отображения интерфейса элементы (битмапы, данные для списков, графиков и прочее), инициализировать дополнительные элементы интерфейса, такие как выдвижное меню (Drawer), повесить на элементы интерфейса колбэки.
Очевидно, что это огромный объем работы и следует приложить все усилия для того, чтобы выполнить ее как можно быстрее. Два главных инструмента в этом деле:
- отложенная инициализация;
- параллельный запуск задач.
Отложенная инициализация означает, что все, что можно выполнить позже, надо выполнить позже. Не стоит создавать и инициализировать все данные и объекты, которые только могут понадобиться приложению, в самом начале. Сначала инициализируем только то, что нужно для корректного отображения главного экрана, затем переходим ко всему остальному.
Обработку сложных и дорогостоящих операций, которые невозможно оптимизировать, а также блокируемых операций типа чтения с диска или получения данных с сервера, следует отправлять в отдельный поток и обновлять интерфейс асинхронно по завершении его работы.
Пример: у тебя есть приложение, которое должно показывать на главном экране сводку данных, полученных из интернета. Самый очевидный способ сделать это — получить данные с сервера и после этого отобразить интерфейс. И хотя Android по умолчанию не позволяет выполнять сетевые запросы в основном потоке приложения, вынуждая создавать отдельный поток для получения данных с сервера, большинство разработчиков все равно постараются сделать код последовательным.
Проблема такого подхода в том, что он вносит задержки, которые просто не нужны. Большую часть времени сетевой поток будет простаивать в ожидании данных, и это время лучше использовать для отображения интерфейса. Другими словами: сразу после запуска приложения следует создать поток, который будет получать данные с сервера, но не ожидать получения этих данных, а создавать интерфейс, используя временные заглушки вместо пока еще не принятых данных.
В качестве заглушек можно использовать пустые картинки, пустые строки, пустые списки (например, RecyclerView можно инициализировать сразу, а при получении данных просто вызвать notifyDataSetChanged()
). После получения данных с сервера их следует кешировать. При следующем запуске их можно будет использовать вместо заглушек.
Такой подход эффективно работает в отношении не только сетевых коммуникаций, но и любых задач, требующих долгих вычислений и/или ожидания данных. Например, лаунчеру необходимо немало времени, чтобы запросить у системы список установленных приложений, отсортировать его, загрузить в память иконки и другие данные. Поэтому современные лаунчеры делают это асинхронно: отображают интерфейс и с помощью фонового потока заполняют его иконками приложений.
Еще одно узкое место: формирование интерфейса из описания лайотов в XML-файле. Когда ты вызываешь метод setContentView()
или inflate()
объекта LayoutInflater (в коде фрагмента), Android находит нужный лайот в бинарном XML-файле (для эффективности Android Studio упаковывает XML в бинарный формат), читает его, проводит парсинг и на основе полученных данных формирует интерфейс, измеряя и подгоняя элементы интерфейса друг к другу.
Это действительно сложная и дорогая операция. Поэтому необходимо уделить особое внимание оптимизации лайотов: избегать излишней вложенности лайотов друг в друга (например, использовать RelativeLayout вместо вложенных друг в друга LinearLayout), а также разбить сложные описания интерфейса на множество более мелких и загружать их только тогда, когда в этом возникнет необходимость.
Другой вариант — перейти на язык Kotlin и использовать библиотеку Anko. Она позволяет описывать интерфейс прямо в коде, благодаря чему скорость отображения интерфейса возрастает в четыре раза, а ты получаешь большую гибкость в управлении логикой формирования интерфейса.
Фризы и проседания FPS
В Android только главный поток приложения имеет право обновлять экран и обрабатывать нажатия на экран. Это значит, что, когда твое приложение занимается сложной работой в главном потоке, оно не имеет возможности реагировать на нажатия. Для пользователя приложение будет выглядеть зависшим. В данном случае опять же поможет вынос сложных операций в отдельные потоки.
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»