Ghi bài tập thể dục bằng ExerciseClient

Dịch vụ sức khoẻ cung cấp cho bạn tính năng hỗ trợ hàng đầu cho các ứng dụng tập thể dục thông qua ExerciseClient. Với ExerciseClient, ứng dụng của bạn có thể kiểm soát thời điểm thực hiện một bài tập thể dục, thêm mục tiêu tập thể dục và nhận thông tin cập nhật về trạng thái tập thể dục, sự kiện tập thể dục hoặc các chỉ số mong muốn khác. Để biết thêm thông tin, hãy xem danh sách đầy đủ các loại bài tập thể dục mà Dịch vụ sức khoẻ hỗ trợ.

Xem Mẫu bài tập thể dục trên GitHub.

Thêm phần phụ thuộc

Để thêm một phần phụ thuộc trên Dịch vụ sức khoẻ, bạn phải thêm kho lưu trữ Google Maven vào dự án. Để biết thêm thông tin, hãy xem Kho lưu trữ Maven của Google.

Sau đó, trong tệp build.gradle cấp mô-đun, hãy thêm phần phụ thuộc sau:

Groovy

dependencies {
    implementation "androidx.health:health-services-client:1.0.0-beta02"
}

Kotlin

dependencies {
    implementation("androidx.health:health-services-client:1.0.0-beta02")
}

Cấu trúc ứng dụng

Sử dụng cấu trúc ứng dụng sau khi tạo một ứng dụng tập thể dục bằng Dịch vụ sức khoẻ:

Khi chuẩn bị cho một bài tập thể dục và trong khi tập, hoạt động của bạn có thể bị dừng vì nhiều lý do. Có thể người dùng chuyển sang một ứng dụng khác hoặc quay lại mặt đồng hồ. Cũng có thể hệ thống hiển thị nội dung nào đó ở trên hoạt động của bạn, hoặc màn hình bị tắt sau một khoảng thời gian không hoạt động. Sử dụng ForegroundService chạy liên tục cùng với ExerciseClient để đảm bảo hoạt động chính xác cho toàn bộ bài tập thể dục.

Việc sử dụng ForegroundService cho phép bạn dùng Ongoing Activity API (API Hoạt động đang diễn ra) để hiển thị chỉ báo trên mặt đồng hồ, giúp người dùng nhanh chóng quay lại bài tập thể dục.

Bạn cần yêu cầu dữ liệu vị trí một cách thích hợp trong dịch vụ trên nền trước. Trong tệp kê khai, hãy chỉ định foregroundServiceType="location" rồi chỉ định các quyền thích hợp.

Hãy dùng AmbientLifecycleObserver cho hoạt động trước khi tập luyện (chứa lệnh gọi prepareExercise()) và cho hoạt động tập thể dục. Tuy nhiên, trong quá trình tập thể dục, đừng cập nhật màn hình nếu thiết bị đang ở chế độ môi trường xung quanh: Vì khi màn hình thiết bị đang ở chế độ môi trường xung quanh, Dịch vụ sức khoẻ sẽ gộp dữ liệu tập luyện theo lô để tiết kiệm pin. Vì vậy, thông tin xuất hiện có thể không phải là thông tin gần đây. Trong các bài tập thể dục, hãy cho người dùng thấy dữ liệu có ý nghĩa với họ, chẳng hạn như thông tin cập nhật mới nhất hoặc cho họ thấy màn hình trống.

Kiểm tra các chức năng

Mỗi ExerciseType sẽ hỗ trợ một số kiểu dữ liệu cho các chỉ số và cho mục tiêu tập thể dục. Hãy kiểm tra các chức năng này khi khởi động, vì chúng có thể khác nhau tuỳ thuộc vào thiết bị. Một thiết bị có thể không hỗ trợ một loại bài tập thể dục nhất định, hoặc có thể không hỗ trợ một chức năng cụ thể, chẳng hạn như tự động tạm dừng. Ngoài ra, các chức năng của thiết bị có thể thay đổi theo thời gian, chẳng hạn như sau khi cập nhật phần mềm.

Khi khởi động ứng dụng, hãy truy vấn các chức năng của thiết bị rồi lưu trữ và xử lý những nội dung sau:

  • Các bài tập mà nền tảng hỗ trợ.
  • Các tính năng được hỗ trợ cho mỗi bài tập.
  • Các kiểu dữ liệu được hỗ trợ cho mỗi bài tập.
  • Các quyền cần thiết cho từng kiểu dữ liệu đó.

