Библиотека 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-beta01"
Затем инициализируйте и включите 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 ознакомьтесь с нашим примером приложения производительности .
Оставьте отзыв
Поделитесь с нами своими отзывами и идеями через эти ресурсы:
- Трекер проблем
- Сообщайте о проблемах, чтобы мы могли исправить ошибки.
Рекомендуется для вас
- Примечание: текст ссылки отображается, когда JavaScript отключен.
- Создание базовых профилей {:#creating-profile-rules}
- Аргументы инструментария Microbenchmark
- Аргументы инструментария Macrobenchmark