يمكنك تحسين تجربة الصحة واللياقة البدنية في تطبيقك من خلال توسيع نطاقها ليشمل الأجهزة القابلة للارتداء التي تعمل بنظام التشغيل Wear OS.
إضافة وحدة Wear OS
يوفّر "استوديو Android" معالجًا مفيدًا لإضافة وحدة Wear OS إلى تطبيقك. في قائمة ملف > وحدة جديدة، اختَر Wear OS، كما هو موضّح في الصورة التالية:
يُرجى العِلم أنّ الحدّ الأدنى لإصدار حزمة تطوير البرامج (SDK) يجب أن يكون المستوى 30 لواجهة برمجة التطبيقات أو أعلى ليتيح لك استخدام أحدث إصدار من "خدمات الصحة". تسهّل "خدمات الصحة" تتبُّع المقاييس وتسجيل البيانات من خلال ضبط مستشعرات الصحة تلقائيًا.
بعد إكمال المعالج، زامِن مشروعك. يظهر إعداد التشغيل التالي:
يتيح لك ذلك تشغيل وحدة Wear OS على جهاز قابل للارتداء. أمامك خياران:
التشغيل على محاكي
تشغيل التطبيق على جهاز حقيقي
يؤدي تنفيذ عملية الإعداد إلى نشر التطبيق على محاكي Wear OS أو الجهاز وعرض تجربة "hello world". هذا هو الإعداد الأساسي لواجهة المستخدم باستخدام Compose for Wear OS لبدء استخدام تطبيقك.
إضافة Health Services وHilt
ادمج المكتبات التالية في وحدة Wear OS:
- خدمات الصحة: تسهّل الوصول إلى أجهزة الاستشعار والبيانات على الساعة، كما أنّها أكثر كفاءة في استهلاك الطاقة.
- Hilt: تتيح إمكانية إدارة عمليات إدخال التبعيات وتنفيذها بفعالية.
إنشاء أداة Health Services Manager
لتسهيل استخدام خدمات الصحة، ولعرض واجهة برمجة تطبيقات أصغر حجمًا وأكثر سلاسة، يمكنك إنشاء برنامج تضمين على النحو التالي:
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 طريقة 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 التالي إرسال قيم معدّل ضربات القلب التي تم تلقّيها إلى جزء آخر من قاعدة الرموز البرمجية لتطبيقك:
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 في أداة إنشاء نموذج العرض. يراقب عنصر HeartRateMonitor
هذا بيانات معدّل نبضات القلب ويُصدر تعديلات على واجهة المستخدم حسب الحاجة.