Hãy sử dụng ExerciseCapabilities.getExerciseTypeCapabilities() cùng với loại bài tập bạn muốn để xem bạn có thể yêu cầu loại chỉ số nào, có thể định cấu hình mục tiêu tập thể dục nào và đang có sẵn những tính năng nào khác dành cho loại đó. Lệnh này được minh hoạ trong ví dụ sau:

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

Bên trong ExerciseTypeCapabilities được trả về, supportedDataTypes sẽ liệt kê các loại dữ liệu mà bạn có thể yêu cầu cung cấp dữ liệu. Kiểu dữ liệu này thay đổi tuỳ theo thiết bị. Vì vậy, hãy cẩn thận để không yêu cầu một DataType không được hỗ trợ, nếu không yêu cầu của bạn có thể bị từ chối.

Sử dụng các trường supportedGoalssupportedMilestones để xác định xem bài tập có thể hỗ trợ mục tiêu tập thể dục mà bạn muốn tạo hay không.

Nếu ứng dụng cho phép người dùng sử dụng tính năng tự động tạm dừng, thì bạn phải kiểm tra để chắc chắn rằng chức năng này được thiết bị hỗ trợ bằng cách sử dụng supportsAutoPauseAndResume. ExerciseClient từ chối các yêu cầu không được thiết bị hỗ trợ.

Ví dụ sau đây sẽ kiểm tra khả năng hỗ trợ cho loại dữ liệu HEART_RATE_BPM, chức năng mục tiêu STEPS_TOTAL và chức năng tự động tạm dừng:

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

Đăng ký nhận thông tin cập nhật về trạng thái tập thể dục

Thông tin cập nhật về bài tập thể dục sẽ được gửi đến trình nghe. Ứng dụng của bạn chỉ có thể đăng ký một trình nghe tại một thời điểm. Thiết lập trình nghe trước khi bắt đầu bài tập thể dục, như trong ví dụ sau. Trình nghe của bạn chỉ nhận thông tin cập nhật về các bài tập mà ứng dụng của bạn sở hữu.

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)

Quản lý thời gian tập thể dục

Dịch vụ sức khoẻ hỗ trợ tối đa một bài tập tại một thời điểm trên mọi ứng dụng của thiết bị. Nếu một bài tập đang được theo dõi và một ứng dụng khác bắt đầu theo dõi một bài tập mới, thì bài tập đầu tiên sẽ chấm dứt.

Trước khi bắt đầu bài tập, hãy làm như sau:

  • Kiểm tra xem một bài tập đã được theo dõi hay chưa và phản ứng theo cách phù hợp. Ví dụ: yêu cầu người dùng xác nhận trước khi ghi đè lên một bài tập thể dục trước đây và bắt đầu theo dõi một bài tập mới.

Ví dụ sau đây cho biết cách kiểm tra một bài tập hiện có bằng 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.
    }
}

Quyền

Khi sử dụng ExerciseClient, hãy đảm bảo ứng dụng của bạn đã yêu cầu và có các quyền cần thiết. Nếu ứng dụng của bạn dùng dữ liệu LOCATION, hãy đảm bảo ứng dụng đó yêu cầu và có các quyền thích hợp đối với kiểu dữ liệu đó.

Đối với mọi loại dữ liệu, trước khi gọi prepareExercise() hoặc startExercise(), hãy làm như sau:

  • Chỉ định các quyền thích hợp cho những loại dữ liệu được yêu cầu trong tệp AndroidManifest.xml.
  • Xác minh rằng người dùng đã cấp các quyền cần thiết. Để biết thêm thông tin, hãy xem phần Yêu cầu quyền cho ứng dụng. Dịch vụ sức khoẻ sẽ từ chối yêu cầu nếu các quyền cần thiết chưa được cấp.

Đối với dữ liệu vị trí, hãy thực hiện thêm các bước sau:

Chuẩn bị tập thể dục

Một số cảm biến, như GPS hoặc nhịp tim, có thể cần chút thời gian để khởi động, hoặc người dùng có thể muốn xem dữ liệu trước khi bắt đầu bài tập thể dục. Phương thức prepareExerciseAsync() không bắt buộc giúp các cảm biến này khởi động và nhận dữ liệu mà không cần khởi động bộ tính giờ cho bài tập thể dục. Thời gian chuẩn bị này không ảnh hưởng đến activeDuration.

