Du kannst die Gesundheits- und Fitnessfunktionen deiner App verbessern, indem du sie auf Wearables mit Wear OS ausweitest.
Wear OS-Modul hinzufügen
Android Studio bietet einen praktischen Assistenten zum Hinzufügen eines Wear OS-Moduls zu Ihrer App. Wählen Sie im Menü File > New Module die Option Wear OS aus, wie im folgenden Bild dargestellt:
Die Mindest-SDK-Version muss API 30 oder höher sein, damit Sie die aktuelle Version von Health Services verwenden können. Health Services erleichtert das Erfassen von Messwerten und das Aufzeichnen von Daten, da Gesundheitssensoren automatisch konfiguriert werden.
Synchronisieren Sie Ihr Projekt, nachdem Sie den Assistenten durchlaufen haben. Die folgende Run-Konfiguration wird angezeigt:
So können Sie das Wear OS-Modul auf einem Wearable ausführen. Es gibt zwei Möglichkeiten:
Auf einem Emulator ausführen
Auf einem echten Gerät ausführen
Wenn Sie die Konfiguration ausführen, wird die App auf dem Wear OS-Emulator oder -Gerät bereitgestellt und es wird „Hello World“ angezeigt. Dies ist die grundlegende Einrichtung der Benutzeroberfläche mit Compose für Wear OS, um mit Ihrer App zu beginnen.
Health Services und Hilt hinzufügen
Integrieren Sie die folgenden Bibliotheken in Ihr Wear OS-Modul:
- Gesundheitsdienste:Ermöglichen einen einfachen und energieeffizienten Zugriff auf Sensoren und Daten auf der Smartwatch.
- Hilt:Ermöglicht eine effektive Dependency Injection und ‑Verwaltung.
Health Services Manager erstellen
Um die Verwendung von Health Services etwas komfortabler zu gestalten und eine kleinere und reibungslosere API bereitzustellen, können Sie einen Wrapper wie diesen 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 zur Verwaltung erstellt haben, verwenden Sie den folgenden Snippet:
@Module
@InstallIn(SingletonComponent::class)
internal object DataModule {
@Provides
@Singleton
fun provideHealthServices(@ApplicationContext context: Context): HealthServicesManager = HealthServicesManager(context)
}
Sie können HealthServicesManager wie jede andere Hilt-Abhängigkeit einfügen.
Das neue HealthServicesManager bietet eine heartRateMeasureFlow()-Methode, mit der ein Listener für den Herzfrequenzmesser registriert und die empfangenen Daten ausgegeben werden.
Datenupdates auf Wearables aktivieren
Für Aktualisierungen von Fitnessdaten ist die Berechtigung BODY_SENSORS erforderlich. Falls noch nicht geschehen, deklarieren Sie die Berechtigung BODY_SENSORS in der Manifestdatei Ihrer App. 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 werden in Emulatoren auch automatisch Testdaten angezeigt. In früheren Versionen können Sie den Datenstream vom Sensor simulieren. Führen Sie in einem Terminalfenster diesen ADB-Befehl aus:
adb shell am broadcast \
-a "whs.USE_SYNTHETIC_PROVIDERS" \
com.google.android.wearable.healthservices
Wenn du andere Herzfrequenzwerte sehen möchtest, kannst du verschiedene Trainings simulieren. 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 von:
adb shell am broadcast \
-a "whs.synthetic.user.START_RUNNING" \
com.google.android.wearable.healthservices
Führen Sie den folgenden Befehl aus, um die Datensimulation zu beenden:
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 lesen. In der Benutzeroberfläche der Wear OS-App wird der aktuelle Herzfrequenzwert angezeigt, der vom Sensor auf dem Wearable gemessen wird.
Beginnen Sie in Ihrer 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 Benutzeroberfläche Ihrer App anzuzeigen:
val heartRate by viewModel.hr
Text(
text = "Heart Rate: $heartRate",
style = MaterialTheme.typography.display1
)
Daten an ein tragbares Gerät senden
Wenn Sie Gesundheits- und Fitnessdaten an ein tragbares Gerät senden möchten, verwenden Sie die Klasse DataClient in Health Services. Das folgende Code-Snippet zeigt, wie du Herzfrequenzdaten sendest, die deine App zuvor erfasst hat:
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
So empfangen Sie die Daten auf dem Smartphone: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
}
}
}
}
}
Nach Abschluss dieses Schritts werden Sie einige interessante Details bemerken:
- Mit der Annotation
@AndroidEntryPointkönnen wir Hilt in dieser Klasse verwenden. - Die
@Inject lateinit var heartRateMonitor: HeartRateMonitorwird tatsächlich eine Abhängigkeit in diese Klasse einfügen. - Die Klasse implementiert
onDataChanged()und empfängt eine Sammlung von Ereignissen, die Sie parsen und verwenden können.
Mit der folgenden HeartRateMonitor-Logik können Sie die empfangenen Herzfrequenzwerte an einen anderen Teil des App-Codes 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 über einen SharedFlow zur Verfügung.
Der letzte Teil ist die Deklaration von Service in der Smartphone-App 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 tragbaren Gerät anzeigen
Fügen Sie im Teil Ihrer App, der auf einem tragbaren Gerät ausgeführt wird, die HeartRateMonitor in den Konstruktor Ihres Viewmodels ein. Dieses HeartRateMonitor-Objekt beobachtet die Herzfrequenzdaten und gibt bei Bedarf UI-Updates aus.