บันทึกการออกกำลังกายด้วย ExerciseClient

บริการสุขภาพให้การสนับสนุนชั้นยอดสำหรับแอปออกกำลังกายผ่าน ExerciseClient เมื่อใช้ ExerciseClient แอปจะควบคุมได้ว่าเมื่อใดที่การออกกำลังกายกำลังดำเนินอยู่ เพิ่มเป้าหมายการออกกำลังกาย และรับข้อมูลอัปเดตเกี่ยวกับสถานะการออกกำลังกาย เหตุการณ์การออกกำลังกาย หรือเมตริกอื่นๆ ที่ต้องการ ดูข้อมูลเพิ่มเติมได้ที่รายการประเภทการออกกำลังกายทั้งหมดที่ Health Services รองรับ

ดูตัวอย่างแบบฝึกหัดใน GitHub

เพิ่มทรัพยากร Dependency

หากต้องการเพิ่มการพึ่งพาบริการสุขภาพ คุณต้องเพิ่มที่เก็บ Maven ของ Google ลงในโปรเจ็กต์ ดูข้อมูลเพิ่มเติมได้ที่ที่เก็บ Maven ของ Google

จากนั้นเพิ่มข้อกําหนดต่อไปนี้ในไฟล์ build.gradle ระดับโมดูล

ดึงดูด

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

Kotlin

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

โครงสร้างแอป

ใช้โครงสร้างแอปต่อไปนี้เมื่อสร้างแอปออกกำลังกายที่มีบริการสุขภาพ

เมื่อเตรียมตัวสำหรับการออกกำลังกายและระหว่างออกกำลังกาย กิจกรรมของคุณอาจหยุดลงด้วยเหตุผลหลายประการ ผู้ใช้อาจเปลี่ยนไปใช้แอปอื่นหรือกลับไปที่หน้าปัด ระบบอาจแสดงข้อมูลบางอย่างบนกิจกรรมของคุณ หรือหน้าจออาจปิดลงหลังจากไม่มีการใช้งานเป็นระยะเวลาหนึ่ง ใช้ ForegroundService ที่ทำงานอย่างต่อเนื่องร่วมกับ ExerciseClient เพื่อช่วยตรวจสอบการทํางานอย่างถูกต้องตลอดการออกกําลังกาย

การใช้ ForegroundService ช่วยให้คุณใช้ Ongoing Activity API เพื่อแสดงตัวบ่งชี้บนหน้าปัดได้ ซึ่งจะช่วยให้ผู้ใช้กลับไปออกกำลังกายได้อย่างรวดเร็ว

คุณต้องขอข้อมูลตำแหน่งอย่างเหมาะสมในบริการที่ทำงานอยู่เบื้องหน้า ในไฟล์ Manifest ให้ระบุประเภทและสิทธิ์บริการที่ทำงานอยู่เบื้องหน้าที่จำเป็น

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

ใช้ AmbientLifecycleObserver สําหรับกิจกรรมก่อนออกกำลังกายซึ่งมีคําเรียก prepareExercise() และ สําหรับกิจกรรมการออกกําลังกาย อย่างไรก็ตาม อย่าอัปเดตข้อมูลในหน้าจอระหว่างออกกำลังกายหรือขณะอยู่ในโหมดแอมเบียนท์ เนื่องจากบริการสุขภาพจะจัดกลุ่มข้อมูลการออกกำลังกายเมื่อหน้าจออุปกรณ์อยู่ในโหมดแอมเบียนท์เพื่อประหยัดพลังงาน ดังนั้นข้อมูลที่แสดงจึงอาจไม่ใช่ข้อมูลล่าสุด ระหว่างออกกำลังกาย ให้แสดงข้อมูลที่เป็นประโยชน์ต่อผู้ใช้ โดยแสดงข้อมูลล่าสุดหรือหน้าจอว่าง

ตรวจสอบความสามารถ

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 ที่ระบบไม่รองรับ ไม่เช่นนั้นคำขออาจดำเนินการไม่สำเร็จ

