ExerciseClient로 운동 기록

건강 관리 서비스는 ExerciseClient를 통해 운동 앱을 최고 수준으로 지원합니다. ExerciseClient를 사용하면 앱에서 운동의 진행 시점을 제어하고, 운동 목표를 추가하며, 운동 상태, 운동 이벤트 또는 다른 원하는 측정항목의 업데이트를 받을 수 있습니다. 자세한 내용은 건강 관리 서비스에서 지원하는 운동 유형의 전체 목록을 참고하세요.

GitHub의 운동 샘플을 참고하세요.

종속 항목 추가

건강 관리 서비스에 종속 항목을 추가하려면 프로젝트에 Google Maven 저장소를 추가해야 합니다. 자세한 내용은 Google의 Maven 저장소를 참고하세요.

그런 다음, 모듈 수준의 build.gradle 파일에 다음 종속 항목을 추가합니다.

Groovy

dependencies {
    implementation "androidx.health:health-services-client:1.1.0-alpha02"
}

Kotlin

dependencies {
    implementation("androidx.health:health-services-client:1.1.0-alpha02")
}

앱 구조

건강 관리 서비스로 운동 앱을 빌드하는 경우 다음 앱 구조를 사용합니다.

운동을 준비할 때와 운동 중에는 다양한 이유로 활동이 중지될 수 있습니다. 사용자가 다른 앱으로 전환하거나 시계 화면으로 돌아갈 수 있습니다. 시스템이 활동 위에 무언가를 표시하거나 일정 시간 동안 사용하지 않으면 화면이 꺼질 수도 있습니다. 지속적으로 실행되는 ForegroundServiceExerciseClient와 함께 사용하면 운동을 하는 내내 앱이 바르게 작동합니다.

ForegroundService를 사용하면 Ongoing Activity API를 사용하여 시계 노출 영역에 표시기를 표시할 수 있으므로 사용자가 운동으로 빠르게 돌아갈 수 있습니다.

포그라운드 서비스에서 적절하게 위치 데이터를 요청해야 합니다. 매니페스트 파일에서 foregroundServiceType="location"을 지정하고 적절한 권한을 지정합니다.

운동 활동과, prepareExercise() 호출이 포함된 운동 전 활동에는 AmbientLifecycleObserver를 사용하세요. 단, 대기 모드에서 운동 중에는 디스플레이를 업데이트하지 마세요. 기기 화면이 대기 모드일 때는 건강 관리 서비스가 운동 데이터를 일괄 처리하여 전력을 절약하므로 표시되는 정보가 최신 정보가 아닐 수 있습니다. 운동 중에는 최신 정보나 빈 화면 등 해당 상황에 적합한 데이터를 표시하세요.

기능 확인

ExerciseType은 측정항목 및 운동 목표의 특정 데이터 유형을 지원합니다. 이러한 기능은 기기에 따라 다를 수 있으므로 시작할 때 확인하세요. 기기가 특정 운동 유형을 지원하지 않거나 자동 일시중지와 같은 특정 기능을 지원하지 않을 수도 있습니다. 또한 기기의 기능은 소프트웨어 업데이트 등으로 시간이 지나면서 변경될 수 있습니다.

앱 시작 시 기기 기능을 쿼리하고 다음을 저장하고 처리합니다.

  • 플랫폼에서 지원하는 운동
  • 각 운동에 지원되는 기능
  • 각 운동에 지원되는 데이터 유형
  • 이러한 각 데이터 유형에 필요한 권한

원하는 운동 유형과 함께 ExerciseCapabilities.getExerciseTypeCapabilities()를 사용하여 요청할 수 있는 측정항목의 종류, 설정할 수 있는 운동 목표, 운동 유형에서 제공하는 기타 기능을 확인할 수 있습니다. 예를 들면 다음과 같습니다.

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)
    }
}

반환된 ExerciseTypeCapabilities 내에서 supportedDataTypes는 데이터를 요청할 수 있는 데이터 유형을 나열합니다. 이는 기기에 따라 다르므로 지원되지 않는 DataType을 요청하지 않도록 주의해야 합니다. 그러지 않으면 요청이 실패할 수 있습니다.

supportedGoalssupportedMilestones 필드를 사용하여 이 운동이 사용자가 만들려는 운동 목표를 지원할 수 있는지 확인합니다.