Trước khi gọi prepareExerciseAsync(), hãy kiểm tra những thông tin sau:

  • Hãy kiểm tra chế độ cài đặt vị trí trên toàn nền tảng. Người dùng có thể kiểm soát chế độ cài đặt này trong trình đơn Cài đặt chính; khác với hoạt động kiểm tra quyền ở cấp ứng dụng.

    Nếu chế độ cài đặt này tắt, hãy thông báo cho người dùng rằng họ đã từ chối quyền truy cập thông tin vị trí và nhắc họ bật nếu ứng dụng của bạn cần truy cập thông tin vị trí.

  • Xác nhận rằng ứng dụng của bạn có các quyền khi bắt đầu chạy đối với tính năng cảm biến cơ thể, nhận dạng hoạt động và vị trí chính xác. Đối với các quyền bị thiếu, hãy nhắc người dùng cấp quyền khi bắt đầu chạy để cung cấp đầy đủ ngữ cảnh. Nếu người dùng không cấp một quyền cụ thể, hãy xoá các kiểu dữ liệu liên kết với quyền đó khỏi lệnh gọi đến prepareExerciseAsync(). Nếu cả quyền đối với cảm biến cơ thể lẫn quyền truy cập thông tin vị trí đều không được cung cấp, vui lòng không gọi prepareExerciseAsync() vì lệnh gọi chuẩn bị chỉ dùng để lấy dữ liệu về nhịp tim ổn định hoặc bản sửa lỗi GPS trước khi bắt đầu bài tập thể dục. Ứng dụng vẫn có thể nhận được các thông tin về quãng đường theo bước đi, nhịp độ, tốc độ và các chỉ số khác không yêu cầu những quyền đó.

Hãy làm như sau để đảm bảo lệnh gọi đến prepareExerciseAsync() có thể thành công:

  • Sử dụng AmbientLifecycleObserver cho hoạt động trước khi tập luyện có chứa lệnh gọi chuẩn bị.
  • Gọi prepareExerciseAsync() từ dịch vụ trên nền trước. Nếu lệnh này không thuộc một dịch vụ và được liên kết với vòng đời hoạt động, thì việc chuẩn bị cho cảm biến có thể bị tắt một cách không cần thiết.
  • Gọi endExercise() để tắt cảm biến và giảm mức sử dụng pin nếu người dùng di chuyển khỏi phần hoạt động trước khi tập luyện.

Ví dụ sau đây trình bày cách gọi 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.

Sau khi ứng dụng ở trạng thái PREPARING, thông tin cập nhật về tình trạng của cảm biến sẽ được phân phối trong ExerciseUpdateCallback thông qua onAvailabilityChanged(). Sau đó, thông tin này có thể được hiển thị cho người dùng để họ quyết định xem có nên bắt đầu bài tập thể dục hay không.

Bắt đầu bài tập thể dục

Khi bạn muốn bắt đầu một bài tập thể dục, hãy tạo một ExerciseConfig để định cấu hình loại bài tập thể dục, loại dữ liệu mà bạn muốn nhận chỉ số và bất kỳ mục tiêu hoặc mốc quan trọng nào của việc tập thể dục.

Mục tiêu tập thể dục bao gồm một DataType và một điều kiện. Mục tiêu tập thể dục là mục tiêu một lần, được kích hoạt khi người dùng đáp ứng một điều kiện, chẳng hạn như khi người dùng chạy được một quãng đường nhất định. Bạn cũng có thể đặt một mốc quan trọng cho hoạt động tập thể dục. Các mốc quan trọng cho hoạt động tập thể dục có thể được kích hoạt nhiều lần, chẳng hạn như mỗi khi quãng đường chạy của người dùng đạt đến một điểm cố định vượt quá quãng đường họ đã đặt.

Mẫu sau đây trình bày cách tạo một mục tiêu cho mỗi loại:

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

Bạn cũng có thể đánh dấu số vòng cho tất cả các bài tập. Dịch vụ sức khoẻ cung cấp một ExerciseLapSummary với các chỉ số được tổng hợp trong khoảng thời gian lặp lại.

