Аналитика — это неотъемлемая часть современного мобильного приложения. Аналитика позволяет собрать информацию о пользователе, чтобы развивать и совершенствовать продукт.
Часто сбор информации снижает производительность приложения. Процесс дополнительно нагружает CPU и память, а это высокая цена. Медленная работа приложения может стать причиной негативных отзывов пользователей, снизить рейтинг и привести к потере аудитории.
С такой проблемой столкнулась и наша команда Android-разработчиков во время работы над очередным проектом, который был связан с новостями. Нам нужно было регистрировать отображение каждой новости в списке.
Попытка №1
Получив задание на сбор аналитики, команда достаточно быстро показала результат. Триггером для генерации события выбрали метод onViewAttachedToWindow. Все вроде хорошо, но при быстром скролле интерфейс заметно подвисал — что-то пошло не так. Проблему нужно было решать.
Каждый воспринимает подвисание по-разному, так что мы нуждались в фактах и доказательствах. Чтобы измерить подвисание, выбрали показатель FPS (Frames Per Second), а для измерения показателя — FPS-meter TinyDenser. После подключения утилиты команда получила объективное подтверждение проблемы — показатель падал, временами достаточно заметно: меньше 30 кадров в секунду, запись экрана с включенной утилитой показана на Рисунке 1.
Рисунок 1. Запись экрана до оптимизации
Попытка №2
А если отложить отправку событий, пока пользователь скроллит список? «Хммм», — подумала команда, и решила создать очередь из событий и отправлять их после того, как скролл остановится. Тут все просто: добавляем OnScrollListener в RecyclerView и ждем, пока newState будет ровным SCROLL_STATE_IDLE — задача частично решена.
class IdleStateScrollListener(private val analyticsFacade: AnalyticsFacade)
: RecyclerView.OnScrollListener() {
fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
analyticsFacade.setPending(newState != RecyclerView.SCROLL_STATE_IDLE)
}
}
Следующий шаг — реализовать накопление событий и их отправку.
Для управления очередью создали класс, который отвечал за добавление событий в очередь и отправку аналитики в сервис. Для периодической отправки выбрали ScheduledExecutorService с одним потоком, который запускал Runnable каждую секунду, время подобрали эмпирическим способом.
Это сразу дало результаты, значительный прирост FPS. В принципе, задача была решена, на Рисунке 2 видим результат работы приложения после второй попытки. Но осталась одна задачка — события отправлялись в сервис, что приводило к частому генерированию объектов класса Intent, а это дополнительно нагружало GC и доставляло «приятности» в виде stop-the-world пауз.
Рисунок 2. Запись экрана после второй попытки
Попытка №3
«Отправка не одного события за раз, а списка событий — еще одна чудесная идея», — подумала команда. После короткой доработки класса команда реализовала отправление событий списком и получила стабильные значения показателя на уровне 55–60 кадров в секунду (Рисунок 3).
Рисунок 3. Запись экрана после третий попытки
Выводы
Тривиальная задача по сбору аналитики превратилась в интересный и познавательный процесс, в ходе которого команда прокачала свои навыки в исследовании проблем производительность приложений и поиска путей их решения. Надеюсь, наш опыт пригодится как начинающим так и опытным разработчикам.
Можно ли было сделать еще что-то?
Наша команда остановилась на третьем варианте, но это не единственное, что можно было применить.
В текущей реализации при срабатывании метода onViewAttachedToWindow происходит превращение новости в объект события аналитики, соответственно, создание нового объекта. Одно из возможных решений — отложить конвертацию до момента отправки: накапливать в очереди не события, а сами элементы списка. Тогда конвертация будет происходить, когда скролл будет в режиме SCROLL_STATE_IDLE. Также для событий можно было создать пул объектов. В комплексе эти действия могут дать дополнительный прирост производительности приложения.
Автор: idg_chernyshov