Библиотека JankStats

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

Возможности

JankStats основан на существующих возможностях платформы Android, включая API FrameMetrics в Android 7 (уровень API 24) и выше или OnPreDrawListener в более ранних версиях. Эти механизмы могут помочь приложениям отслеживать, сколько времени требуется для обработки кадров. Библиотека JanksStats предлагает две дополнительные возможности, которые делают ее более динамичной и простой в использовании: эвристика ошибок и состояние пользовательского интерфейса.

Янковая эвристика

Хотя вы можете использовать FrameMetrics для отслеживания длительности кадров, FrameMetrics не предлагает никакой помощи в определении фактических задержек. Однако JankStats имеет настраиваемые внутренние механизмы определения момента возникновения зависаний, что делает отчеты более полезными.

состояние пользовательского интерфейса

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

JankStats решает эту проблему, вводя API state , который позволяет вам взаимодействовать с библиотекой и предоставлять информацию об активности приложения. Когда JankStats регистрирует информацию о некорректном кадре, он включает текущее состояние приложения в отчеты о недопустимых кадрах.

Использование

Чтобы начать использовать JankStats, создайте экземпляр и включите библиотеку для каждого Window . Каждый объект JankStats отслеживает данные только внутри Window . Для создания экземпляра библиотеки требуется экземпляр Window вместе с прослушивателем OnFrameListener , оба из которых используются для отправки метрик клиенту. Прослушиватель вызывается с FrameData для каждого кадра и детализирует:

  • Время начала кадра
  • Значения длительности
  • Следует ли считать кадр ненужным
  • Набор пар строк, содержащих информацию о состоянии приложения во время кадра.

Чтобы сделать JankStats более полезным, приложения должны заполнять библиотеку соответствующей информацией о состоянии пользовательского интерфейса для создания отчетов в FrameData. Вы можете сделать это через API PerformanceMetricsState (а не напрямую через JankStats), где находится вся логика управления состоянием и API.

Инициализация

Чтобы начать использовать библиотеку JankStats, сначала добавьте зависимость JankStats в файл Gradle:

implementation "androidx.metrics:metrics-performance:1.0.0-beta02" 

Затем инициализируйте и включите JankStats для каждого Window . Вам также следует приостановить отслеживание JankStats, когда действие переходит в фоновый режим. Создайте и включите объект JankStats в переопределениях вашей активности:

