Tích hợp mô-đun Wear OS

Nâng cao trải nghiệm về sức khoẻ và thể hình của ứng dụng bằng cách mở rộng ứng dụng sang thiết bị có thể đeo các thiết bị sử dụng công nghệ Wear OS.

Thêm mô-đun Wear OS

Android Studio cung cấp một trình hướng dẫn tiện lợi để thêm mô-đun Wear OS vào ứng dụng của bạn. Trong Tệp > Trên trình đơn New Module (Mô-đun mới), chọn Wear OS, như minh hoạ trong phần sau đây hình ảnh:

Trình hướng dẫn mô-đun Wear OS trong Android Studio
Hình 1: Tạo mô-đun Wear OS

Điều quan trọng cần lưu ý là Minimum SDK (SDK tối thiểu) phải là API 30 trở lên để cho phép bạn sử dụng phiên bản Dịch vụ sức khoẻ mới nhất. Dịch vụ sức khoẻ giúp theo dõi chỉ số và ghi lại dữ liệu dễ dàng hơn bằng cách định cấu hình cảm biến.

Sau khi hoàn tất trình hướng dẫn, hãy đồng bộ hoá dự án của bạn. Thao tác Run (Chạy) sau đây cấu hình xuất hiện:

Hình ảnh cho thấy nút chạy của ứng dụng Wear OS
Hình 2: Nút chạy cho mô-đun Wear OS mới

Thao tác này cho phép bạn chạy mô-đun Wear OS trên một thiết bị đeo. Bạn có hai tùy chọn:

Việc chạy cấu hình sẽ triển khai ứng dụng cho trình mô phỏng Wear OS hoặc thiết bị và hiện lời "chào mọi người" của bạn. Đây là cách thiết lập giao diện người dùng cơ bản, sử dụng Compose cho Wear OS để làm quen với ứng dụng của bạn.

Thêm Dịch vụ sức khoẻ và Hilt

Tích hợp các thư viện sau vào mô-đun Wear OS:

  • Dịch vụ sức khoẻ: cung cấp quyền truy cập vào các cảm biến và dữ liệu trên đồng hồ rất tiện lợi và tiết kiệm điện hơn.
  • Hilt: Cho phép chèn và quản lý phần phụ thuộc một cách hiệu quả.

Tạo Trình quản lý dịch vụ sức khoẻ

Để việc sử dụng Dịch vụ sức khoẻ thuận tiện hơn một chút và cung cấp và mượt mà hơn, bạn có thể tạo một trình bao bọc như sau:

private const val TAG = "WATCHMAIN"

class HealthServicesManager(context: Context) {
    private val measureClient = HealthServices.getClient(context).measureClient

    suspend fun hasHeartRateCapability() = runCatching {
        val capabilities = measureClient.getCapabilities()
        (DataType.HEART_RATE_BPM in capabilities.supportedDataTypesMeasure)
    }.getOrDefault(false)

    /**
     * Returns a cold flow. When activated, the flow will register a callback for heart rate data
     * and start to emit messages. When the consuming coroutine is canceled, the measure callback
     * is unregistered.
     *
     * [callbackFlow] creates a  bridge between a callback-based API and Kotlin flows.
     */
    @ExperimentalCoroutinesApi
    fun heartRateMeasureFlow(): Flow<MeasureMessage> = callbackFlow {
        val callback = object : MeasureCallback {
            override fun onAvailabilityChanged(dataType: DeltaDataType<*, *>, availability: Availability) {
                // Only send back DataTypeAvailability (not LocationAvailability)
                if (availability is DataTypeAvailability) {
                    trySendBlocking(MeasureMessage.MeasureAvailability(availability))
                }
            }

            override fun onDataReceived(data: DataPointContainer) {
                val heartRateBpm = data.getData(DataType.HEART_RATE_BPM)
                Log.d(TAG, "💓 Received heart rate: ${heartRateBpm.first().value}")
                trySendBlocking(MeasureMessage.MeasureData(heartRateBpm))
            }
        }

        Log.d(TAG, " Registering for data...")
        measureClient.registerMeasureCallback(DataType.HEART_RATE_BPM, callback)

        awaitClose {
            Log.d(TAG, "👋 Unregistering for data")
            runBlocking {
                measureClient.unregisterMeasureCallback(DataType.HEART_RATE_BPM, callback)
            }
        }
    }
}