앱에서 사용자가 자동 일시중지를 사용할 수 있으면 기기에서 이 기능을 지원하는지 supportsAutoPauseAndResume을 사용하여 확인해야 합니다. ExerciseClient는 기기에서 지원되지 않는 요청을 거부합니다.

다음 예에서는 HEART_RATE_BPM 데이터 유형, STEPS_TOTAL 목표 기능, 자동 일시중지 기능에 관한 지원을 확인합니다.

// 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

운동 상태 업데이트 등록

운동 업데이트는 리스너로 전송됩니다. 앱은 한 번에 하나의 리스너만 등록할 수 있습니다. 다음 예와 같이 운동을 시작하기 전에 리스너를 설정하세요. 리스너는 앱이 소유한 운동의 업데이트만 수신합니다.

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)

운동 전체 기간 관리

건강 관리 서비스는 기기의 모든 앱에서 한 번에 최대 한 가지 운동만 지원합니다. 운동을 추적하는 중인데 다른 앱에서 새로운 운동 추적을 시작하면 첫 번째 운동이 종료됩니다.

운동을 시작하기 전에 다음을 실행하세요.

  • 운동을 이미 추적하고 있는지 확인한 후 적절히 반응합니다. 예를 들어 이전 운동을 무시하고 새 운동 추적을 시작하기 전에 사용자에게 확인을 요청해야 합니다.

다음 예는 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.
    }
}

권한

ExerciseClient를 사용할 때는 앱이 필요한 권한을 요청하고 유지해야 합니다. 앱에서 LOCATION 데이터를 사용한다면 이 데이터에 관해서도 적절한 권한을 요청하고 유지해야 합니다.

모든 데이터 유형의 경우 prepareExercise() 또는 startExercise()를 호출하기 전에 다음을 실행합니다.

  • AndroidManifest.xml 파일에서 요청된 데이터 유형에 적절한 권한을 지정합니다.
  • 사용자가 필요한 권한을 부여했는지 확인합니다. 자세한 내용은 앱 권한 요청을 참고하세요. 아직 필요한 권한이 부여되지 않은 경우 건강 관리 서비스는 요청을 거부합니다.

위치 데이터의 경우 다음 단계를 추가로 실행합니다.

  • isProviderEnabled(LocationManager.GPS_PROVIDER)를 사용하여 기기에서 GPS가 사용 설정되어 있는지 확인합니다. 필요한 경우 위치 설정을 열라는 메시지를 사용자에게 표시합니다.
  • 적절한 foregroundServiceType이 있는 ForegroundService가 운동 전반에 걸쳐 유지되도록 합니다.

운동 준비

GPS 또는 심박수와 같은 일부 센서는 예열하는 데 시간이 조금 걸릴 수 있고 사용자는 운동을 시작하기 전에 데이터를 확인하고자 할 수도 있습니다. 선택사항인 prepareExerciseAsync() 메서드를 사용하면 운동 타이머를 시작하지 않고도 이러한 센서가 예열되어 데이터를 수신할 수 있습니다. activeDuration은 이 준비 시간의 영향을 받지 않습니다.

prepareExerciseAsync()를 호출하기 전에 다음을 확인하세요.

  • 플랫폼 전체 위치 설정을 확인합니다. 사용자가 기본 설정 메뉴에서 이 설정을 제어합니다. 앱 수준 권한 확인과는 다릅니다.

    설정이 사용 중지되어 있다면 위치 정보 액세스를 거부했다고 사용자에게 알리고, 앱에 위치 정보가 필요한 경우 위치 정보 액세스를 사용 설정하라는 메시지를 표시합니다.

  • 앱에 생체 신호 센서, 활동 감지, 상세한 위치 액세스에 관한 런타임 권한이 있는지 확인합니다. 권한이 누락된 경우 사용자에게 충분한 정보를 제공하는 런타임 권한 요청 메시지를 표시합니다. 사용자가 특정 권한을 부여하지 않으면 prepareExerciseAsync() 호출에서 해당 권한과 연결된 데이터 유형을 삭제합니다. 생체 신호 센서와 위치 정보 액세스 권한이 모두 부여되지 않은 경우에는 prepareExerciseAsync()를 호출하지 마세요. 준비 호출은 운동을 시작하기 전에 특히 안정적인 심박수 또는 GPS 수정을 얻기 위한 것이기 때문입니다. 앱은 걸음 수에 기반한 이동 거리, 페이스, 속도 및 이러한 권한이 필요하지 않은 기타 측정항목을 계속 가져올 수 있습니다.

