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

Часто сбор информации снижает производительность приложения. Процесс дополнительно нагружает 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. Также для событий можно было создать пул объектов. В комплексе эти действия могут дать дополнительный прирост производительности приложения.

Комментарии (6)


  1. Dwite
    22.06.2018 22:13

    А каким сервисом аналитики пользуетесь? Просто большинство современных сервисов вроде Fabric делают все очень оптимизировано и из коробки уже очень давно)


    1. idg_chernyshov Автор
      23.06.2018 12:58

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


  1. sankarsana
    23.06.2018 12:59

    Спасибо за статью! Познавательно!
    Вы писали о частом создании intent.
    Вопрос такой, на сколько затратна операция, создания интента?


    1. idg_chernyshov Автор
      23.06.2018 13:00

      Это такая же операци создания объекта со всеми вытекающими.


  1. funca
    23.06.2018 17:08

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

    Расскажите еще немного о бизнес-задаче. В чем практический смысл трекать появление новостей на экране при скроллинге со скоростью 55-60 fps?


    1. idg_chernyshov Автор
      23.06.2018 20:30

      Задача стояла регистрировать факт отображения preview каждой новости на экране, не зависимо от скорости скрола ленты, как именно эта аналитика потом была бы использована я могу только предполагать, возможно потом применялись алгоритмы машинного обучения для анализа и дальнейшего формирования наиболее релевантной выдачи.

      Почему 60 fps — мы стремились получить максимальную производительность для достижения планости работы приложения. Лично меня очень напрягает когда я хочу быстро пролистать контент, а в этот момент приложение тормозит, по этому я не хотел отдавать приложение так как есть.