יש לך אפשרות לשפר את חוויית הבריאות והכושר של האפליקציה באמצעות הרחבה שלה לגאדג'טים לבישים מכשירים שמופעלים על ידי Wear OS.
הוספת מודול של Wear OS
ב-Android Studio יש אשף שימושי להוספת מודול Wear OS לאפליקציה. לחשבון את File > בתפריט 'מודול חדש', בוחרים באפשרות Wear OS, כמו שמוצג בהמשך תמונה:
חשוב לציין שערך ה-SDK המינימלי חייב להיות מבוסס על API מגרסה 30 ואילך כדי לאפשר לך להשתמש בגרסה העדכנית ביותר של שירותי הבריאות. שירותי בריאות מאפשר לעקוב בקלות אחרי מדדים ולתעד נתונים על ידי הגדרת תקינות חיישנים אוטומטיים.
לאחר השלמת האשף, מסנכרנים את הפרויקט. ההרצה הבאה: מופיעה:
ההרשאה הזו מאפשרת להריץ את המודול של Wear OS במכשיר לביש. עומדות לרשותך שתי אפשרויות:
מפעילים אמולטור.
צריך לפעול במכשיר אמיתי.
כשמפעילים את ההגדרות האישיות, האפליקציה פורסת את האמולטור של Wear OS או ומראה 'שלום עולם' חוויה אישית. זו ההגדרה הבסיסית של ממשק המשתמש, באמצעות כתיבת הודעה ל-Wear OS כדי להתחיל לעבוד עם האפליקציה.
הוספת שירותי בריאות והיטל
משלבים את הספריות הבאות במודול Wear OS:
- Health Services: מאפשרת גישה לחיישנים ולנתונים בשעון נוח מאוד וחסכוני יותר בחשמל.
- Hilt: מאפשר החדרה וניהול יעילים של תלות.
יצירת חשבון של מנהל שירותי הבריאות
כדי להפוך את השימוש בשירותי בריאות לנוח יותר, וכדי לחשוף ו-smoother API, אפשר ליצור wrapper באופן הבא:
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()
}
אחרי שיוצרים את מודול Hilt כדי לנהל אותו, באמצעות קטע הקוד הבא:
@Module
@InstallIn(SingletonComponent::class)
internal object DataModule {
@Provides
@Singleton
fun provideHealthServices(@ApplicationContext context: Context): HealthServicesManager = HealthServicesManager(context)
}
אפשר להחדיר את HealthServicesManager
כמו כל תלות אחרת של Hilt.
הגרסה החדשה של HealthServicesManager
מספקת את ה-method heartRateMeasureFlow()
רושם מאזינים למוניטור הלב ופולט את הנתונים שהתקבלו.
הפעלת עדכוני נתונים במכשירים לבישים
כדי לעדכן נתונים שקשורים לכושר, נדרשת ההרשאה BODY_SENSORS
. אם
עדיין לא עשית זאת, עליך להצהיר על ההרשאה BODY_SENSORS
ב
בקובץ המניפסט של האפליקציה. לאחר מכן מבקשים את ההרשאה, כפי שמוצג בקטע הקוד הזה:
val permissionState = rememberPermissionState(
permission = Manifest.permission.BODY_SENSORS,
onPermissionResult = { granted -> /* do something */ }
)
[...]
if (permissionState.status.isGranted) {
// do something
} else {
permissionState.launchPermissionRequest()
}
אם בודקים את האפליקציה במכשיר פיזי, הנתונים אמורים להתחיל להתעדכן.
החל מ-Wear OS 4, אמולטורים מציגים גם את נתוני הבדיקה באופן אוטומטי. ב- גרסאות שונות, אפשר לדמות את זרם הנתונים מהחיישן. במסוף ואז מריצים את פקודת ה-ADB הזו:
adb shell am broadcast \
-a "whs.USE_SYNTHETIC_PROVIDERS" \
com.google.android.wearable.healthservices
כדי לראות ערכי דופק שונים, כדאי לנסות לדמות תרגילים שונים. הפקודה הזו מדמה הליכה:
adb shell am broadcast \
-a "whs.synthetic.user.START_WALKING" \
com.google.android.wearable.healthservices
הפקודה הזו מדמה ריצה:
adb shell am broadcast \
-a "whs.synthetic.user.START_RUNNING" \
com.google.android.wearable.healthservices
כדי להפסיק את הדמיית הנתונים, מריצים את הפקודה הבאה:
adb shell am broadcast -a \
"whs.USE_SENSOR_PROVIDERS" \
com.google.android.wearable.healthservices
קריאת נתוני הדופק
אם ניתנה ההרשאה BODY_SENSORS
, אפשר לקרוא את הדופק של המשתמש
(heartRateMeasureFlow()
) בHealthServicesManager
. באפליקציה ל-Wear OS
ממשק משתמש, מופיע ערך הדופק הנוכחי הנמדד על ידי החיישן
מכשיר לביש.
במכשיר ViewModel
, מתחילים לאסוף נתונים באמצעות האובייקט של זרימת הלב,
כפי שמוצג בקטע הקוד הבא:
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
}
}
צריך להשתמש באובייקט קומפוזבילי דומה לזה כדי להציג את הנתונים הפעילים ממשק המשתמש של האפליקציה:
val heartRate by viewModel.hr
Text(
text = "Heart Rate: $heartRate",
style = MaterialTheme.typography.display1
)
שליחת נתונים למכשיר נייד
כדי לשלוח נתוני בריאות וכושר למכשיר נישא, צריך להשתמש ב-DataClient
בכיתה בשירותי בריאות. בקטע הקוד הבא מוסבר איך לשלוח לב
דירוג הנתונים שהאפליקציה שלך אספה בעבר:
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")
}
}
}
קבלת הנתונים בטלפון
כדי לקבל את הנתונים בטלפון,
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
}
}
}
}
}
לאחר השלמת השלב הזה, תוכלו לראות כמה פרטים מעניינים:
- ההערה
@AndroidEntryPoint
מאפשרת לנו להשתמש ב-Hilt בכיתה הזו @Inject lateinit var heartRateMonitor: HeartRateMonitor
אכן להחדיר תלות במחלקה הזו- הכיתה מטמיעה את
onDataChanged()
ומקבלת אוסף של אירועים שאפשר לנתח ולהשתמש
לוגיקת HeartRateMonitor
הבאה מאפשרת לשלוח את קצב הלב שהתקבל
לחלק אחר ב-codebase של האפליקציה:
class HeartRateMonitor {
private val datapoints = MutableSharedFlow<Int>(extraBufferCapacity = 10)
fun receive(): SharedFlow<Int> = datapoints.asSharedFlow()
fun send(hr: Int) {
datapoints.tryEmit(hr)
}
}
אפיק נתונים מקבל את האירועים מהשיטה onDataChanged()
והופך אותם
זמין לתצפיתנים באמצעות SharedFlow
.
החלק האחרון הוא ההצהרה של Service
באפליקציה לטלפון
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>
הצגת נתונים בזמן אמת במכשיר נישא
בחלק של האפליקציה שפועל במכשיר נייד, מחדירים את
HeartRateMonitor
ב-constructor של המודל של התצוגה המפורטת. הHeartRateMonitor
הזה
האובייקט בודק את נתוני הדופק ופולט עדכונים בממשק המשתמש לפי הצורך.