Ví dụ trước cho thấy việc sử dụng isGpsEnabled phải có giá trị là đúng khi yêu cầu dữ liệu vị trí. Tuy nhiên, việc sử dụng GPS cũng có thể hữu ích nhờ có các chỉ số khác. Nếu ExerciseConfig chỉ định quãng đường dưới dạng DataType, thì giá trị này sẽ mặc định sử dụng các bước để ước tính quãng đường. Bạn có thể sử dụng thông tin vị trí để ước tính quãng đường bằng cách tuỳ ý chọn bật GPS.

Tạm dừng, tiếp tục và kết thúc bài tập thể dục

Bạn có thể tạm dừng, tiếp tục và kết thúc bài tập thể dục bằng phương thức thích hợp, chẳng hạn như pauseExerciseAsync() hoặc endExerciseAsync().

Hãy sử dụng trạng thái từ ExerciseUpdate làm nguồn đáng tin cậy. Bài tập thể dục này không được coi là tạm dừng khi lệnh gọi đến pauseExerciseAsync() trả về, mà thay vào đó được coi là tạm dừng khi trạng thái đó được phản ánh trong thông báo ExerciseUpdate. Điều này đặc biệt quan trọng đối với trạng thái giao diện người dùng. Nếu người dùng nhấn vào nút tạm dừng, nút tạm dừng sẽ vô hiệu hoá và gọi pauseExerciseAsync() trên Dịch vụ sức khoẻ. Hãy chờ đến khi Dịch vụ sức khoẻ ở trạng thái tạm dừng bằng cách sử dụng ExerciseUpdate.exerciseStateInfo.state rồi chuyển sang nút tiếp tục. Nguyên nhân là do việc cập nhật trạng thái của Dịch vụ sức khoẻ có thể mất nhiều thời gian hơn so với việc nhấn nút. Vì vậy, nếu bạn liên kết mọi thay đổi về giao diện người dùng với thao tác nhấn nút, thì thao tác này có thể sẽ không đồng bộ với trạng thái của Dịch vụ sức khoẻ.

Hãy lưu ý những điều trên trong các trường hợp sau:

  • Bật tính năng tự động tạm dừng: bài tập thể dục có thể tạm dừng hoặc bắt đầu mà không cần sự tương tác của người dùng.
  • Ứng dụng khác bắt đầu một bài tập thể dục: bài tập thể dục của bạn có thể bị chấm dứt mà không cần sự tương tác của người dùng.

Nếu bài tập thể dục của ứng dụng bị một ứng dụng khác chấm dứt, thì ứng dụng của bạn phải xử lý linh hoạt trường hợp chấm dứt này:

  • Lưu trạng thái từng phần của bài tập thể dục để tiến trình của người dùng không bị xoá.
  • Xoá biểu tượng Hoạt động đang diễn ra và gửi cho người dùng thông báo về việc bài tập thể dục của họ đã bị một ứng dụng khác chấm dứt.

Ngoài ra, hãy xử lý trường hợp các quyền bị thu hồi trong một bài tập đang diễn ra. Trường hợp này được gửi qua trạng thái isEnded với ExerciseEndReasonAUTO_END_PERMISSION_LOST. Xử lý trường hợp này theo cách tương tự như trường hợp chấm dứt: lưu trạng thái từng phần, xoá biểu tượng Hoạt động đang diễn ra và gửi thông báo cho người dùng về những gì đã xảy ra.

Ví dụ sau đây trình bày cách kiểm tra đúng cách trường hợp chấm dứt:

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

Quản lý thời lượng hoạt động

Trong khi tập thể dục, ứng dụng có thể hiển thị thời lượng hoạt động của bài tập thể dục. Ứng dụng, Dịch vụ sức khoẻ và Bộ vi điều khiển (MCU) của thiết bị (bộ xử lý công suất thấp chịu trách nhiệm theo dõi hoạt động tập thể dục) đều phải được đồng bộ hoá cùng với thời lượng hoạt động hiện tại. Để giúp quản lý việc này, Dịch vụ sức khoẻ sẽ gửi ActiveDurationCheckpoint cung cấp một điểm neo mà ứng dụng có thể bắt đầu hẹn giờ từ đó.

Vì thời lượng hoạt động được gửi từ MCU và có thể mất một chút thời gian để đến ứng dụng, nên ActiveDurationCheckpoint chứa 2 thuộc tính:

  • activeDuration: thời lượng tập thể dục
  • time: thời điểm tính toán thời lượng hoạt động nêu trên