ใช้ช่อง supportedGoals และ supportedMilestones เพื่อระบุว่าการออกกำลังกายรองรับเป้าหมายการออกกำลังกายที่คุณต้องการสร้างหรือไม่

หากแอปของคุณอนุญาตให้ผู้ใช้ใช้การหยุดชั่วคราวอัตโนมัติ คุณต้องตรวจสอบว่าอุปกรณ์รองรับฟังก์ชันการทำงานนี้โดยใช้ 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

ลงทะเบียนรับการอัปเดตสถานะการออกกำลังกาย

ส่งข้อมูลอัปเดตการออกกำลังกายไปยังผู้ฟัง แอปของคุณจะลงทะเบียนผู้ฟังได้ครั้งละ 1 คนเท่านั้น ตั้งค่า Listener ก่อนเริ่มการออกกำลังกาย ดังที่แสดงในตัวอย่างต่อไปนี้ ผู้ฟังจะได้รับเฉพาะข้อมูลอัปเดตเกี่ยวกับแบบฝึกหัดที่แอปของคุณเป็นเจ้าของ

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)

จัดการอายุการใช้งานของการออกกำลังกาย

บริการสุขภาพรองรับการออกกำลังกายได้สูงสุดครั้งละ 1 รายการในแอปทั้งหมดในอุปกรณ์ หากมีการติดตามการออกกำลังกายอยู่และแอปอื่นเริ่มติดตามการออกกำลังกายใหม่ การออกกำลังกายแรกจะสิ้นสุดลง

ก่อนเริ่มออกกำลังกาย ให้ทำดังนี้

  • ตรวจสอบว่ามีการติดตามการออกกำลังกายอยู่หรือไม่ และดำเนินการตามความเหมาะสม เช่น ขอให้ผู้ใช้ยืนยันก่อนลบล้างการออกกำลังกายก่อนหน้าและเริ่มติดตามการออกกำลังกายใหม่

ตัวอย่างต่อไปนี้แสดงวิธีตรวจสอบว่ามีแบบฝึกหัดที่มีอยู่หรือไม่ โดยมี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
  • ยืนยันว่าผู้ใช้ได้ให้สิทธิ์ที่จำเป็นแล้ว ดูข้อมูลเพิ่มเติมได้ที่หัวข้อขอสิทธิ์ของแอป Health Services จะปฏิเสธคำขอหากยังไม่ได้ให้สิทธิ์ที่จำเป็น

สำหรับข้อมูลตำแหน่ง ให้ทำตามขั้นตอนเพิ่มเติมต่อไปนี้

  • ตรวจสอบว่าเปิดใช้ GPS ในอุปกรณ์แล้วโดยใช้ isProviderEnabled(LocationManager.GPS_PROVIDER) แจ้งให้ผู้ใช้เปิดการตั้งค่าตำแหน่ง หากจำเป็น
  • ตรวจสอบว่า ForegroundService ที่มี foregroundServiceType ที่เหมาะสมมีการใช้งานตลอดการออกกำลังกาย

เตรียมพร้อมสำหรับการออกกำลังกาย

เซ็นเซอร์บางประเภท เช่น GPS หรืออัตราการเต้นของหัวใจ อาจใช้เวลาสักครู่ในการอุ่นเครื่อง หรือผู้ใช้อาจต้องการดูข้อมูลก่อนเริ่มออกกำลังกาย วิธีprepareExerciseAsync() (ไม่บังคับ) จะช่วยให้เซ็นเซอร์เหล่านี้อุ่นเครื่องและรับข้อมูลได้โดยไม่ต้องเริ่มจับเวลาสำหรับการออกกำลังกาย activeDuration จะไม่ได้รับผลกระทบจากเวลาเตรียมความพร้อมนี้

