دمج وحدة Wear OS

حسِّن تجربة الصحة واللياقة البدنية لتطبيقك من خلال توسيعه على الأجهزة القابلة للارتداء. الأجهزة التي تعمل بنظام Wear OS

إضافة وحدة Wear OS

يوفّر "استوديو Android" معالجًا مفيدًا لإضافة وحدة Wear OS إلى تطبيقك. ضِمن ملف > قائمة وحدة جديدة، حدد Wear OS كما هو موضح في ما يلي الصورة:

معالج وحدة Wear OS في "استوديو Android"
الشكل 1: إنشاء وحدة Wear OS

من المهم ملاحظة أنّ الحدّ الأدنى لحزمة تطوير البرامج (SDK) يجب أن يكون API 30 أو أعلى. للسماح لك باستخدام أحدث إصدار من "الخدمات الصحية". خدمات الصحة تسهِّل تتبع المقاييس وتسجيل البيانات من خلال ضبط السلامة أجهزة الاستشعار تلقائيًا.

بعد إكمال المعالج، يمكنك مزامنة مشروعك. تشغيل الإعدادات:

صورة تعرض زر تشغيل تطبيق Wear OS
الشكل 2: زر التشغيل في وحدة Wear OS الجديدة

يتيح لك ذلك تشغيل وحدة 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 يلاحظ بيانات معدل ضربات القلب ويُصدر تحديثات واجهة المستخدم حسب الحاجة.