Giám sát dữ liệu ở chế độ nền

Các bản cập nhật dữ liệu thụ động phù hợp với những ứng dụng cần theo dõi dữ liệu Dịch vụ sức khoẻ ở chế độ nền. Các bản cập nhật này dùng trong các trường hợp sử dụng kéo dài nhiều giờ, nhiều ngày hoặc thậm chí lâu hơn. Nếu cần lưu trữ hoặc xử lý dữ liệu sức khoẻ khi ứng dụng không chạy cũng như người dùng không có hoạt động thể dục thể thao một cách rõ ràng, bạn nên dùng ứng dụng (client) thụ động của Dịch vụ sức khoẻ.

Để biết ví dụ về cách sử dụng dữ liệu thụ động, hãy xem mẫu Dữ liệu thụ độngMục tiêu thụ động 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.

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

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

Trước khi đăng ký nhận thông tin cập nhật dữ liệu, hãy kiểm tra để đảm bảo rằng thiết bị có thể cung cấp loại dữ liệu mà ứng dụng của bạn cần. Việc kiểm tra các chức năng giúp bạn có thể bật hoặc tắt một số tính năng hoặc sửa đổi giao diện người dùng của ứng dụng để bù cho những tính năng không có.

val healthClient = HealthServices.getClient(this /*context*/)
val passiveMonitoringClient = healthClient.passiveMonitoringClient
lifecycleScope.launchWhenCreated {
    val capabilities = passiveMonitoringClient.capabilities.await()
    // Supported types for passive data collection
    supportsHeartRate =
        DataType.HEART_RATE_BPM in capabilities.supportedDataTypesPassiveMonitoring
    // Supported types for PassiveGoals
    supportsStepsGoal =
        DataType.STEPS_DAILY in capabilities.supportedDataTypesPassiveGoals
}

Đăng ký nhận dữ liệu thụ động

Bạn có thể nhận dữ liệu thụ động thông qua một dịch vụ, lệnh gọi lại hoặc cả hai. Dịch vụ này cho phép ứng dụng của bạn nhận dữ liệu ở chế độ nền khi không có phần nào của ứng dụng xuất hiện ở nền trước. Khi bạn nhận được dữ liệu ở chế độ nền, dữ liệu đó sẽ được phân phối theo lô. Lệnh gọi lại nhận được dữ liệu với tốc độ nhanh hơn một chút, nhưng chỉ khi ứng dụng đang chạy và lệnh gọi lại đó được thông báo thành công.

Dù bạn sử dụng phương thức nào, trước tiên, hãy tạo PassiveListenerConfig để xác định loại dữ liệu sẽ nhận như trong ví dụ sau:

val passiveListenerConfig = PassiveListenerConfig.builder()
    .setDataTypes(setOf(DataType.HEART_RATE_BPM))
    .build()

Để nhận dữ liệu bằng lệnh gọi lại, hãy xác định và đăng ký lệnh gọi lại như trong ví dụ sau:

val passiveListenerCallback: PassiveListenerCallback = object : PassiveListenerCallback {
    override fun onNewDataPointsReceived(dataPoints: DataPointContainer) {
        // TODO: Do something with dataPoints
    }
}

passiveMonitoringClient.setPassiveListenerCallback(
    passiveListenerConfig,
    passiveListenerCallback
)

// To remove the listener
passiveMonitoringClient.clearPassiveListenerCallbackAsync()

Việc sử dụng một dịch vụ cũng tương tự như vậy, nhưng thay vì tạo một lớp bắt nguồn từ PassiveListenerCallback, hãy lấy dữ liệu từ PassiveListenerService như trong ví dụ sau:

class PassiveDataService : PassiveListenerService() {
    override fun onNewDataPointsReceived(dataPoints: DataPointContainer) {
        // TODO: Do something with dataPoints
    }
}

passiveMonitoringClient.setPassiveListenerServiceAsync(
    PassiveDataService::class.java,
    passiveListenerConfig
)

Tiếp theo, hãy khai báo dịch vụ trong tệp AndroidManifest.xml. Cần có quyền truy cập Dịch vụ sức khoẻ để đảm bảo rằng chỉ Dịch vụ sức khoẻ mới có thể liên kết với dịch vụ này:

<service android:name=".PassiveDataService"
    android:permission="com.google.android.wearable.healthservices.permission.PASSIVE_DATA_BINDING"
    android:exported="true" />

Thời gian thông dịch

Dữ liệu bạn nhận được từ Dịch vụ sức khoẻ được phân theo lô. Do đó, bạn có thể nhận các điểm dữ liệu thuộc nhiều loại, hoặc nhiều điểm dữ liệu cùng loại, trong cùng một lô. Hãy sử dụng dấu thời gian có trong các đối tượng này thay vì thời gian mà ứng dụng nhận được để xác định thứ tự chính xác của các sự kiện.