โปรดตรวจสอบสิ่งต่อไปนี้ก่อนโทรหา prepareExerciseAsync()

  • ตรวจสอบการตั้งค่าตำแหน่งทั้งแพลตฟอร์ม ผู้ใช้จะควบคุมการตั้งค่านี้ได้ในเมนูการตั้งค่าหลัก ซึ่งแตกต่างจากการตรวจสอบสิทธิ์ระดับแอป

    หากการตั้งค่าปิดอยู่ ให้แจ้งให้ผู้ใช้ทราบว่าปฏิเสธการเข้าถึงตำแหน่งแล้ว และแจ้งให้ผู้ใช้เปิดใช้หากแอปต้องใช้ตำแหน่ง

  • ยืนยันว่าแอปของคุณมีสิทธิ์รันไทม์สำหรับเซ็นเซอร์ร่างกาย (API ระดับ 35 หรือต่ำกว่า) หรืออัตราการเต้นของหัวใจ (API ระดับ 36 ขึ้นไป) การจดจำกิจกรรม และตำแหน่งที่แน่นอน สําหรับสิทธิ์ที่ขาดหายไป ให้แจ้งให้ผู้ใช้ขอสิทธิ์รันไทม์โดยระบุบริบทที่เพียงพอ หากผู้ใช้ไม่ให้สิทธิ์ที่เฉพาะเจาะจง ให้นําประเภทข้อมูลที่เชื่อมโยงกับสิทธิ์นั้นออกจากการเรียก prepareExerciseAsync() หากไม่ได้ให้สิทธิ์เซ็นเซอร์ร่างกาย (อัตราการเต้นของหัวใจใน API ระดับ 36 ขึ้นไป) หรือสิทธิ์เข้าถึงตำแหน่ง อย่าเรียก 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 (API level 35
// or lower), heart rate (API level 36+), or location permissions are given.
exerciseClient.prepareExerciseAsync(warmUpConfig).await()

// Data and availability updates are delivered to the registered listener.

เมื่อแอปอยู่ในสถานะ PREPARING ระบบจะส่งการอัปเดตความพร้อมใช้งานของเซ็นเซอร์ใน ExerciseUpdateCallback ถึง onAvailabilityChanged() จากนั้นระบบจะแสดงข้อมูลนี้ต่อผู้ใช้เพื่อให้ผู้ใช้ตัดสินใจว่าจะเริ่มออกกำลังกายหรือไม่

เริ่มออกกำลังกาย

เมื่อต้องการเริ่มการออกกําลังกาย ให้สร้าง ExerciseConfig เพื่อกําหนดค่าประเภทการออกกําลังกาย ประเภทข้อมูลที่ต้องการรับเมตริก ตลอดจนเป้าหมายหรือเหตุการณ์สําคัญของการออกกําลังกาย

เป้าหมายการออกกำลังกายประกอบด้วย DataType และเงื่อนไข เป้าหมายการออกกำลังกายเป็นเป้าหมายแบบครั้งเดียวที่จะทริกเกอร์เมื่อเงื่อนไขหนึ่งๆ ตรงตาม เช่น เมื่อผู้ใช้วิ่งระยะทางหนึ่งๆ นอกจากนี้ คุณยังตั้งค่าเหตุการณ์สำคัญในการออกกำลังกายได้ด้วย คุณสามารถทริกเกอร์เหตุการณ์สำคัญในการออกกำลังกายได้หลายครั้ง เช่น ทุกครั้งที่ผู้ใช้วิ่งผ่านจุดหนึ่งๆ เกินระยะทางที่กำหนดไว้

ตัวอย่างต่อไปนี้แสดงวิธีสร้างเป้าหมาย 1 รายการของแต่ละประเภท

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 ซึ่งต้องเป็นจริงเมื่อขอข้อมูลตําแหน่ง อย่างไรก็ตาม การใช้ GPS ยังช่วยเกี่ยวกับเมตริกอื่นๆ ได้ด้วย หาก ExerciseConfig ระบุระยะทางเป็น DataType ระบบจะใช้จำนวนก้าวเพื่อประมาณระยะทางโดยค่าเริ่มต้น หากเปิดใช้ GPS (ไม่บังคับ) ระบบจะใช้ข้อมูลตำแหน่งแทนเพื่อประมาณระยะทาง

หยุดชั่วคราว เล่นต่อ และสิ้นสุดการออกกำลังกาย

คุณสามารถหยุดชั่วคราว เล่นต่อ และสิ้นสุดการออกกำลังกายได้โดยใช้วิธีการที่เหมาะสม เช่น pauseExerciseAsync() หรือ endExerciseAsync()

ใช้สถานะจาก ExerciseUpdate เป็นแหล่งข้อมูลที่ถูกต้อง ระบบจะไม่ถือว่าการออกกําลังกายหยุดชั่วคราวเมื่อการเรียกใช้ pauseExerciseAsync() แสดงผล แต่จะใช้สถานะที่แสดงในข้อความ ExerciseUpdate ซึ่งถือเป็นสิ่งสำคัญอย่างยิ่งที่ต้องพิจารณาเมื่อพูดถึงสถานะ UI หากผู้ใช้กดหยุดชั่วคราว ให้ปิดใช้ปุ่มหยุดชั่วคราวและโทรหา pauseExerciseAsync() ในบริการสุขภาพ รอให้บริการสุขภาพเข้าสู่สถานะหยุดชั่วคราวโดยใช้ ExerciseUpdate.exerciseStateInfo.state แล้วสลับปุ่มเพื่อดำเนินการต่อ เนื่องจากการอัปเดตสถานะบริการสุขภาพอาจใช้เวลานานกว่าการกดปุ่ม ดังนั้นหากคุณเชื่อมโยงการเปลี่ยนแปลง UI ทั้งหมดกับการกดปุ่ม UI อาจไม่ซิงค์กับสถานะบริการสุขภาพ

โปรดคำนึงถึงเรื่องนี้ในสถานการณ์ต่อไปนี้

  • เปิดใช้การหยุดชั่วคราวอัตโนมัติ: การออกกำลังกายจะหยุดชั่วคราวหรือเริ่มโดยไม่ได้รับการโต้ตอบจากผู้ใช้
  • แอปอื่นเริ่มการออกกำลังกาย: การออกกำลังกายอาจสิ้นสุดลงโดยที่ผู้ใช้ไม่ได้โต้ตอบ

หากแอปอื่นสิ้นสุดการออกกำลังกายของแอปคุณ แอปของคุณต้องจัดการการสิ้นสุดอย่างราบรื่น โดยทำดังนี้

  • บันทึกสถานะการออกกำลังกายบางส่วนเพื่อไม่ให้ลบความคืบหน้าของผู้ใช้
  • นำไอคอนกิจกรรมต่อเนื่องออกและส่งการแจ้งเตือนให้ผู้ใช้ทราบว่าแอปอื่นสิ้นสุดการออกกำลังกายแล้ว

รวมถึงจัดการกรณีที่มีการเพิกถอนสิทธิ์ระหว่างการดำเนินการ ซึ่งส่งโดยใช้สถานะ isEnded โดยมี ExerciseEndReason เท่ากับ AUTO_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
        }
        ...
    }
    ...
}

