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