Do đó, trong ứng dụng, thời lượng hoạt động của một bài tập thể dục có thể được tính từ ActiveDurationCheckpoint theo phương trình sau:

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

Phương trình này có tính đến khoảng chênh lệch nhỏ giữa thời lượng hoạt động được tính toán trên MCU và khi đến ứng dụng. Bạn có thể sử dụng phương trình này để cài đồng hồ đo thời gian trong ứng dụng và đảm bảo rằng bộ tính giờ của ứng dụng hoàn toàn phù hợp với thời gian trong Dịch vụ sức khoẻ và MCU.

Nếu bài tập thể dục bị tạm dừng, ứng dụng sẽ chờ khởi động lại bộ tính giờ trong giao diện người dùng cho đến khi vượt quá thời gian đã tính toán mà giao diện người dùng hiện đang hiển thị. Điều này là do tín hiệu tạm dừng chuyển đến Dịch vụ sức khoẻ và MCU có độ trễ ngắn. Ví dụ: nếu ứng dụng bị tạm dừng tại thời điểm t = 10 giây, thì có thể Dịch vụ sức khoẻ sẽ không gửi bản cập nhật PAUSED đến ứng dụng cho đến thời điểm t = 10,2 giây.

Làm việc với dữ liệu từ ExerciseClient

Các chỉ số cho kiểu dữ liệu mà ứng dụng của bạn đã đăng ký sẽ được gửi đến trong thông báo ExerciseUpdate.

Bộ xử lý chỉ gửi thông báo khi đang ở trạng thái thức hoặc khi đạt đến thời gian báo cáo tối đa, chẳng hạn như 150 giây một lần. Đừng dựa vào tần suất ExerciseUpdate để đặt trước đồng hồ đo thời gian bằng activeDuration. Hãy xem phần Mẫu bài tập trên GitHub để biết ví dụ về cách triển khai một đồng hồ đo thời gian độc lập.

Khi người dùng bắt đầu một bài tập thể dục, thông báo ExerciseUpdate có thể được gửi thường xuyên, chẳng hạn như mỗi giây. Khi người dùng bắt đầu bài tập thể dục, màn hình có thể tắt. Sau đó, Dịch vụ sức khoẻ có thể phân phối dữ liệu ít thường xuyên hơn, nhưng vẫn được lấy mẫu ở cùng tần suất, để tránh đánh thức bộ xử lý chính. Khi người dùng nhìn vào màn hình, mọi dữ liệu trong quy trình xử lý hàng loạt sẽ được gửi ngay đến ứng dụng của bạn.

Kiểm soát tốc độ phân lô

Trong một số trường hợp, bạn nên kiểm soát tần suất ứng dụng nhận các kiểu dữ liệu nhất định trong khi màn hình đang tắt. Đối tượng BatchingMode cho phép ứng dụng của bạn ghi đè hành vi phân lô mặc định để phân phối dữ liệu thường xuyên hơn.

Để định cấu hình tốc độ phân lô, hãy hoàn thành các bước sau:

  1. Kiểm tra xem thiết bị có hỗ trợ định nghĩa BatchingMode cụ thể hay không:

    // 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. Chỉ định rằng đối tượng ExerciseConfig phải sử dụng một BatchingMode cụ thể, như trong đoạn mã sau.

    val config = ExerciseConfig(
        exerciseType = ExerciseType.WORKOUT,
        dataTypes = setOf(
            DataType.HEART_RATE_BPM,
            DataType.TOTAL_CALORIES
        ),
        // ...
        batchingModeOverrides = setOf(BatchingMode.HEART_RATE_5_SECONDS)
    )
    
  3. Bạn có thể tuỳ ý định cấu hình BatchingMode một cách linh động trong khi tập, thay vì duy trì một hành vi phân lô cụ thể trong suốt thời gian thực hiện bài tập thể dục:

    val desiredModes = setOf(BatchingMode.HEART_RATE_5_SECONDS)
    exerciseClient.overrideBatchingModesForActiveExercise(desiredModes)
    
  4. Để xoá BatchingMode đã tuỳ chỉnh và quay lại hành vi mặc định, hãy chuyển một tập rỗng vào exerciseClient.overrideBatchingModesForActiveExercise().

Dấu thời gian