Lấy dấu thời gian của từng DataPoint, trước hết bằng cách tính toán dấu thời gian khởi động, như trong ví dụ sau:

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

Sau đó, giá trị này có thể được truyền đến getStartInstant() hoặc getEndInstant().

Khôi phục yêu cầu đăng ký sau khi khởi động

Yêu cầu đăng ký dữ liệu thụ động không được duy trì sau khi khởi động lại. Để nhận dữ liệu sau khi thiết bị khởi động lại, hãy tạo lại các yêu cầu đăng ký của bạn bằng cách sử dụng BroadcastReceiver có vai trò nghe tin do hệ thống truyền ra về ACTION_BOOT_COMPLETED.

Trong trình nhận, đừng tìm cách khôi phục trực tiếp các yêu cầu đăng ký. Thay vào đó, hãy uỷ quyền chức năng này cho một trình thực thi WorkManager. Khi thiết bị khởi động, Dịch vụ sức khoẻ có thể mất ít nhất 10 giây để xác nhận yêu cầu đăng ký dữ liệu thụ động. Quá trình này có thể vượt quá thời gian thực thi cho phép của BroadcastReceiver. Ngược lại, trình thực thi WorkManagergiới hạn thực thi là 10 phút.

Đoạn mã sau đây minh hoạ các dạng của BroadcastReceiver:

class StartupReceiver : BroadcastReceiver() {

   override fun onReceive(context: Context, intent: Intent) {
       if (intent.action != Intent.ACTION_BOOT_COMPLETED) return


       // TODO: Check permissions first
       WorkManager.getInstance(context).enqueue(
           OneTimeWorkRequestBuilder<RegisterForPassiveDataWorker>().build()
       )
   }
}

class RegisterForPassiveDataWorker(
   private val appContext: Context,
   workerParams: WorkerParameters
) : Worker(appContext, workerParams) {

   override fun doWork(): Result {
       runBlocking {
           HealthServices.getClient(appContext)
                .passiveMonitoringClient
                .setPassiveListenerCallback(...)
       }
       return Result.success()
   }
}

Để sắp xếp cho hệ thống thực thi đoạn mã này khi thiết bị khởi động, hãy thực hiện 2 thay đổi đối với tệp AndroidManifest.xml.

Trước tiên, hãy thêm quyền sau đây ở dạng phần tử con của <manifest>:

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

Tiếp theo, hãy thêm bộ lọc ý định của trình nhận sau đây làm phần tử con của <application>:

<receiver
    android:name=".StartupReceiver"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

Trạng thái hoạt động

Ứng dụng (client) thụ động cũng có thể cung cấp thông tin cấp cao về trạng thái người dùng, chẳng hạn như liệu người dùng có đang ngủ hay không. Để nhận những thông tin cập nhật này, hãy làm theo các bước sau:

  1. Yêu cầu quyền ACTIVITY_RECOGNITION.
  2. Gọi setShouldUserActivityInfoBeRequested(true) trong trình tạo PassiveListenerConfig.

Ghi đè phương thức onUserActivityInfoReceived() trong lệnh gọi lại hoặc dịch vụ, đồng thời sử dụng UserActivityInfo được trả về như trong ví dụ sau:

override fun onUserActivityInfoReceived(info: UserActivityInfo) {
    val stateChangeTime: Instant = info.stateChangeTime // may be in the past!
    val userActivityState: UserActivityState = info.userActivityState
    if (userActivityState == UserActivityState.USER_ACTIVITY_ASLEEP) {
        // ...
    }
}

Mục tiêu thụ động

Bạn có thể định cấu hình ứng dụng (client) thụ động để thông báo cho ứng dụng khi đạt được các mục tiêu thụ động, chẳng hạn như người dùng hoàn thành 10.000 bước trong 1 ngày.

Để làm việc này, hãy tạo một mục tiêu như trong ví dụ sau:

val dailyStepsGoal by lazy {
    val condition = DataTypeCondition(
        dataType = DataType.STEPS_DAILY,
        threshold = 10_000, // Trigger every 10000 steps
        comparisonType = ComparisonType.GREATER_THAN_OR_EQUAL
    )
    PassiveGoal(condition)
}

Thêm mục tiêu này vào PassiveListenerConfig như trong ví dụ sau:

val passiveListenerConfig = PassiveListenerConfig.builder()
    .setDailyGoals(setOf(dailyStepsGoal))
    .build()

Ghi đè phương thức onGoalCompleted() trong lệnh gọi lại hoặc dịch vụ, đồng thời sử dụng PassiveGoal được trả về như trong ví dụ sau:

override fun onGoalCompleted(goal: PassiveGoal) {
    when (goal.dataTypeCondition.dataType) {
        DataType.STEPS_DAILY -> {
            // ...
        }
    }
}