prepareExerciseAsync() 호출이 성공할 수 있도록 다음을 실행하세요.

  • 준비 호출이 포함된 운동 전 활동을 위해 AmbientLifecycleObserver를 사용합니다.
  • 포그라운드 서비스에서 prepareExerciseAsync()를 호출합니다. 메서드가 서비스에 있지 않고 활동 수명 주기에 연결된 경우 센서 준비가 불필요하게 종료될 수 있습니다.
  • 사용자가 운동 전 활동을 벗어나면 endExercise()를 호출하여 센서를 끄고 전력 사용량을 줄입니다.

다음 예는 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.

앱이 PREPARING 상태가 되면 센서 가용성 업데이트가 onAvailabilityChanged()를 통해 ExerciseUpdateCallback에 전송됩니다. 그러면 이 정보가 사용자에게 표시될 수 있고 사용자가 운동 시작 여부를 결정할 수 있습니다.

운동 시작

운동을 시작하려면 ExerciseConfig를 생성하여 운동 유형, 수신하려는 측정항목의 데이터 유형 및 운동 목표 또는 주요 기록을 설정합니다.

운동 목표는 DataType과 조건으로 구성됩니다. 운동 목표는 조건이 충족될 때(예: 사용자가 특정 거리를 달릴 때) 트리거되는 일회성 목표입니다. 운동 주요 기록도 설정할 수 있습니다. 운동 주요 기록은 여러 번 트리거될 수 있습니다(예: 사용자가 설정된 거리를 지나 특정 지점을 달릴 때마다).

다음 샘플은 유형별로 한 가지 목표를 만드는 방법을 보여줍니다.

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()
}

모든 운동의 랩을 표시할 수도 있습니다. 건강 관리 서비스는 랩 기간 동안 집계된 측정항목과 함께 ExerciseLapSummary를 제공합니다.

이전 예는 isGpsEnabled의 사용 방법을 보여주며, 이 메서드는 위치 데이터를 요청할 때 true여야 합니다. 한편, GPS를 사용하면 다른 측정항목에 도움이 될 수 있습니다. ExerciseConfig에서 거리를 DataType으로 지정하면 기본적으로 걸음 수를 사용하여 거리를 추정합니다. 선택사항으로 GPS를 사용 설정하면 위치 정보를 사용하여 거리를 추정할 수 있습니다.

운동 일시중지, 다시 시작, 종료

pauseExerciseAsync() 또는 endExerciseAsync()와 같은 적절한 메서드를 사용하여 운동을 일시중지, 다시 시작, 종료할 수 있습니다.

ExerciseUpdate의 상태를 정보 소스로 사용합니다. 운동은 pauseExerciseAsync() 호출이 반환될 때가 아니라 일시중지 상태가 ExerciseUpdate 메시지에 반영될 때 일시중지된 것으로 간주됩니다. 이는 특별히 UI 상태와 관련하여 고려해야 할 중요한 부분입니다. 사용자가 일시중지를 누르면 일시중지 버튼을 사용 중지하고 건강 관리 서비스의 pauseExerciseAsync()를 호출합니다. ExerciseUpdate.exerciseStateInfo.state를 사용하여 건강 관리 서비스가 일시중지 상태에 도달할 때까지 기다린 후 버튼을 다시 시작으로 전환합니다. 이는 건강 관리 서비스 상태 업데이트가 전달되는 데 걸리는 시간이 버튼 누르기보다 오래 걸릴 수 있기 때문입니다. 따라서 모든 UI 변경사항을 버튼 누르기에 연결하면 UI가 건강 관리 서비스 상태와 동기화되지 않을 수 있습니다.

다음 상황에 유의하세요.

  • 자동 일시중지가 사용 설정됨: 사용자 상호작용 없이 운동이 일시중지되거나 시작될 수 있습니다.
  • 다른 앱에서 운동을 시작함: 사용자 상호작용 없이 운동이 종료될 수 있습니다.

앱의 운동이 다른 앱에 의해 종료되는 경우 앱은 다음과 같이 적절하게 종료를 처리해야 합니다.

  • 사용자의 진행 상황이 삭제되지 않도록 일부 운동 상태를 저장합니다.
  • 진행 중인 활동 아이콘을 삭제하고 사용자에게 다른 앱으로 인해 운동이 종료되었음을 알리는 알림을 보냅니다.

