Com o uso de ExerciseClient
, os Recursos de saúde oferecem suporte de primeira classe a
apps de treino.
E graças ao ExerciseClient
, seu app pode controlar quando um
exercício está em andamento, adicionar metas de exercício e receber atualizações sobre o estado do
exercício, eventos de exercício
ou outras métricas desejadas. Para saber mais, consulte a lista completa de
tipos de exercício
com suporte aos Recursos de saúde.
Consulte a Exemplo de exercício no GitHub.
Adicionar dependências
Para adicionar uma dependência aos Recursos de saúde, é preciso adicionar o repositório Maven do Google ao seu projeto. Para mais informações, consulte a seção Repositório Maven do Google.
Em seguida, no arquivo build.gradle
do módulo, adicione a dependência abaixo:
Groovy
dependencies { implementation "androidx.health:health-services-client:1.1.0-alpha04" }
Kotlin
dependencies { implementation("androidx.health:health-services-client:1.1.0-alpha04") }
Estrutura do app
Use a estrutura de apps abaixo ao criar um app de exercícios com os Recursos de saúde:
- Mantenha suas telas e a navegação dentro de uma atividade principal.
- Gerencie o estado de treino, os dados do sensor, a atividade em andamento e os dados com um serviço em primeiro plano.
- Armazene dados com o Room e use o WorkManager para fazer upload deles.
Ao se preparar para um treino e durante o treino, sua atividade
pode ser interrompida por vários motivos. O usuário pode alternar para outro app ou voltar
ao mostrador do relógio. O sistema pode mostrar algo por cima da
atividade ou a tela pode ser desligada após um período de inatividade.
Use um ForegroundService
em execução contínua com ExerciseClient
para garantir o funcionamento correto durante todo o
treino.
O uso de um ForegroundService
possibilita usar a API Ongoing Activity para mostrar
um indicador na superfície do relógio, permitindo que o usuário retorne rapidamente ao
treino.
É essencial solicitar os dados de local corretamente no serviço em primeiro plano. No seu arquivo de manifesto, especifique o serviço em primeiro plano necessário tipos e permissões:
<manifest ...> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <application ...> <!-- If your app is designed only for devices that run Wear OS 4 or lower, use android:foregroundServiceType="location" instead. --> <service android:name=".MyExerciseSessionRecorder" android:foregroundServiceType="health|location"> </service> </application> </manifest>
Use
AmbientLifecycleObserver
para a atividade de pré-treino, que contém a chamada prepareExercise()
, e
para a atividade de treino. No entanto, não atualize a tela durante o treino
no modo ambiente. Os Recursos de saúde agrupam dados de treino
quando a tela do dispositivo está no modo ambiente para economizar energia. Por isso, as informações
mostradas podem não ser recentes. Durante os treinos, mostre dados que façam sentido para o
usuário, com informações atualizadas ou uma tela em branco.
Verificar os recursos
Cada ExerciseType
oferece suporte a determinados tipos de dados para métricas e
metas de exercício. Verifique esses recursos na inicialização porque eles podem variar dependendo
do dispositivo. Um dispositivo pode não oferecer suporte a um determinado tipo de exercício ou não ter
funções específicas, como a pausa automática. Além disso, os recursos de um dispositivo
podem mudar com o tempo, por exemplo, após uma atualização de software.
Na inicialização do app, consulte os recursos do dispositivo para armazenar e processar estas informações:
- Os exercícios com suporte na plataforma.
- Os recursos com suporte em cada exercício.
- Os tipos de dados com suporte em cada exercício.
- As permissões necessárias para cada um desses tipos de dados.
Use ExerciseCapabilities.getExerciseTypeCapabilities()
com
seu tipo de exercício desejado para conferir quais tipos de métrica você pode solicitar, quais
metas de exercícios podem ser configuradas e que outros recursos estão disponíveis para
esse tipo. Isso é mostrado neste exemplo:
val healthClient = HealthServices.getClient(this /*context*/)
val exerciseClient = healthClient.exerciseClient
lifecycleScope.launch {
val capabilities = exerciseClient.getCapabilitiesAsync().await()
if (ExerciseType.RUNNING in capabilities.supportedExerciseTypes) {
runningCapabilities =
capabilities.getExerciseTypeCapabilities(ExerciseType.RUNNING)
}
}
Dentro dos ExerciseTypeCapabilities
retornados,
os supportedDataTypes
listam os tipos de dados para os quais você pode solicitar dados. Isso varia de acordo com o dispositivo.
Se você solicitar um DataType
sem suporte, sua solicitação poderá
falhar.
Use os campos
supportedGoals
e
supportedMilestones
para determinar se o exercício oferece suporte à meta que você
quer criar.
Se o app permitir que o usuário use a pausa automática, confira
se essa funcionalidade tem suporte do dispositivo usando
supportsAutoPauseAndResume
.
ExerciseClient
rejeita solicitações que não tenham suporte do
dispositivo.
O exemplo a seguir consulta o suporte ao tipo de dados HEART_RATE_BPM
,
o recurso de meta STEPS_TOTAL
e a funcionalidade de pausa automática:
// Whether we can request heart rate metrics.
supportsHeartRate = DataType.HEART_RATE_BPM in runningCapabilities.supportedDataTypes
// Whether we can make a one-time goal for aggregate steps.
val stepGoals = runningCapabilities.supportedGoals[DataType.STEPS_TOTAL]
supportsStepGoals =
(stepGoals != null && ComparisonType.GREATER_THAN_OR_EQUAL in stepGoals)
// Whether auto-pause is supported.
val supportsAutoPause = runningCapabilities.supportsAutoPauseAndResume
Registro para atualizações do estado do exercício
As atualizações de exercícios são enviadas ao listener. Seu app só pode registrar um único listener por vez. Configure o listener antes de iniciar o treino, conforme mostrado no exemplo a seguir. Seu listener só recebe atualizações sobre exercícios do app.
val callback = object : ExerciseUpdateCallback {
override fun onExerciseUpdateReceived(update: ExerciseUpdate) {
val exerciseStateInfo = update.exerciseStateInfo
val activeDuration = update.activeDurationCheckpoint
val latestMetrics = update.latestMetrics
val latestGoals = update.latestAchievedGoals
}
override fun onLapSummaryReceived(lapSummary: ExerciseLapSummary) {
// For ExerciseTypes that support laps, this is called when a lap is marked.
}
override fun onAvailabilityChanged(
dataType: DataType<*, *>,
availability: Availability
) {
// Called when the availability of a particular DataType changes.
when {
availability is LocationAvailability -> // Relates to Location/GPS.
availability is DataTypeAvailability -> // Relates to another DataType.
}
}
}
exerciseClient.setUpdateCallback(callback)
Gerenciar o ciclo de vida do exercício
Os Recursos de saúde oferecem suporte a, no máximo, um exercício por vez em todos os apps no dispositivo. Caso um exercício esteja sendo monitorado e outro app comece a monitorar um novo exercício, o primeiro é encerrado.
Antes de iniciar o exercício, faça o seguinte:
- Confira se um exercício já está sendo monitorado e proceda da forma certa. Por exemplo, solicite a confirmação do usuário antes de substituir o exercício anterior e começar a monitorar um novo.
O exemplo abaixo mostra como verificar se há um exercício usando
getCurrentExerciseInfoAsync
:
lifecycleScope.launch {
val exerciseInfo = exerciseClient.getCurrentExerciseInfoAsync().await()
when (exerciseInfo.exerciseTrackedStatus) {
OTHER_APP_IN_PROGRESS -> // Warn user before continuing, will stop the existing workout.
OWNED_EXERCISE_IN_PROGRESS -> // This app has an existing workout.
NO_EXERCISE_IN_PROGRESS -> // Start a fresh workout.
}
}
Permissões
Ao usar o ExerciseClient
, confira se o app solicita e mantém a
as permissões necessárias.
Caso seu app use dados de LOCATION
, ele precisa solicitar e manter os
as permissões apropriadas para isso também.
Para todos os tipos de dados, antes de chamar prepareExercise()
ou startExercise()
,
faça o seguinte:
- Especifique as permissões adequadas para os tipos de dados solicitados no arquivo
AndroidManifest.xml
. - Verifique se o usuário concedeu as permissões necessárias. Para mais informações, consulte Solicitar permissões do app. O serviço Recursos de saúde vai rejeitar a solicitação, se as permissões necessárias ainda não tiverem sido concedidas.
Para dados de local, siga estas outras etapas:
- Confira se o GPS está ativado no dispositivo, usando
isProviderEnabled(LocationManager.GPS_PROVIDER)
. Peça que o usuário abra as configurações de localização se necessário. - Um
ForegroundService
com oforegroundServiceType
adequado deve ser mantido durante todo o treino.
Preparar um treino
Alguns sensores, como GPS ou frequência cardíaca, podem demorar um pouco para começar a funcionar ou o usuário
pode querer conferir dados antes de iniciar o treino. O método opcional
prepareExerciseAsync()
permite que esses sensores comecem a funcionar e que os dados sejam recebidos sem iniciar
o cronômetro do treino. A activeDuration
não é afetada por esse
tempo de preparação.
Antes de fazer a chamada para prepareExerciseAsync()
, confira o seguinte:
Confira a configuração de localização para toda a plataforma. O usuário controla essa configuração no menu principal do app. Ela é diferente da verificação de permissões do app.
Se a configuração estiver desativada, notifique o usuário de que ele negou acesso à localização e solicite a ativação quando for necessário.
Verifique se o app tem permissões de execução para sensores corporais, reconhecimento de atividades e localização exata. Se não tiver, solicite ao usuário permissões de execução que forneçam o contexto adequado. Se o usuário não conceder uma permissão específica, remova os tipos de dados associados a essa permissão da chamada para
prepareExerciseAsync()
. Se as permissões de localização e o sensor corporal não forem fornecidos, não chameprepareExerciseAsync()
, já que a chamada de preparo serve especificamente para conseguir uma frequência cardíaca estável ou o sinal do GPS antes de iniciar um exercício. O app ainda pode conseguir extrair a distância, o ritmo e a velocidade com base em passos e outras métricas que não exigem essas permissões.
Faça o seguinte para garantir que sua chamada para prepareExerciseAsync()
seja bem-sucedida:
- Use
AmbientLifecycleObserver
para a atividade de pré-treino que contém a chamada de preparo. - Chame
prepareExerciseAsync()
no seu serviço em primeiro plano. A preparação do sensor pode ser eliminada desnecessariamente se não estiver em um serviço e estiver vinculada ao ciclo de vida da atividade. - Chame
endExercise()
para desativar os sensores e reduzir o uso de energia, se o usuário sair da atividade antes do treino.
O exemplo a seguir mostra como chamar prepareExerciseAsync()
:
val warmUpConfig = WarmUpConfig(
ExerciseType.RUNNING,
setOf(
DataType.HEART_RATE_BPM,
DataType.LOCATION
)
)
// Only necessary to call prepareExerciseAsync if body sensor or location
//permissions are given
exerciseClient.prepareExerciseAsync(warmUpConfig).await()
// Data and availability updates are delivered to the registered listener.
Quando o app está no estado PREPARING
, as atualizações de disponibilidade do sensor são
entregues no ExerciseUpdateCallback
pelo onAvailabilityChanged()
.
Essas informações
podem ser apresentadas ao usuário para que ele decida se quer iniciar o
treino.
Iniciar o treino
Quando quiser iniciar um exercício, crie um objeto ExerciseConfig
para configurar o
tipo de exercício, os tipos de dados para os quais você quer receber métricas e quaisquer
metas ou marcos de exercício.
Metas de exercício consistem em um DataType
e uma
condição. Elas são um objetivo único, que é acionado quando uma
condição é atendida (por exemplo, quando o usuário corre determinada distância). Um marco de
exercício também pode ser definido. Os marcos podem ser acionados várias vezes,
como cada vez que o usuário
atinge um determinado ponto após a distância definida.
O exemplo abaixo mostra como criar uma meta para cada tipo:
const val CALORIES_THRESHOLD = 250.0
const val DISTANCE_THRESHOLD = 1_000.0 // meters
suspend fun startExercise() {
// Types for which we want to receive metrics.
val dataTypes = setOf(
DataType.HEART_RATE_BPM,
DataType.CALORIES_TOTAL,
DataType.DISTANCE
)
// Create a one-time goal.
val calorieGoal = ExerciseGoal.createOneTimeGoal(
DataTypeCondition(
dataType = DataType.CALORIES_TOTAL,
threshold = CALORIES_THRESHOLD,
comparisonType = ComparisonType.GREATER_THAN_OR_EQUAL
)
)
// Create a milestone goal. To make a milestone for every kilometer, set the initial
// threshold to 1km and the period to 1km.
val distanceGoal = ExerciseGoal.createMilestone(
condition = DataTypeCondition(
dataType = DataType.DISTANCE_TOTAL,
threshold = DISTANCE_THRESHOLD,
comparisonType = ComparisonType.GREATER_THAN_OR_EQUAL
),
period = DISTANCE_THRESHOLD
)
val config = ExerciseConfig(
exerciseType = ExerciseType.RUNNING,
dataTypes = dataTypes,
isAutoPauseAndResumeEnabled = false,
isGpsEnabled = true,
exerciseGoals = mutableListOf<ExerciseGoal<Double>>(calorieGoal, distanceGoal)
)
exerciseClient.startExerciseAsync(config).await()
}
Você também pode marcar voltas para todos os exercícios. Os Recursos de saúde fornecem um
ExerciseLapSummary
com métricas agregadas no período da volta.
O exemplo anterior mostra o uso de isGpsEnabled
, que precisa ser definido como verdadeiro
ao solicitar dados de local. No entanto, o uso de GPS também pode ajudar com outras métricas.
Se a ExerciseConfig
especificar a distância como um DataType
, o padrão será
usar etapas para estimar a distância. Se você ativar o GPS, as informações de localização
poderão ser usadas para estimar a distância.
Pausar, retomar e encerrar um treino
É possível pausar, retomar e encerrar os treinos usando o método adequado, como
pauseExerciseAsync()
ou
endExerciseAsync()
.
Use o estado de ExerciseUpdate
como a fonte da verdade. O treino não é
considerado como pausado quando a chamada para pauseExerciseAsync()
é retornada, mas quando
esse estado é refletido na mensagem ExerciseUpdate
. Quando se trata de estados da interface,
é muito importante pensar nisso. Se o usuário pressionar "pausar",
você precisará desativar o botão de pausa e chamar pauseExerciseAsync()
nos
Recursos de saúde. Aguarde até que os Recursos de saúde cheguem ao estado
"pausado" usando ExerciseUpdate.exerciseStateInfo.state
e toque no botão
para "retomar". Isso ocorre porque as atualizações de estado dos Recursos de saúde podem levar mais tempo
para serem entregues do que o pressionamento do botão. Se você vincular todas as mudanças de interface aos pressionamentos de botão,
poderá haver uma dessincronização da interface com o estado dos Recursos de saúde.
Não esqueça:
- A pausa automática está ativada: o treino pode ser pausado ou iniciado sem a interação do usuário.
- Outro app inicia um treino: seu treino pode ser encerrado sem a interação do usuário.
Seu app precisa reagir corretamente quando um treino dele é encerrado por outro app.
- Salvar o estado parcial de treino para que o progresso de um usuário não seja apagado.
- Remover o ícone de atividade em andamento e enviar ao usuário uma notificação informando que o treino foi encerrado por outro app.
Quando permissões forem revogadas durante um exercício, seu app também precisa reagir. Essa informação é enviada pelo estado isEnded
, com um
ExerciseEndReason
de AUTO_END_PERMISSION_LOST
. Lide com essa situação de forma semelhante ao
caso de encerramento: salve o estado parcial, remova o ícone da atividade em andamento
e envie uma notificação sobre o que aconteceu com o usuário.
O exemplo a seguir mostra como verificar o encerramento corretamente:
val callback = object : ExerciseUpdateCallback {
override fun onExerciseUpdateReceived(update: ExerciseUpdate) {
if (update.exerciseStateInfo.state.isEnded) {
// Workout has either been ended by the user, or otherwise terminated
}
...
}
...
}
Gerenciar a duração ativa
Durante um exercício, o app pode mostrar a duração ativa
do treino. O app, os Recursos de saúde e a unidade de controle micro do dispositivo
(MCU) — a baixa potência
responsável pelo monitoramento de atividade física, todos precisam estar sincronizados,
a mesma duração ativa atual. Para ajudar a gerenciar isso, o Recursos de Saúde envia um ActiveDurationCheckpoint
,
que fornece um ponto de fixação que o app pode usar para
iniciar o timer.
Como a duração ativa é enviada pelo MCU e pode demorar um
pouco para chegar ao app, ActiveDurationCheckpoint
contém duas propriedades:
activeDuration
: por quanto tempo o exercício está ativotime
: quando a duração ativa foi calculada
No app, a duração ativa de um exercício pode ser
calculada com ActiveDurationCheckpoint
usando a equação abaixo:
(now() - checkpoint.time) + checkpoint.activeDuration
Esse cálculo considera o pequeno delta entre a duração ativa, que é calculada no MCU, e a chegada ao app e pode ser usado para propagar um cronômetro no app, além de garantir que o timer esteja perfeitamente alinhado com o horário dos Recursos de saúde e do MCU.
Se o exercício for pausado, o app vai esperar para reiniciar o timer na interface
até que o tempo calculado tenha passado do tempo que a interface está mostrando no momento.
Isso ocorre porque o sinal de pausa chega aos Recursos de saúde e ao MCU com
um pequeno atraso. Por exemplo, se o app pausa em t=10 segundos, os Recursos de Saúde podem não fornecer a atualização PAUSED
ao app até t=10,2 segundos.
Trabalhar com dados do ExerciseClient
As métricas dos tipos de dados que o app registrou são entregues em
mensagens
ExerciseUpdate
.
O processador entrega mensagens somente quando ativado ou ao atingir um período máximo de
geração de relatórios, por exemplo, a cada 150 segundos. Não dependa da frequência de
ExerciseUpdate
para avançar um cronômetro com a
activeDuration
. Consulte a
Exemplo de exercício
no GitHub para conferir um exemplo de como implementar um cronômetro independente.
Quando um usuário inicia um treino, mensagens ExerciseUpdate
podem ser entregues
com frequência, por exemplo, a cada segundo. Quando o usuário inicia o treino, a tela pode
ser desligada. Os Recursos de saúde podem fornecer dados com uma frequência menor, mesmo que a amostragem continue sendo feita na mesma
frequência, para evitar a ativação do processador principal. Quando o usuário
confere a tela, todos os dados no processo de envio em lote são imediatamente
enviados ao app.
Controlar a taxa de lotes
Em alguns casos, convém controlar a frequência com que seu app
recebe determinados tipos de dados enquanto a tela está desligada. Um objeto
BatchingMode
permite que seu app substitua o comportamento de lote padrão para receber entregas
de dados com mais frequência.
Para configurar a taxa de lotes, siga estas etapas:
Confira se a definição
BatchingMode
específica tem suporte no dispositivo:// Confirm BatchingMode support to control heart rate stream to phone. suspend fun supportsHrWorkoutCompanionMode(): Boolean { val capabilities = exerciseClient.getCapabilities() return BatchingMode.HEART_RATE_5_SECONDS in capabilities.supportedBatchingModeOverrides }
Especifique que o objeto
ExerciseConfig
precisa usar umBatchingMode
específico, conforme mostrado no snippet de código abaixo.val config = ExerciseConfig( exerciseType = ExerciseType.WORKOUT, dataTypes = setOf( DataType.HEART_RATE_BPM, DataType.TOTAL_CALORIES ), // ... batchingModeOverrides = setOf(BatchingMode.HEART_RATE_5_SECONDS) )
Também é possível configurar
BatchingMode
dinamicamente no treino em vez de ter um comportamento de lote específico durante todo o treino:val desiredModes = setOf(BatchingMode.HEART_RATE_5_SECONDS) exerciseClient.overrideBatchingModesForActiveExercise(desiredModes)
Para limpar o
BatchingMode
personalizado e retornar ao comportamento padrão, transmita um conjunto vazio paraexerciseClient.overrideBatchingModesForActiveExercise()
.
Carimbos de data/hora
O ponto no tempo de cada ponto de dados representa a duração desde a inicialização do dispositivo. Para converter isso em um carimbo de data/hora, faça o seguinte:
val bootInstant =
Instant.ofEpochMilli(System.currentTimeMillis() - SystemClock.elapsedRealtime())
Esse valor pode ser usado com getStartInstant()
ou getEndInstant()
para cada ponto de dados.
Precisão de dados
Alguns tipos de dados podem ter informações de precisão associadas a cada ponto de dados,
representadas na propriedade accuracy
.
As classes HrAccuracy
e LocationAccuracy
podem ser preenchidas para os
tipos de dados HEART_RATE_BPM
e LOCATION
, respectivamente. Quando presente, use a propriedade
accuracy
para determinar se cada ponto de dados é suficiente
para o aplicativo.
Armazenar e fazer upload de dados
Use o Room para persistir dados enviados do Recursos de saúde. O upload de dados acontece no fim do exercício usando um mecanismo como o Work Manager. Isso garante que as chamadas de rede para o upload de dados sejam adiadas até o fim do exercício, o que minimiza o consumo de energia durante o exercício e simplifica o trabalho realizado.
Lista de verificação de integração
Antes de publicar seu app que usa os Recursos de saúde ExerciseClient
, consulte
a lista de verificação a seguir para garantir que a experiência do usuário evite alguns problemas comuns.
Confirme o seguinte:
- Seu app verifica os recursos do tipo de exercício e dos recursos do dispositivo cada vez que o aplicativo é executado. Dessa forma, você pode detectar quando um dispositivo ou exercício específico não é compatível com um dos tipos de dados de que seu app precisa.
- Você solicita e mantém as permissões necessárias e as especifica em
no arquivo de manifesto. Antes de chamar
prepareExerciseAsync()
, seu app confirma se as permissões de execução foram concedidas. - Seu app usa
getCurrentExerciseInfoAsync()
para processar os casos em que:- Um exercício já está sendo monitorado e seu aplicativo substitui o anterior exercício.
- Outro app encerrou seu exercício. Isso pode acontecer quando o usuário abre o app novamente, ele recebe uma mensagem explicando que o exercício parou porque outro app assumiu o controle.
- Se você estiver usando dados de
LOCATION
:- O app mantém um
ForegroundService
com asforegroundServiceType
durante todo o exercício (incluindo da chamada de preparação). - Verifique se o GPS está ativado no dispositivo usando
isProviderEnabled(LocationManager.GPS_PROVIDER)
e solicita que o usuário abra as configurações de localização, se necessário. - Para casos de uso exigentes, em que o recebimento de dados de local com é muito importante, considere integrar a API Provedor de localização (FLP) e usa os dados dele como uma correção inicial de local. Quando estiver mais estável informações de local disponíveis nos Recursos de saúde, use esses dados do FLP.
- O app mantém um
- Se o app exigir o upload de dados, todas as chamadas de rede para esse processo serão ser adiado até o fim do exercício. Caso contrário, ao longo do exercício, faz as chamadas de rede necessárias com moderação.
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado
- Atualizações de dados passivos
- Recursos de saúde no Wear OS
- Começar a usar Blocos