sealed class MeasureMessage {
    class MeasureAvailability(val availability: DataTypeAvailability) : MeasureMessage()
    class MeasureData(val data: List<SampleDataPoint<Double>>) : MeasureMessage()
}

Sau khi bạn tạo mô-đun Hilt để quản lý, hãy sử dụng đoạn mã sau:

@Module
@InstallIn(SingletonComponent::class)
internal object DataModule {
    @Provides
    @Singleton
    fun provideHealthServices(@ApplicationContext context: Context): HealthServicesManager = HealthServicesManager(context)
}

bạn có thể chèn HealthServicesManager như bất kỳ phần phụ thuộc Hilt nào khác.

HealthServicesManager mới cung cấp phương thức heartRateMeasureFlow() đăng ký một trình nghe cho thiết bị theo dõi tim và phát ra dữ liệu đã nhận.

Cho phép cập nhật dữ liệu trên thiết bị đeo

Việc cập nhật dữ liệu liên quan đến hoạt động thể dục cần có quyền BODY_SENSORS. Nếu bạn chưa thực hiện, hãy khai báo quyền BODY_SENSORS trong tệp kê khai của ứng dụng. Sau đó, hãy yêu cầu cấp quyền như trong đoạn mã sau:

val permissionState = rememberPermissionState(
    permission = Manifest.permission.BODY_SENSORS,
    onPermissionResult = { granted -> /* do something */ }
)

[...]

if (permissionState.status.isGranted) {
    // do something
} else {
    permissionState.launchPermissionRequest()
}

Nếu bạn kiểm thử ứng dụng trên một thiết bị thực, thì dữ liệu sẽ bắt đầu cập nhật.

Kể từ Wear OS 4, trình mô phỏng cũng tự động hiển thị dữ liệu kiểm thử. Trên trang trước các phiên bản, bạn có thể mô phỏng luồng dữ liệu từ cảm biến. Trong thiết bị đầu cuối hãy chạy lệnh ADB này:

adb shell am broadcast \
-a "whs.USE_SYNTHETIC_PROVIDERS" \
com.google.android.wearable.healthservices

Để xem nhiều giá trị tần số tim, hãy thử mô phỏng nhiều bài tập thể dục. Lệnh này mô phỏng hoạt động đi bộ:

adb shell am broadcast \
-a "whs.synthetic.user.START_WALKING" \
com.google.android.wearable.healthservices

Lệnh này mô phỏng hoạt động chạy:

adb shell am broadcast \
-a "whs.synthetic.user.START_RUNNING" \
com.google.android.wearable.healthservices

Để dừng mô phỏng dữ liệu, hãy chạy lệnh sau:

adb shell am broadcast -a \
"whs.USE_SENSOR_PROVIDERS" \
com.google.android.wearable.healthservices

Đọc dữ liệu tần số tim

Khi cấp quyền BODY_SENSORS, bạn có thể đọc nhịp tim của người dùng (heartRateMeasureFlow()) trong HealthServicesManager. Trong ứng dụng Wear OS Giao diện người dùng, giá trị tần số tim hiện tại xuất hiện, được đo bằng cảm biến trên thiết bị đeo của bạn.

Trong ViewModel, hãy bắt đầu thu thập dữ liệu bằng cách sử dụng đối tượng luồng nhịp tim. như minh hoạ trong đoạn mã sau:

val hr: MutableState<Double> = mutableStateOf(0.0)

[...]

healthServicesManager
    .heartRateMeasureFlow()
    .takeWhile { enabled.value }
    .collect { measureMessage ->
        when (measureMessage) {
            is MeasureData -> {
                val latestHeartRateValue = measureMessage.data.last().value
                hr.value = latestHeartRateValue
            }

            is MeasureAvailability -> availability.value =
                    measureMessage.availability
        }
    }

Sử dụng đối tượng có thể kết hợp tương tự như sau để hiển thị dữ liệu trực tiếp trong giao diện người dùng của ứng dụng:

val heartRate by viewModel.hr

Text(
  text = "Heart Rate: $heartRate",
  style = MaterialTheme.typography.display1
)

Gửi dữ liệu đến một thiết bị cầm tay

Để gửi dữ liệu sức khoẻ và hoạt động thể dục đến một thiết bị cầm tay, hãy sử dụng DataClient trong Dịch vụ sức khoẻ. Đoạn mã sau đây minh hoạ cách gửi trái tim mà ứng dụng của bạn thu thập trước đây:

class HealthServicesManager(context: Context) {
    private val dataClient by lazy { Wearable.getDataClient(context) }

[...]

    suspend fun sendToHandheldDevice(heartRate: Int) {
        try {
            val result = dataClient
                .putDataItem(PutDataMapRequest
                    .create("/heartrate")
                    .apply { dataMap.putInt("heartrate", heartRate) }
                    .asPutDataRequest()
                    .setUrgent())
                .await()

            Log.d(TAG, "DataItem saved: $result")
        } catch (cancellationException: CancellationException) {
            throw cancellationException
        } catch (exception: Exception) {
            Log.d(TAG, "Saving DataItem failed: $exception")
        }
    }
}

Nhận dữ liệu trên điện thoại

Để nhận dữ liệu trên điện thoại, hãy tạo một WearableListenerService:

@AndroidEntryPoint
class DataLayerListenerService : WearableListenerService() {

    @Inject
    lateinit var heartRateMonitor: HeartRateMonitor

    override fun onDataChanged(dataEvents: DataEventBuffer) {

        dataEvents.forEach { event ->
            when (event.type) {
                DataEvent.TYPE_CHANGED -> {
                    event.dataItem.run {
                        if (uri.path?.compareTo("/heartrate") == 0) {
                            val heartRate = DataMapItem.fromDataItem(this)
                                    .dataMap.getInt(HR_KEY)
                            Log.d("DataLayerListenerService",
                                    "New heart rate value received: $heartRate")
                            heartRateMonitor.send(heartRate)
                        }
                    }
                }

                DataEvent.TYPE_DELETED -> {
                    // DataItem deleted
                }
            }
        }
    }
}

Sau khi hoàn tất bước này, hãy lưu ý một vài chi tiết thú vị:

  • Chú giải @AndroidEntryPoint cho phép chúng ta sử dụng Hilt trong lớp này
  • @Inject lateinit var heartRateMonitor: HeartRateMonitor thực sự sẽ chèn phần phụ thuộc vào lớp này
  • Lớp này sẽ triển khai onDataChanged() và nhận một tập hợp các sự kiện bạn có thể phân tích cú pháp và sử dụng

Logic HeartRateMonitor sau đây cho phép bạn gửi nhịp tim nhận được vào một phần khác trong cơ sở mã của ứng dụng:

class HeartRateMonitor {
    private val datapoints = MutableSharedFlow<Int>(extraBufferCapacity = 10)

    fun receive(): SharedFlow<Int> = datapoints.asSharedFlow()

    fun send(hr: Int) {
        datapoints.tryEmit(hr)
    }
}

Xe buýt dữ liệu nhận các sự kiện từ phương thức onDataChanged() và tạo các sự kiện đó có sẵn cho trình quan sát dữ liệu bằng SharedFlow.

Bit cuối cùng là khai báo Service trong ứng dụng điện thoại AndroidManifest.xml:

<service
    android:name=".DataLayerListenerService"
    android:exported="true">
    <intent-filter>
        <!-- listeners receive events that match the action and data filters -->
        <action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
        <data
            android:host="*"
            android:pathPrefix="/heartrate"
            android:scheme="wear" />
    </intent-filter>
</service>

Hiển thị dữ liệu theo thời gian thực trên thiết bị cầm tay

Trong phần ứng dụng chạy trên một thiết bị cầm tay, hãy chèn HeartRateMonitor vào hàm khởi tạo của mô hình khung hiển thị. HeartRateMonitor này đối tượng sẽ quan sát dữ liệu tần số tim và gửi nội dung cập nhật giao diện người dùng khi cần.