Integracja modułu Wear OS

Zwiększ możliwości aplikacji związane ze zdrowiem i aktywnością fizyczną, rozszerzając ją na urządzenia do noszenia urządzeń z Wear OS.

Dodawanie modułu Wear OS

W Android Studio dostępny jest praktyczny kreator, który pozwala dodać moduł Wear OS do aplikacji. W Plik > W menu Nowy moduł wybierz Wear OS, jak pokazano poniżej obraz:

Kreator modułów Wear OS w Android Studio
Rysunek 1. Tworzenie modułu Wear OS
.

Pamiętaj, że minimalny pakiet SDK musi mieć interfejs API w wersji 30 lub nowszej. aby móc korzystać z najnowszej wersji Usług zdrowotnych. Usługi medyczne ułatwia śledzenie wskaźników i rejestrowanie danych przez skonfigurowanie stanu ich czujniki.

Po zakończeniu pracy w kreatorze zsynchronizuj projekt. Następująca wersja konfiguracja jest wyświetlana:

Obraz przedstawiający przycisk uruchamiania aplikacji na Wear OS
Rysunek 2. Przycisk uruchamiania nowego modułu Wear OS
.

Pozwoli to uruchomić moduł Wear OS na urządzeniu do noszenia. Dostępne są dwie opcje:

Uruchomienie konfiguracji spowoduje wdrożenie aplikacji w emulatorze Wear OS lub na urządzeniu i pokazuje „Witaj świecie”. i uzyskiwanie dodatkowych informacji. To jest podstawowa konfiguracja interfejsu, wykorzystująca Utwórz wiadomość na Wear OS, aby zacząć korzystać z aplikacji.

Dodaj usługi medyczne i Hilt

Zintegruj z modułem Wear OS te biblioteki:

  • Usługi zdrowotne: umożliwia dostęp do czujników i danych na zegarku jest niezwykle wygodne i oszczędne.
  • Hilt: umożliwia skuteczne wstrzykiwanie zależności i zarządzanie nimi.

Tworzenie Menedżera usług zdrowotnych

Aby korzystanie z usług medycznych było nieco wygodniejsze i możliwy był większy i płynniejszego interfejsu API, można utworzyć kod podobny do tego:

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

Po utworzeniu modułu Hilt do zarządzania nim użyj tego fragmentu kodu:

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

możesz wstrzyknąć HealthServicesManager jako dowolną inną zależność Hilt.

Nowa metoda HealthServicesManager udostępnia metodę heartRateMeasureFlow(), która: rejestruje urządzenie śledzące tętno i emituje otrzymane dane.

Włącz aktualizacje danych na urządzeniach do noszenia

Aktualizacje danych związanych z aktywnością fizyczną wymagają uprawnienia BODY_SENSORS. Jeśli jeśli jeszcze tego nie zrobiłeś, zadeklaruj uprawnienie BODY_SENSORS w plik manifestu aplikacji. Następnie poproś o odpowiednie uprawnienie, jak pokazano w tym fragmencie:

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

[...]

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

Jeśli testujesz aplikację na urządzeniu fizycznym, dane powinny zacząć się aktualizować.

Od wersji Wear OS 4 emulatory automatycznie wyświetlają też dane testowe. Poprzedni możesz symulować strumień danych z czujnika. Terminal w oknie, uruchom to polecenie ADB:

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

Aby zobaczyć różne wartości tętna, spróbuj symulować różne ćwiczenia. To polecenie symuluje chodzenie:

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

To polecenie symuluje uruchomienie:

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

Aby zatrzymać symulowanie danych, uruchom to polecenie:

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

Odczytuj dane o tętnie

Po przyznaniu uprawnienia BODY_SENSORS możesz odczytywać tętno użytkownika (heartRateMeasureFlow()) w: HealthServicesManager. W aplikacji Wear OS UI, wyświetlana jest bieżąca wartość tętna mierzona przez czujnik na urządzeniu urządzenia do noszenia.

Zacznij zbierać dane w aplikacji ViewModel za pomocą obiektu przepływu tętna. jak w tym fragmencie:

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

Użyj w tym celu obiektu kompozycyjnego podobnego do poniższego, aby wyświetlić bieżące dane w interfejsu aplikacji:

val heartRate by viewModel.hr

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

Wysyłanie danych na urządzenie przenośne

Aby wysłać dane o zdrowiu i aktywności fizycznej na urządzenie mobilne, użyj aplikacji DataClient na zajęciach do spraw zdrowia. Ten fragment kodu pokazuje, jak wysłać serce dane o szybkości zebranych wcześniej przez aplikację:

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

Odbieranie danych na telefonie

Aby odbierać dane na telefonie, utwórz 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
               
}
           
}
       
}
   
}
}

Zwróć uwagę na kilka interesujących szczegółów:

  • Adnotacja @AndroidEntryPoint pozwala nam używać Hilt w tych zajęciach
  • @Inject lateinit var heartRateMonitor: HeartRateMonitor rzeczywiście wstrzyknij zależność w tej klasie
  • Klasa implementuje funkcję onDataChanged() i otrzymuje kolekcję zdarzeń, które można analizować i używać

Ta logika funkcji HeartRateMonitor umożliwia wysyłanie otrzymanych informacji o tętnie do innej części bazy kodu aplikacji:

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

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

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

Magistrala danych odbiera zdarzenia z metody onDataChanged() i tworzy je dostępnych dla obserwatorów danych za pomocą interfejsu SharedFlow.

Ostatnim elementem jest zadeklarowanie atrybutu Service w aplikacji Telefon 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>

Pokazuj dane w czasie rzeczywistym na urządzeniu mobilnym

W części aplikacji, która działa na urządzeniu mobilnym, wstrzyknij HeartRateMonitor do konstruktora modelu widoku. To urządzenie (HeartRateMonitor) obiekt monitoruje dane o tętnie i w razie potrzeby wysyła aktualizacje interfejsu.