จัดการระยะเวลาที่ใช้งานอยู่

ในระหว่างการออกกำลังกาย แอปจะแสดงระยะเวลาที่ใช้งานของการออกกำลังกายได้ แอป บริการสุขภาพ และ Micro Controller Unit (MCU) ของอุปกรณ์ ซึ่งเป็นโปรเซสเซอร์พลังงานต่ำที่รับผิดชอบการติดตามการออกกำลังกาย ทั้งหมดต้องซิงค์กันโดยมีระยะเวลาที่ใช้งานอยู่ในปัจจุบันเดียวกัน บริการสุขภาพจะส่ง ActiveDurationCheckpoint เพื่อช่วยจัดการเรื่องนี้ ซึ่งจะเป็นจุดยึดที่แอปสามารถเริ่มตัวจับเวลาได้

เนื่องจากระยะเวลาที่ใช้งานอยู่จะส่งมาจาก MCU และอาจใช้เวลาสักครู่จึงจะมาถึงในแอป ActiveDurationCheckpoint จึงมีพร็อพเพอร์ตี้ 2 รายการ ได้แก่

  • activeDuration: ระยะเวลาที่ใช้งานแบบฝึกหัด
  • time: เมื่อคํานวณระยะเวลาที่ใช้งาน

ดังนั้นในแอป ระยะเวลาที่ใช้งานของการออกกำลังกายจะคำนวณจาก ActiveDurationCheckpoint โดยใช้สมการต่อไปนี้

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