또한 운동이 진행되는 동안 권한이 취소되는 사례를 처리합니다. 이는 isEnded 상태를 사용하여 전송됩니다. 이때 ExerciseEndReasonAUTO_END_PERMISSION_LOST입니다. 이 사례를 종료 사례와 유사한 방식으로 처리합니다. 부분 상태를 저장하고 진행 중인 활동 아이콘을 삭제한 후 발생한 상황에 관한 알림을 사용자에게 전송합니다.

다음 예는 종료를 올바르게 확인하는 방법을 보여줍니다.

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
        }
        ...
    }
    ...
}

활성 기간 관리

운동 중에 앱이 운동의 활성 기간을 표시할 수 있습니다. 앱, 건강 관리 서비스, 기기 MCU(Micro Controller Unity, 운동 모니터링을 담당하는 저전력 프로세서)는 모두 동일한 현재 활성 기간과 동기화되어야 합니다. 이를 관리하는 데 도움이 되도록 건강 관리 서비스는 앱이 타이머를 시작할 수 있는 앵커 포인트를 제공하는 ActiveDurationCheckpoint를 전송합니다.

활성 기간은 MCU에서 전송되고 앱에 도착하는 데 약간의 시간이 걸릴 수 있으므로 ActiveDurationCheckpoint에는 다음 두 가지 속성이 포함됩니다.

  • activeDuration: 운동이 활성화된 후 경과된 시간
  • time: 활성 기간이 계산된 시점

따라서 앱에서 운동의 활성 기간은 ActiveDurationCheckpoint로부터 다음 공식을 사용하여 계산할 수 있습니다.

(now() - checkpoint.time) + checkpoint.activeDuration

이 공식은 MCU에서 계산되는 활성 기간과 활성 기간이 앱에 도착하는 시점 사이의 작은 델타를 고려합니다. 이 값은 앱에서 크로노미터를 시드하는 데 사용될 수 있으며 앱의 타이머가 건강 관리 서비스와 MCU의 시간에 완벽하게 일치되도록 합니다.

운동이 일시중지되면 앱은 계산된 시간이 UI에 현재 표시되는 시간을 지날 때까지 UI에서 타이머가 다시 시작되기를 기다립니다. 이는 일시중지 신호가 약간 지연되어 건강 관리 서비스와 MCU에 도달하기 때문입니다. 예를 들어, t=10초일 때 앱이 일시중지되면 건강 관리 서비스는 t=10.2초일 때까지 앱에 PAUSED 업데이트를 제공하지 않을 수도 있습니다.

ExerciseClient의 데이터 사용

앱이 등록한 데이터 유형의 측정항목은 ExerciseUpdate 메시지로 전송됩니다.

프로세서는 깨어 있을 때 또는 최대 보고 기간에 도달할 때만(예: 150초마다) 메시지를 전송합니다. activeDuration으로 크로노미터를 진행하기 위해 ExerciseUpdate 실행 빈도에 의존하면 안 됩니다. 독립 크로노미터를 구현하는 방법의 예는 GitHub의 운동 샘플을 참고하세요.

사용자가 운동을 시작하면 ExerciseUpdate 메시지가 자주 전송될 수 있습니다(예: 1초마다). 사용자가 운동을 시작하면 화면이 꺼질 수 있습니다. 그러면 건강 관리 서비스는 빈도를 줄여 데이터를 전송하지만 데이터는 계속 동일한 빈도로 샘플링되어 기본 프로세서의 절전 모드가 해제되는 것을 방지할 수 있습니다. 사용자가 화면을 보면 일괄 처리 중인 모든 데이터가 즉시 앱으로 전송됩니다.

일괄 처리 속도 제어

일부 시나리오에서는 화면이 꺼진 상태에서 앱이 특정 데이터 유형을 수신하는 빈도를 제어하는 것이 좋습니다. BatchingMode 객체를 사용하면 앱에서 기본 일괄 처리 동작을 재정의하여 데이터를 더 자주 전송할 수 있습니다.