class JankLoggingActivity : AppCompatActivity() {      private lateinit var jankStats: JankStats       override fun onCreate(savedInstanceState: Bundle?) {         super.onCreate(savedInstanceState)         // ...         // metrics state holder can be retrieved regardless of JankStats initialization         val metricsStateHolder = PerformanceMetricsState.getHolderForHierarchy(binding.root)          // initialize JankStats for current window         jankStats = JankStats.createAndTrack(window, jankFrameListener)          // add activity name as state         metricsStateHolder.state?.putState("Activity", javaClass.simpleName)         // ...     }  

В приведенном выше примере информация о состоянии текущего действия вводится после создания объекта JankStats. Все будущие отчеты FrameData, созданные для этого объекта JankStats, теперь также включают информацию о действиях.

Метод JankStats.createAndTrack принимает ссылку на объект Window , который является прокси для иерархии представлений внутри этого Window , а также для самого Window . jankFrameListener вызывается в том же потоке, который используется для внутренней доставки этой информации с платформы в JankStats.

Чтобы включить отслеживание и создание отчетов для любого объекта JankStats, вызовите isTrackingEnabled = true . Хотя по умолчанию эта функция включена, приостановка действия отключает отслеживание. В этом случае обязательно включите отслеживание, прежде чем продолжить. Чтобы остановить отслеживание, вызовите isTrackingEnabled = false .

override fun onResume() {     super.onResume()     jankStats.isTrackingEnabled = true }  override fun onPause() {     super.onPause()     jankStats.isTrackingEnabled = false }  

Отчетность

Библиотека JankStats сообщает обо всех ваших отслеживаемых данных для каждого кадра в OnFrameListener для включенных объектов JankStats. Приложения могут хранить и объединять эти данные для последующей загрузки. Для получения дополнительной информации ознакомьтесь с примерами, представленными в разделе «Агрегация» .

Вам потребуется создать и предоставить OnFrameListener для вашего приложения, чтобы получать покадровые отчеты. Этот прослушиватель вызывается в каждом кадре для предоставления приложениям текущих данных о спаме.

private val jankFrameListener = JankStats.OnFrameListener { frameData ->     // A real app could do something more interesting, like writing the info to local storage and later on report it.     Log.v("JankStatsSample", frameData.toString()) }  

Прослушиватель предоставляет покадровую информацию о заторах с помощью объекта FrameData . Он содержит следующую информацию о запрошенном кадре:

  • isjank : логический флаг, указывающий, произошло ли зависание в кадре.
  • frameDurationUiNanos : продолжительность кадра (в наносекундах).
  • frameStartNanos : время начала кадра (в наносекундах).
  • states : состояние вашего приложения во время кадра.

Если вы используете Android 12 (уровень API 31) или выше, вы можете использовать следующее, чтобы предоставить больше данных о длительности кадров:

  • FrameDataApi24 предоставляет frameDurationCpuNanos для отображения времени, проведенного в частях кадра, не связанных с графическим процессором.
  • FrameDataApi31 предоставляет frameOverrunNanos для отображения количества времени после крайнего срока кадра, которое потребовалось для завершения кадра.

Используйте StateInfo в прослушивателе для хранения информации о состоянии приложения.

Обратите внимание, что OnFrameListener вызывается в том же потоке, который используется внутри для доставки покадровой информации в JankStats. В Android версии 6 (уровень API 23) и ниже это основной поток (UI). В Android версии 7 (уровень API 24) и выше это поток, созданный и используемый FrameMetrics. В любом случае важно обработать обратный вызов и быстро вернуться, чтобы предотвратить проблемы с производительностью в этом потоке.

Также обратите внимание, что объект FrameData, отправленный в обратном вызове, повторно используется в каждом кадре, чтобы избежать необходимости выделять новые объекты для отчета о данных. Это означает, что вы должны скопировать и кэшировать эти данные в другом месте, поскольку этот объект должен считаться устаревшим и устаревшим, как только обратный вызов вернется.

Агрегирование

Вероятно, вы захотите, чтобы код вашего приложения агрегировал данные по кадрам, что позволит вам сохранять и загружать информацию по своему усмотрению. Хотя подробности сохранения и загрузки выходят за рамки альфа-версии API JankStats, вы можете просмотреть предварительное действие по агрегированию покадровых данных в более крупную коллекцию с помощью JankAggregatorActivity , доступного в нашем репозитории GitHub .

JankAggregatorActivity использует класс JankStatsAggregator для наложения собственного механизма отчетов поверх механизма JankStats OnFrameListener , чтобы обеспечить абстракцию более высокого уровня для сообщения только о наборе информации, охватывающей множество кадров.

Вместо непосредственного создания объекта JankStats JankAggregatorActivity создает объект JankStatsAggregator , который внутри себя создает собственный объект JankStats:

class JankAggregatorActivity : AppCompatActivity() {      private lateinit var jankStatsAggregator: JankStatsAggregator       override fun onCreate(savedInstanceState: Bundle?) {         super.onCreate(savedInstanceState)         // ...         // Metrics state holder can be retrieved regardless of JankStats initialization.         val metricsStateHolder = PerformanceMetricsState.getHolderForHierarchy(binding.root)          // Initialize JankStats with an aggregator for the current window.         jankStatsAggregator = JankStatsAggregator(window, jankReportListener)          // Add the Activity name as state.         metricsStateHolder.state?.putState("Activity", javaClass.simpleName)     }  

Похожий механизм используется в JankAggregatorActivity для приостановки и возобновления отслеживания с добавлением события pause() в качестве сигнала для выдачи отчета с вызовом метода issueJankReport() , поскольку изменения жизненного цикла кажутся подходящим моментом для фиксации состояния джанк в приложении:

override fun onResume() {     super.onResume()     jankStatsAggregator.jankStats.isTrackingEnabled = true }  override fun onPause() {     super.onPause()     // Before disabling tracking, issue the report with (optionally) specified reason.     jankStatsAggregator.issueJankReport("Activity paused")     jankStatsAggregator.jankStats.isTrackingEnabled = false }  

Приведенный выше пример кода — это все, что нужно приложению для включения JankStats и получения данных кадра.

Управляйте государством

Возможно, вы захотите вызвать другие API для настройки JankStats. Например, внедрение информации о состоянии приложения делает данные кадра более полезными, предоставляя контекст для тех кадров, в которых происходит зависание.

Этот статический метод извлекает текущий объект MetricsStateHolder для данной иерархии представления.

PerformanceMetricsState.getHolderForHierarchy(view: View): MetricsStateHolder 

Можно использовать любое представление в активной иерархии. Внутренне это проверяет, существует ли существующий объект Holder , связанный с этой иерархией представлений. Эта информация кэшируется в представлении наверху этой иерархии. Если такого объекта не существует, getHolderForHierarchy() создает его.

Статический метод getHolderForHierarchy() позволяет избежать необходимости кэшировать где-то экземпляр держателя для последующего извлечения и упрощает извлечение существующего объекта состояния из любого места кода (или даже кода библиотеки, который в противном случае не имел бы доступа к оригинальный экземпляр).

Обратите внимание, что возвращаемое значение является объектом-держателем, а не самим объектом состояния. Значение объекта состояния внутри держателя устанавливается только JankStats. То есть, если приложение создает объект JankStats для окна, содержащего эту иерархию представлений, тогда создается и устанавливается объект состояния. В противном случае, если JankStats не отслеживает информацию, нет необходимости в объекте состояния, и коду приложения или библиотеки не обязательно вводить состояние.

Этот подход позволяет получить держатель, который затем может заполнить JankStats. Внешний код может запросить владелец в любое время. Вызывающие могут кэшировать облегченный объект Holder и использовать его в любое время для установки состояния, в зависимости от значения его внутреннего свойства state , как в примере кода ниже, где состояние устанавливается только тогда, когда внутреннее свойство состояния держателя не равно нулю:

val metricsStateHolder = PerformanceMetricsState.getHolderForHierarchy(binding.root) // ... metricsStateHolder.state?.putState("Activity", javaClass.simpleName) 

Чтобы управлять состоянием пользовательского интерфейса/приложения, приложение может внедрить (или удалить) состояние с помощью методов putState и removeState . JankStats регистрирует временные метки этих вызовов. Если кадр перекрывает время начала и окончания состояния, JankStats сообщает эту информацию о состоянии вместе с данными о времени для кадра.

Для любого состояния добавьте две части информации: key (категория состояния, например «RecyclerView») и value (информация о том, что происходило в данный момент, например «прокрутка»).

Удалите состояния с помощью метода removeState() , когда это состояние больше не является допустимым, чтобы гарантировать, что вместе с данными кадра не будет сообщаться неверная или вводящая в заблуждение информация.

Вызов putState() с добавленным ранее key заменяет существующее value этого состояния новым.

Версия API состояния putSingleFrameState() добавляет состояние, которое регистрируется только один раз, в следующем отчетном кадре. После этого система автоматически удалит его, гарантируя, что в вашем коде случайно не окажется устаревшего состояния. Обратите внимание, что не существует эквивалента removeState() для SingleFrame, поскольку JankStats автоматически удаляет состояния одного кадра.

private val scrollListener = object : RecyclerView.OnScrollListener() {     override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {         // check if JankStats is initialized and skip adding state if not         val metricsState = metricsStateHolder?.state ?: return          when (newState) {             RecyclerView.SCROLL_STATE_DRAGGING -> {                 metricsState.putState("RecyclerView", "Dragging")             }             RecyclerView.SCROLL_STATE_SETTLING -> {                 metricsState.putState("RecyclerView", "Settling")             }             else -> {                 metricsState.removeState("RecyclerView")             }         }     } } 

Обратите внимание, что ключ, используемый для состояний, должен быть достаточно значимым, чтобы обеспечить возможность последующего анализа. В частности, поскольку состояние с тем же key , что было добавлено ранее, заменит это предыдущее значение, вам следует попытаться использовать уникальные имена key для объектов, которые могут иметь разные экземпляры в вашем приложении или библиотеке. Например, приложение с пятью различными RecyclerViews может захотеть предоставить идентифицируемые ключи для каждого из них вместо того, чтобы просто использовать RecyclerView для каждого из них, а затем не иметь возможности легко определить в результирующих данных, к какому экземпляру относятся данные кадра.

Янковая эвристика

Чтобы настроить внутренний алгоритм определения того, что считается мусором, используйте свойство jankHeuristicMultiplier .

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

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

Использование в Jetpack Compose

В настоящее время для использования JankStats в Compose требуется очень мало настроек. Чтобы сохранить PerformanceMetricsState при изменении конфигурации, запомните его следующим образом:

/**  * Retrieve MetricsStateHolder from compose and remember until the current view changes.  */ @Composable fun rememberMetricsStateHolder(): PerformanceMetricsState.Holder {     val view = LocalView.current     return remember(view) { PerformanceMetricsState.getHolderForHierarchy(view) } }  

А чтобы использовать JankStats, добавьте текущее состояние в stateHolder , как показано здесь:

val metricsStateHolder = rememberMetricsStateHolder()  // Reporting scrolling state from compose should be done from side effect to prevent recomposition. LaunchedEffect(metricsStateHolder, listState) {     snapshotFlow { listState.isScrollInProgress }.collect { isScrolling ->         if (isScrolling) {             metricsStateHolder.state?.putState("LazyList", "Scrolling")         } else {             metricsStateHolder.state?.removeState("LazyList")         }     } }  

Для получения полной информации об использовании JankStats в вашем приложении Jetpack Compose ознакомьтесь с нашим примером приложения производительности .

Оставьте отзыв

Поделитесь с нами своими отзывами и идеями через эти ресурсы:

Трекер проблем
Сообщайте о проблемах, чтобы мы могли исправить ошибки.
{% дословно %} {% дословно %} {% дословно %} {% дословно %}