ข้อมูลนี้ใช้เพื่ออธิบายค่าต่างเล็กน้อยระหว่างระยะเวลาที่ใช้งานอยู่ซึ่งคำนวณใน MCU กับเวลาที่มาถึงแอป ซึ่งสามารถใช้เพื่อกำหนดค่าเริ่มต้นของนาฬิกาจับเวลาในแอปและช่วยให้มั่นใจว่าตัวจับเวลาของแอปจะสอดคล้องกับเวลาใน Health Services และ MCU อย่างสมบูรณ์

หากการออกกำลังกายหยุดชั่วคราว แอปจะรอเพื่อเริ่มตัวจับเวลาใน UI อีกครั้งจนกว่าเวลาที่คํานวณได้จะผ่านไปนานกว่าที่ UI แสดงอยู่ เนื่องจากสัญญาณหยุดชั่วคราวจะไปถึงบริการสุขภาพและ MCU ด้วยความล่าช้าเล็กน้อย เช่น หากแอปหยุดชั่วคราวที่ t=10 วินาที Health Services อาจไม่ส่งการอัปเดต PAUSED ไปยังแอปจนกว่า t=10.2 วินาที

ทำงานกับข้อมูลจาก ExerciseClient

ระบบจะส่งเมตริกสำหรับประเภทข้อมูลที่แอปของคุณลงทะเบียนไว้ในรูปแบบข้อความ ExerciseUpdate

โปรเซสเซอร์จะส่งข้อความเฉพาะเมื่อทำงานอยู่หรือเมื่อถึงระยะเวลาการรายงานสูงสุด เช่น ทุกๆ 150 วินาที อย่าใช้ความถี่ ExerciseUpdate เพื่อเดินหน้านาฬิกาจับเวลาด้วย activeDuration ดูตัวอย่างวิธีใช้นาฬิกาจับเวลาอิสระได้ในตัวอย่างแบบฝึกหัดบน GitHub

เมื่อผู้ใช้เริ่มออกกำลังกาย ระบบสามารถส่งExerciseUpdateข้อความได้บ่อยครั้ง เช่น ทุกๆ วินาที เมื่อผู้ใช้เริ่มออกกำลังกาย หน้าจออาจปิดลง จากนั้นบริการข้อมูลสุขภาพจะส่งข้อมูลได้น้อยลง แต่ยังคงสุ่มตัวอย่างด้วยความถี่เดิมเพื่อหลีกเลี่ยงการปลุกตัวประมวลผลหลัก เมื่อผู้ใช้มองไปที่หน้าจอ ระบบจะส่งข้อมูลที่กำลังจัดกลุ่มไปยังแอปของคุณทันที

ควบคุมอัตราการจัดกลุ่ม

ในบางสถานการณ์ คุณอาจต้องการควบคุมความถี่ที่แอปได้รับข้อมูลบางประเภทขณะที่หน้าจอปิดอยู่ ออบเจ็กต์ 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

คุณสามารถป้อนข้อมูลคลาส HrAccuracy และ LocationAccuracy สำหรับประเภทข้อมูล HEART_RATE_BPM และ LOCATION ตามลำดับ หากมี ให้ใช้พร็อพเพอร์ตี้ accuracy เพื่อระบุว่าจุดข้อมูลแต่ละจุดมีความแม่นยำเพียงพอสําหรับแอปพลิเคชันหรือไม่

จัดเก็บและอัปโหลดข้อมูล