일괄 처리 비율을 구성하려면 다음 단계를 완료합니다.

  1. 특정 BatchingMode 정의가 기기에서 지원되는지 확인합니다.

    // 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
    }
    
  2. 다음 코드 스니펫에서와 같이 ExerciseConfig 객체가 특정 BatchingMode를 사용하도록 지정합니다.

    val config = ExerciseConfig(
        exerciseType = ExerciseType.WORKOUT,
        dataTypes = setOf(
            DataType.HEART_RATE_BPM,
            DataType.TOTAL_CALORIES
        ),
        // ...
        batchingModeOverrides = setOf(BatchingMode.HEART_RATE_5_SECONDS)
    )
    
  3. 운동 중에 특정 일괄 처리 동작이 지속되도록 하는 대신 운동 중에 BatchingMode를 동적으로 구성할 수도 있습니다.

    val desiredModes = setOf(BatchingMode.HEART_RATE_5_SECONDS)
    exerciseClient.overrideBatchingModesForActiveExercise(desiredModes)
    
  4. 맞춤설정된 BatchingMode를 지우고 기본 동작으로 돌아가려면 빈 세트를 exerciseClient.overrideBatchingModesForActiveExercise()에 전달합니다.

타임스탬프

각 데이터 포인트의 시점은 기기가 부팅된 이후의 기간을 나타냅니다. 이를 타임스탬프로 변환하려면 다음을 실행합니다.

val bootInstant =
    Instant.ofEpochMilli(System.currentTimeMillis() - SystemClock.elapsedRealtime())

그런 다음 이 값은 각 데이터 포인트에 맞게 getStartInstant() 또는 getEndInstant()와 함께 사용할 수 있습니다.

데이터 정확성

일부 데이터 유형에는 각 데이터 포인트와 연결된 정확성 정보가 포함될 수 있습니다. 이 정보는 accuracy 속성에 표시됩니다.

HEART_RATE_BPMLOCATION 데이터 유형에 맞게 각각 HrAccuracyLocationAccuracy 클래스가 채워져야 합니다. accuracy 속성이 있다면 이 속성을 사용하여 각 데이터 포인트가 애플리케이션에 충분한 정확성을 제공하는지 판단합니다.

데이터 저장 및 업로드

Room을 사용하여 건강 관리 서비스에서 제공하는 데이터를 유지할 수 있습니다. 데이터 업로드는 Work Manager와 같은 메커니즘을 사용하여 운동이 끝날 때 이루어집니다. 이를 통해 운동이 종료될 때까지 데이터 업로드를 위한 네트워크 호출이 지연되므로 운동 중 전력 소모가 최소화되고 작업이 간소화됩니다.

통합 체크리스트

건강 관리 서비스의 ExerciseClient를 사용하는 앱을 게시하기 전에 다음 체크리스트를 참고하여 사용자 환경에서 일반적인 문제를 피하도록 하세요. 다음 사항을 확인하세요.

  • 앱은 앱이 실행될 때마다 운동 유형의 기능과 기기의 기능을 확인합니다. 이렇게 하면 특정 기기나 운동에서 앱에 필요한 데이터 유형 중 하나를 지원하지 않을 때 이를 감지할 수 있습니다.
  • 개발자는 필요한 권한을 요청 및 유지관리하고 매니페스트 파일에 이를 지정합니다. prepareExerciseAsync()를 호출하기 전에 앱은 런타임 권한이 부여되었는지 확인합니다.
  • 앱에서 getCurrentExerciseInfoAsync()을 사용하여 다음과 같은 케이스를 처리합니다.
    • 운동을 이미 추적 중이며 앱이 이전 운동을 재정의합니다.
    • 다른 앱에서 운동을 종료했습니다. 이 상황은 사용자가 앱을 다시 열 때 다른 앱이 인계되어 운동이 중지되었다고 설명하는 메시지가 표시될 수 있습니다.
  • LOCATION 데이터를 사용하는 경우:
    • 앱은 운동 (준비 호출 포함)이 진행되는 동안 상응하는 foregroundServiceTypeForegroundService를 유지합니다.
    • isProviderEnabled(LocationManager.GPS_PROVIDER)를 사용하여 기기에서 GPS가 사용 설정되어 있는지 확인하고 필요한 경우 사용자에게 위치 설정을 열라는 메시지를 표시합니다.
    • 지연 시간이 짧은 위치 데이터 수신이 매우 중요한 까다로운 사용 사례의 경우 통합 위치 정보 제공자(FLP)를 통합하고 그 데이터를 초기 위치 수정으로 사용하는 것이 좋습니다. 건강 관리 서비스에서 더 안정적인 위치 정보를 사용할 수 있는 경우 FLP 대신 사용합니다.
  • 앱에 데이터 업로드가 필요하다면 운동이 종료될 때까지 데이터 업로드를 위한 네트워크 호출이 지연됩니다. 그러지 않으면 연습이 진행되는 동안 앱에서 필요한 네트워크 호출을 드물게 실행합니다.