Как собрать аналитику и не убить производительность

в 13:31, , рубрики: android, performance optimization, recyclerview, Песочница, разработка мобильных приложений, Разработка под android

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

Часто сбор информации снижает производительность приложения. Процесс дополнительно нагружает CPU и память, а это высокая цена. Медленная работа приложения может стать причиной негативных отзывов пользователей, снизить рейтинг и привести к потере аудитории.

С такой проблемой столкнулась и наша команда Android-разработчиков во время работы над очередным проектом, который был связан с новостями. Нам нужно было регистрировать отображение каждой новости в списке.

Попытка №1

Получив задание на сбор аналитики, команда достаточно быстро показала результат. Триггером для генерации события выбрали метод onViewAttachedToWindow. Все вроде хорошо, но при быстром скролле интерфейс заметно подвисал — что-то пошло не так. Проблему нужно было решать.

Каждый воспринимает подвисание по-разному, так что мы нуждались в фактах и доказательствах. Чтобы измерить подвисание, выбрали показатель FPS (Frames Per Second), а для измерения показателя — FPS-meter TinyDenser. После подключения утилиты команда получила объективное подтверждение проблемы — показатель падал, временами достаточно заметно: меньше 30 кадров в секунду, запись экрана с включенной утилитой показана на Рисунке 1.

image

Рисунок 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 пауз.

image

Рисунок 2. Запись экрана после второй попытки

Попытка №3

«Отправка не одного события за раз, а списка событий — еще одна чудесная идея», — подумала команда. После короткой доработки класса команда реализовала отправление событий списком и получила стабильные значения показателя на уровне 55–60 кадров в секунду (Рисунок 3).

image

Рисунок 3. Запись экрана после третий попытки

Выводы

Тривиальная задача по сбору аналитики превратилась в интересный и познавательный процесс, в ходе которого команда прокачала свои навыки в исследовании проблем производительность приложений и поиска путей их решения. Надеюсь, наш опыт пригодится как начинающим так и опытным разработчикам.

Можно ли было сделать еще что-то?

Наша команда остановилась на третьем варианте, но это не единственное, что можно было применить.

В текущей реализации при срабатывании метода onViewAttachedToWindow происходит превращение новости в объект события аналитики, соответственно, создание нового объекта. Одно из возможных решений — отложить конвертацию до момента отправки: накапливать в очереди не события, а сами элементы списка. Тогда конвертация будет происходить, когда скролл будет в режиме SCROLL_STATE_IDLE. Также для событий можно было создать пул объектов. В комплексе эти действия могут дать дополнительный прирост производительности приложения.

Автор: idg_chernyshov

Источник

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


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js