Wear OS-Modul einbinden

Verbessere die Gesundheits- und Fitnessfunktionen deiner App, indem du sie auf Wearable-Geräte mit Wear OS ausweitest.

Wear OS-Modul hinzufügen

In Android Studio gibt es einen praktischen Assistenten, mit dem Sie Ihrer App ein Wear OS-Modul hinzufügen können. Wählen Sie im Menü File > New Module (Datei > Neues Modul) die Option Wear OS aus, wie in der folgenden Abbildung gezeigt:

Assistent für Wear OS-Module in Android Studio
Abbildung 1: Wear OS-Modul erstellen

Wichtig: Die Mindestens erforderliche SDK-Version muss API 30 oder höher sein, damit Sie die neueste Version von Health Services nutzen können. Health Services erleichtert das Verfolgen von Messwerten und Aufzeichnen von Daten durch automatische Konfiguration von Gesundheitssensoren.

Nachdem Sie den Assistenten abgeschlossen haben, synchronisieren Sie Ihr Projekt. Die folgende Ausführungskonfiguration wird angezeigt:

Bild, auf dem die Schaltfläche zum Ausführen der Wear OS-App zu sehen ist
Abbildung 2: Schaltfläche „Ausführen“ für das neue Wear OS-Modul

So lässt sich das Wear OS-Modul auf einem Wearable-Gerät ausführen. Es gibt zwei Möglichkeiten:

Wenn Sie die Konfiguration ausführen, wird die App für den Wear OS-Emulator oder das Wear OS-Gerät bereitgestellt und es wird ein „Hallo Welt“-Erlebnis angezeigt. Dies ist die grundlegende Einrichtung der Benutzeroberfläche mit Composer für Wear OS, um Ihre App zu erstellen.

Gesundheitsdienste und Handgriff hinzufügen

Binde die folgenden Bibliotheken in dein Wear OS-Modul ein:

  • Gesundheitsdienste:Ermöglicht den Zugriff auf Sensoren und Daten der Uhr sehr bequem und energieeffizienter.
  • Hilt:Ermöglicht eine effektive Injektion und Verwaltung von Abhängigkeiten.

Manager für Gesundheitsdienste erstellen

Um die Verwendung von Health Services zu vereinfachen und eine kleinere und reibungslosere API bereitzustellen, können Sie einen Wrapper wie den folgenden erstellen:

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

Nachdem Sie das Hilt-Modul zu dessen Verwaltung erstellt haben, verwenden Sie das folgende Snippet:

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

können Sie das HealthServicesManager als jede andere Hilt-Abhängigkeit einschleusen.

Die neue HealthServicesManager bietet eine heartRateMeasureFlow()-Methode, die einen Listener für den Herzmonitor registriert und die empfangenen Daten ausgibt.

Datenupdates auf Wearable-Geräten aktivieren

Für die Aktualisierung von Fitnessdaten ist die Berechtigung „BODY_SENSORS“ erforderlich. Du musst die Berechtigung BODY_SENSORS in der Manifestdatei deiner App deklarieren, falls noch nicht geschehen. Fordern Sie dann die Berechtigung an, wie in diesem Snippet gezeigt:

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

[...]

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

Wenn Sie Ihre App auf einem physischen Gerät testen, sollten die Daten aktualisiert werden.

Ab Wear OS 4 zeigen Emulatoren auch automatisch Testdaten an. In früheren Versionen können Sie den Datenstream des Sensors simulieren. Führen Sie in einem Terminalfenster den folgenden ADB-Befehl aus:

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

Simuliere verschiedene Übungen, um unterschiedliche Herzfrequenzwerte zu sehen. Dieser Befehl simuliert das Gehen:

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

Dieser Befehl simuliert die Ausführung:

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

Um die Simulation der Daten zu beenden, führen Sie den folgenden Befehl aus:

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

Herzfrequenzdaten lesen

Mit der Berechtigung BODY_SENSORS können Sie die Herzfrequenz des Nutzers (heartRateMeasureFlow()) im HealthServicesManager ablesen. Auf der Benutzeroberfläche der Wear OS-App wird der aktuelle Herzfrequenzwert angezeigt, der vom Sensor des Wearable-Geräts gemessen wird.

Beginnen Sie in Ihrem ViewModel mit der Erfassung von Daten mithilfe des Herzfrequenz-Flow-Objekts, wie im folgenden Snippet gezeigt:

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

Verwenden Sie ein zusammensetzbares Objekt ähnlich dem folgenden, um die Live-Daten in der UI Ihrer App anzuzeigen:

val heartRate by viewModel.hr

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

Daten an ein Handheld-Gerät senden

Verwenden Sie die Klasse DataClient in Health Services, um Gesundheits- und Fitnessdaten an ein Handheld-Gerät zu senden. Das folgende Code-Snippet zeigt, wie zuvor von deiner App erfasste Herzfrequenzdaten gesendet werden:

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

Daten auf dem Smartphone empfangen

Erstellen Sie einen WearableListenerService, um die Daten auf dem Smartphone zu empfangen:

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

Nachdem Sie diesen Schritt abgeschlossen haben, finden Sie einige interessante Details:

  • Mit der Annotation @AndroidEntryPoint können wir Hilt in dieser Klasse verwenden.
  • @Inject lateinit var heartRateMonitor: HeartRateMonitor schleust tatsächlich eine Abhängigkeit in diese Klasse ein.
  • Die Klasse implementiert onDataChanged() und empfängt eine Sammlung von Ereignissen, die Sie parsen und verwenden können.

Mit der folgenden HeartRateMonitor-Logik kannst du die empfangenen Herzfrequenzwerte an einen anderen Teil der Codebasis deiner App senden:

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

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

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

Ein Datenbus empfängt die Ereignisse von der Methode onDataChanged() und stellt sie Datenbeobachtern mithilfe einer SharedFlow zur Verfügung.

Das letzte Bit ist die Deklaration von Service in der Telefonanwendung 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>

Echtzeitdaten auf einem Handheld-Gerät anzeigen

Fügen Sie in dem Teil der App, der auf einem Handheld-Gerät ausgeführt wird, das HeartRateMonitor in den Konstruktor des Ansichtsmodells ein. Dieses HeartRateMonitor-Objekt überwacht die Herzfrequenzdaten und gibt bei Bedarf Aktualisierungen der Benutzeroberfläche aus.