Thời điểm tại mỗi điểm dữ liệu thể hiện thời lượng kể từ khi thiết bị khởi động. Để chuyển đổi thành dấu thời gian, hãy làm như sau:

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

Sau đó, bạn có thể dùng giá trị này với getStartInstant() hoặc getEndInstant() cho từng điểm dữ liệu.

Độ chính xác của dữ liệu

Một số loại dữ liệu có thể có thông tin chính xác liên quan đến từng điểm dữ liệu. Điều này được thể hiện trong thuộc tính accuracy.

Bạn có thể điền sẵn các lớp HrAccuracyLocationAccuracy cho loại dữ liệu HEART_RATE_BPMLOCATION tương ứng. Nếu có, hãy sử dụng thuộc tính accuracy để xác định xem mỗi điểm dữ liệu có đủ độ chính xác cho ứng dụng của bạn hay không.

Lưu trữ và tải dữ liệu lên

Sử dụng Room để lưu trữ dữ liệu do Dịch vụ sức khoẻ cung cấp. Quá trình tải dữ liệu lên diễn ra vào cuối bài tập thể dục bằng cơ chế như WorkManager. Điều này đảm bảo rằng các lệnh gọi mạng để tải dữ liệu lên được trì hoãn cho đến khi bài tập kết thúc, giảm thiểu mức tiêu thụ pin trong quá trình tập thể dục và đơn giản hoá công việc.

Danh sách kiểm tra tích hợp

Trước khi phát hành ứng dụng sử dụng ExerciseClient của Dịch vụ sức khoẻ, hãy tham khảo danh sách kiểm tra sau đây để đảm bảo trải nghiệm người dùng tránh được một số vấn đề thường gặp. Hãy xác nhận rằng:

  • Ứng dụng của bạn kiểm tra các chức năng của loại bài tập thể dục và chức năng của thiết bị mỗi khi ứng dụng đó chạy. Bằng cách đó, bạn có thể phát hiện khi một thiết bị hoặc bài tập cụ thể không hỗ trợ một trong các loại dữ liệu mà ứng dụng của bạn cần.
  • Bạn yêu cầu và duy trì các quyền cần thiết, đồng thời chỉ định các quyền này trong tệp kê khai. Trước khi gọi prepareExerciseAsync(), ứng dụng của bạn sẽ xác nhận các quyền khi bắt đầu chạy đã được cấp.
  • Ứng dụng của bạn dùng getCurrentExerciseInfoAsync() để xử lý các trường hợp:
    • Một bài tập đang được theo dõi và ứng dụng của bạn sẽ ghi đè lên bài tập thể dục trước đó.
    • Một ứng dụng khác đã chấm dứt hoạt động tập thể dục của bạn. Điều này có thể xảy ra khi người dùng mở lại ứng dụng, họ sẽ nhận được một thông báo giải thích rằng bài tập thể dục đã dừng lại vì một ứng dụng khác đã tiếp quản.
  • Nếu bạn đang sử dụng dữ liệu LOCATION:
    • Ứng dụng của bạn duy trì một ForegroundServiceforegroundServiceType tương ứng trong suốt thời gian thực hiện bài tập thể dục (bao gồm cả lệnh gọi chuẩn bị).
    • Kiểm tra để đảm bảo bạn đã bật GPS trên thiết bị bằng isProviderEnabled(LocationManager.GPS_PROVIDER) và nhắc người dùng mở phần cài đặt vị trí nếu cần.
    • Đối với các trường hợp sử dụng đòi hỏi cao, trong đó việc nhận dữ liệu vị trí với độ trễ thấp là vô cùng quan trọng, hãy cân nhắc việc tích hợp Trình cung cấp vị trí kết hợp (FLP) và sử dụng dữ liệu của trình cung cấp đó làm bản sửa lỗi vị trí ban đầu. Khi Dịch vụ sức khoẻ có thông tin vị trí ổn định hơn, hãy dùng thông tin đó thay vì FLP.
  • Nếu ứng dụng của bạn yêu cầu tải dữ liệu lên, thì mọi lệnh gọi mạng để tải dữ liệu lên sẽ bị trì hoãn cho đến khi bài tập thể dục kết thúc. Nếu không, trong suốt quá trình tập thể dục, ứng dụng sẽ hạn chế thực hiện mọi lệnh gọi mạng cần thiết.