A biblioteca JankStats ajuda a monitorar e analisar problemas de desempenho nos seus aplicativos. "Jank" (instabilidade) se refere aos frames do aplicativo que levam muito tempo para renderizar, e a biblioteca JankStats fornece relatórios sobre as estatísticas de instabilidade do seu app.
Recursos
A JankStats é baseada nos recursos da Plataforma Android, incluindo a API FrameMetrics do Android 7 (nível 24 da API) e versões mais recentes ou OnPreDrawListener em versões anteriores. Esses mecanismos podem ajudar os aplicativos a monitorar o tempo necessário para que os frames sejam concluídos. Essa biblioteca oferece dois outros recursos que a tornam mais dinâmica e fácil de usar: a heurística de instabilidade e o estado da interface.
Heurística de instabilidade
Embora você possa usar a FrameMetrics para monitorar a duração do frame, ela não oferece nenhuma ajuda para determinar a instabilidade em si. No entanto, a JankStats tem mecanismos internos configuráveis para determinar quando ocorre a instabilidade, tornando os relatórios úteis de modo mais imediato.
Estado da interface
Muitas vezes, é necessário conhecer o contexto dos problemas de performance no app. Por exemplo, se você desenvolver um app complexo multitelas que usa a FrameMetrics e descobrir que ele costuma ter frames extremamente instáveis, vai precisar contextualizar essas informações entendendo onde o problema ocorreu, o que o usuário estava fazendo e como replicar a situação.
A JankStats resolve esse problema introduzindo uma API state
, que possibilita
a comunicação com a biblioteca para fornecer informações sobre a atividade no app. Quando
a JankStats registra informações sobre um frame instável, ela inclui o estado atual do
aplicativo em relatórios de instabilidade.
Uso
Para começar a usar a JankStats, instancie e ative a biblioteca para cada
Window
. Cada objeto JankStats acompanha os dados
em apenas uma Window
. Para instanciar a biblioteca é necessário uma instância Window
e um listener OnFrameListener
,
que são usados para enviar métricas ao cliente. O listener é chamado com
a classe FrameData
em cada frame
e detalha:
- o horário de início do frame;
- os valores de duração;
- se o frame deve ou não ser considerado instável;
- um conjunto de pares de strings contendo informações sobre o estado do aplicativo durante o frame.
Para tornar a JankStats mais útil, os aplicativos precisam preencher a biblioteca com
informações de estado da interface relevantes para a geração de relatórios na classe FrameData. Você pode fazer isso
usando a
API PerformanceMetricsState
(não diretamente a JankStats), que contém todas
as APIs e a lógica de gerenciamento de estado.
Inicialização
Para começar a usar a biblioteca JankStats, primeiro adicione a dependência JankStats ao seu arquivo do Gradle:
implementation "androidx.metrics:metrics-performance:1.0.0-beta01"
Em seguida, inicialize e ative a JankStats para cada Window
. Também é necessário pausar
o monitoramento da JankStats quando uma atividade entra em segundo plano. Crie e ative
o objeto JankStats nas substituições da atividade:
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)
// ...
}
O exemplo acima injeta informações de estado sobre a atividade atual depois de ela construir o objeto JankStats. Todos os relatórios da FrameData futuros criados para esse objeto JankStats agora também incluem informações sobre a atividade.
O método JankStats.createAndTrack
usa uma referência a um objeto
Window
, que é um proxy para a hierarquia de visualização dentro dessa Window
, assim
como para a própria Window
. O jankFrameListener
é chamado na mesma linha de execução usada
para enviar essas informações da plataforma à JankStats internamente.
Para ativar o monitoramento e a geração de relatórios em qualquer objeto JankStats,
chame isTrackingEnabled = true
. Embora ele fique ativado por padrão,
a pausa de uma atividade desativa o monitoramento. Nesse caso, ative
novamente antes de continuar. Para interromper o rastreamento, chame isTrackingEnabled = false
.
override fun onResume() {
super.onResume()
jankStats.isTrackingEnabled = true
}
override fun onPause() {
super.onPause()
jankStats.isTrackingEnabled = false
}
Relatórios
A biblioteca JankStats relata todo o monitoramento dos dados, para cada frame, ao
OnFrameListener
para objetos JankStats ativados. Os apps podem armazenar e agregar esses
dados para upload em outro momento. Para mais informações, confira
os exemplos fornecidos na seção Agregação.
É preciso criar e fornecer o OnFrameListener
para que o app receba
os relatórios por frame. Esse listener é chamado em cada frame para fornecer dados de instabilidade
contínuos aos apps.
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())
}
O listener fornece informações por frame sobre instabilidade com o
objeto FrameData
. Ele
contém as seguintes informações sobre o frame solicitado:
isjank
: uma flag booleana que indica se a instabilidade ocorreu no frame.frameDurationUiNanos
: duração do frame (em nanossegundos).frameStartNanos
: horário em que o frame começou (em nanossegundos).states
: estado do seu app durante o frame.
Se você está usando o Android 12 (nível 31 da API) ou versões mais recentes, pode usar o seguinte para expor mais dados sobre a duração de frames:
FrameDataApi24
forneceframeDurationCpuNanos
para mostrar o tempo gasto nas partes não GPU do frame.FrameDataApi31
forneceframeOverrunNanos
para exibir o tempo decorrido até o término do frame.
Use StateInfo
no
listener para armazenar informações sobre o estado do aplicativo.
Observe que o OnFrameListener
é chamado na mesma linha de execução usada internamente para
enviar as informações por frame para a JankStats.
No Android 6 (nível 23 da API) e versões anteriores, ela corresponde à linha de execução principal de interface.
No Android 7 (nível 24 da API) e versões mais recentes,
corresponde à linha de execução criada e usada pela FrameMetrics. Nos dois casos, é importante
processar o callback e retornar rapidamente para evitar problemas de desempenho nessa
linha de execução.
Além disso, o objeto FrameData enviado no callback é reutilizado em cada frame para que novos objetos não precisem ser alocados para gerar relatórios de dados. Isso significa que é necessário copiar e armazenar em cache esses dados em outro lugar, porque esse objeto precisa ser considerado inativo e obsoleto assim que o callback retornar.
Agregação
O código do app provavelmente vai agregar os dados por frame, o que permite
salvar e fazer upload das informações por conta própria. Embora os detalhes
sobre como salvar e fazer upload estejam além do escopo da versão Alfa da API JankStats, você
pode conferir uma atividade preliminar para agregar dados por frame em uma coleção
maior usando a JankAggregatorActivity
disponível no nosso
Repositório do GitHub (link em inglês).
A JankAggregatorActivity
(link em inglês) usa a classe JankStatsAggregator
para sobrepor o próprio mecanismo
de geração de relatórios sobre o mecanismo OnFrameListener
da JankStats para fornecer uma
abstração de nível superior e relatar apenas uma coleta de informações que
abrange vários frames.
Em vez de criar um objeto JankStats diretamente, a JankAggregatorActivity
cria
um objeto JankStatsAggregator (link em inglês),
que cria o próprio objeto JankStats de forma interna:
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)
}
Um mecanismo parecido é usado na JankAggregatorActivity
para pausar e
retomar o rastreamento, com o acréscimo do evento pause()
como um indicador para emitir
um relatório com uma chamada para issueJankReport()
, já que as mudanças do ciclo de vida parecem ser um
momento adequado para capturar o estado de instabilidade no aplicativo:
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
}
O código de exemplo acima é tudo que um app precisa para ativar a JankStats e receber dados de frame.
Gerenciamento do estado
É possível chamar outras APIs para personalizar a JankStats. Por exemplo, injetar informações de estado do app faz com que os dados do frame sejam mais úteis, fornecendo contexto para os frames em que a instabilidade ocorre.
Esse método estático recupera o
MetricsStateHolder
objeto para uma determinada hierarquia de visualização.
PerformanceMetricsState.getHolderForHierarchy(view: View): MetricsStateHolder
Qualquer visualização em uma hierarquia ativa pode ser usada. Internamente, isso confere
se há um objeto Holder
associado a essa
hierarquia de visualização. Essas informações são armazenadas em cache em uma visualização na parte de cima da
hierarquia. Se não houver nenhum objeto assim, o getHolderForHierarchy()
vai criar um.
O método estático getHolderForHierarchy()
permite evitar o armazenamento em cache da instância para depois, além de facilitar a recuperação de um
objeto de estado de qualquer lugar no código, ou até mesmo do código da biblioteca, que de outra forma não teria
acesso à instância original.
Observe que valor de retorno é um objeto detentor, não o próprio objeto de estado. O valor do objeto de estado dentro do detentor é definido apenas pela JankStats. Ou seja, se um aplicativo cria um objeto JankStats para a janela que contém essa hierarquia de visualização, o objeto de estado é criado e definido. Se a JankStats não monitorar as informações, o objeto de estado não vai ser necessário, e o código do app ou da biblioteca não vai precisar injetar o estado.
Essa abordagem permite a
recuperação de um detentor que a JankStats pode preencher. O código externo
pode solicitar o detentor a qualquer momento. Os autores da chamada podem armazenar o objeto leve Holder
em cache e usá-lo a qualquer momento para definir o estado, dependendo do valor da
propriedade interna state
dele, como no exemplo de código abaixo, em que o estado é definido apenas
quando a propriedade do estado interno do detentor não é nula:
val metricsStateHolder = PerformanceMetricsState.getHolderForHierarchy(binding.root)
// ...
metricsStateHolder.state?.putState("Activity", javaClass.simpleName)
Para controlar o estado da interface ou do app, um app pode injetar (ou remover) um estado
com os métodos putState
e removeState
. A JankStats registra o carimbo de data/hora
dessas chamadas. Se um frame se sobrepõe aos horários de início e término do estado,
a JankStats relata essa informação com os dados de tempo do
frame.
Para qualquer estado, adicione duas informações: key
,
uma categoria de estado (como "RecyclerView"), e value
, informações sobre
o que estava acontecendo no momento (como "rolagem").
Remova estados usando o método removeState()
quando eles não forem
mais válidos para garantir que informações incorretas ou enganosas não sejam relatadas
com os dados do frame.
Ao chamar putState()
usando uma key
adicionada anteriormente, o
value
existente do estado é substituído pelo novo.
A versão putSingleFrameState()
da API de estado adiciona um estado que é
registrado apenas uma vez no próximo frame relatado. O sistema o remove
de forma automática depois disso, garantindo que você não tenha acidentalmente um estado obsoleto no
seu código. Observe que não há um singleFrame equivalente de
removeState()
, já que a JankStats remove os estados de frames únicos automaticamente.
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")
}
}
}
}
A chave usada para os estados precisa ter informações suficientes para permitir uma
análise futura. Como um estado com a mesma key
já adicionada substitui o
valor anterior, tente usar
nomes de key
exclusivos para objetos que possam ter instâncias diferentes no
app ou na biblioteca. Por exemplo, um app com cinco RecyclerViews diferentes pode
querem fornecer chaves identificáveis para cada um deles em vez de simplesmente usar
RecyclerView
para cada um e, em seguida, não conseguir distinguir facilmente no
dados resultantes a qual instância os dados de frame se referem.
Heurística de instabilidade
Para ajustar o algoritmo interno para determinar o que é considerado instabilidade, use
a propriedade jankHeuristicMultiplier
.
Por padrão, o sistema define instabilidade como um frame que leva duas vezes mais tempo na renderização quanto à a taxa de atualização atual. Ele não trata a instabilidade como nada além da taxa de atualização porque as informações sobre o tempo de renderização do app não estão totalmente claras. Por isso, é melhor adicionar um buffer e relatar dificuldades apenas quando houver problemas de desempenho perceptíveis.
Os dois valores podem ser mudados usando esses métodos para se adaptar à situação do app de maneira mais precisa ou em testes para forçar a ocorrência ou não da instabilidade, conforme necessário.
Uso no Jetpack Compose
No momento, há pouca configuração necessária para usar o JankStats no Compose.
Para manter o PerformanceMetricsState
nas mudanças de
configuração, faça o seguinte:
/**
* 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) }
}
E, para usar o JankStats, adicione o estado atual ao stateHolder
, conforme mostrado aqui:
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")
}
}
}
Para ver detalhes completos sobre o uso do JankStats no app Jetpack Compose, consulte nosso app de exemplo de desempenho.
Enviar feedback
Envie comentários e ideias usando os recursos abaixo:
- Issue tracker
- Informe os problemas para que possamos corrigir os bugs. .
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado.
- Criar perfis de referência {:#creating-profile-rules}
- Argumentos de instrumentação de Microbenchmark
- Argumentos de instrumentação da biblioteca Macrobenchmark