ใช้ Room เพื่อเก็บข้อมูลที่ได้รับจากบริการข้อมูลสุขภาพ การอัปโหลดข้อมูลจะเกิดขึ้นเมื่อสิ้นสุดการฝึกโดยใช้กลไกต่างๆ เช่น Work Manager วิธีนี้ช่วยให้มั่นใจได้ว่าการเรียกใช้เครือข่ายเพื่ออัปโหลดข้อมูลจะเลื่อนออกไปจนกว่าการออกกำลังกายจะสิ้นสุดลง ซึ่งจะช่วยลดการใช้พลังงานระหว่างการออกกำลังกายและลดความซับซ้อนของงาน

รายการตรวจสอบการรวมระบบ

ก่อนเผยแพร่แอปที่ใช้ ExerciseClient ของบริการสุขภาพ โปรดดูรายการตรวจสอบต่อไปนี้เพื่อให้ประสบการณ์ของผู้ใช้หลีกเลี่ยงปัญหาที่พบได้ทั่วไป โปรดตรวจสอบว่า

  • แอปจะตรวจสอบความสามารถของประเภทการออกกำลังกายและความสามารถของอุปกรณ์ทุกครั้งที่แอปทำงาน วิธีนี้จะช่วยให้คุณตรวจจับได้เมื่ออุปกรณ์หรือการออกกำลังกายไม่รองรับประเภทข้อมูลอย่างใดอย่างหนึ่งที่แอปต้องการ
  • คุณขอและรักษาสิทธิ์ที่จําเป็นไว้ รวมถึงระบุสิทธิ์เหล่านี้ในไฟล์ Manifest ก่อนเรียกใช้ prepareExerciseAsync() แอปของคุณจะยืนยันว่าได้รับสิทธิ์รันไทม์แล้ว
  • แอปของคุณใช้ getCurrentExerciseInfoAsync() เพื่อจัดการกรณีที่
    • มีการออกกำลังกายที่ติดตามอยู่แล้ว และแอปของคุณลบล้างการออกกำลังกายก่อนหน้านี้
    • แอปอื่นสิ้นสุดการออกกำลังกายของคุณแล้ว กรณีนี้อาจเกิดขึ้นเมื่อผู้ใช้เปิดแอปอีกครั้งและเห็นข้อความอธิบายว่าการออกกำลังกายหยุดลงเนื่องจากแอปอื่นเข้ามาควบคุม
  • หากคุณใช้อินเทอร์เน็ต LOCATION ข้อมูล โปรดทำดังนี้
    • แอปของคุณต้องรักษา ForegroundService กับ foregroundServiceType ที่เกี่ยวข้องไว้ตลอดระยะเวลาของการฝึก (รวมถึงการโทรเตรียมความพร้อม)
    • ตรวจสอบว่าเปิดใช้ GPS ในอุปกรณ์โดยใช้ isProviderEnabled(LocationManager.GPS_PROVIDER) แล้ว และแจ้งให้ผู้ใช้เปิดการตั้งค่าตำแหน่งหากจำเป็น
    • สําหรับ Use Case ที่ต้องใช้ข้อมูลอย่างรวดเร็ว ซึ่งการรับข้อมูลตําแหน่งที่มีความล่าช้าต่ำมีความสําคัญอย่างยิ่ง ให้พิจารณาผสานรวม Fused Location Provider (FLP) และใช้ข้อมูลของฟีเจอร์ดังกล่าวเป็นตำแหน่งเริ่มต้น เมื่อมีข้อมูลตำแหน่งที่แม่นยำกว่าจากบริการสุขภาพ ให้ใช้ข้อมูลดังกล่าวแทน FLP
  • หากแอปของคุณต้องมีการอัปโหลดข้อมูล การเรียกใช้เครือข่ายเพื่ออัปโหลดข้อมูลจะเลื่อนออกไปจนกว่าการออกกำลังกายจะสิ้นสุดลง มิเช่นนั้น ตลอดระยะเวลาการทดสอบ แอปจะเรียกใช้เครือข่ายที่จำเป็นอย่างประหยัด