تسجيل تمرين باستخدام ExerciseClient

توفّر "خدمات الصحة" الدعم لتطبيقات التمرين من خلال ExerciseClient. باستخدام ExerciseClient، يمكن لتطبيقك التحكّم في وقت بدء تمرين، وإضافة أهداف للتمرين، وتلقّي إشعارات بشأن حالة التمرين أو أحداث التمرين أو مقاييس أخرى. لمزيد من المعلومات، يمكنك الاطّلاع على القائمة الكاملة لأنواع التمارين التي تتوافق مع "خدمات الصحة".

اطّلِع على نموذج التمرين على GitHub.

إضافة عناصر تابعة

لإضافة اعتمادية على "خدمات الصحة"، يجب تضمين مستودع Google Maven في مشروعك. لمزيد من المعلومات، راجِع مستودع Maven من Google.

بعد ذلك، أضِف التبعية التالية في ملف build.gradle على مستوى الوحدة:

رائع

dependencies {
    implementation "androidx.health:health-services-client:1.1.0-alpha05"
}

Kotlin

dependencies {
    implementation("androidx.health:health-services-client:1.1.0-alpha05")
}

بنية التطبيق

استخدِم بنية التطبيق التالية عند إنشاء تطبيق للتمارين الرياضية باستخدام "خدمات الصحة":

عند الاستعداد لممارسة التمارين وأثناء ممارسة التمارين، قد يتم إيقاف نشاطك لأسباب مختلفة. قد ينتقل المستخدم إلى تطبيق آخر أو يعود إلى خلفية شاشة الساعة. قد يعرض النظام شيئًا فوق نشاطك، أو قد يتم إيقاف الشاشة بعد فترة من عدم النشاط. استخدِم ForegroundService الذي يعمل باستمرار مع ExerciseClient للمساعدة في ضمان التشغيل الصحيح للتمرين بأكمله.

يتيح لك استخدام ForegroundService الاستفادة من Ongoing Activity API لعرض مؤشر على مساحات شاشة الساعة، ما يتيح للمستخدم العودة سريعًا إلى التمرين.

من الضروري أن تطلب بيانات الموقع الجغرافي بشكل مناسب في خدمتك التي تعمل في المقدّمة. في ملف البيان، حدِّد أنواع الخدمات التي تعمل في المقدّمة والأذونات اللازمة:

<manifest ...>
  <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <application ...>
    
      <!-- If your app is designed only for devices that run Wear OS 4
           or lower, use android:foregroundServiceType="location" instead. -->
      <service
          android:name=".MyExerciseSessionRecorder"
          android:foregroundServiceType="health|location">
      </service>
      
    </application>
</manifest>

استخدِم AmbientLifecycleObserver لنشاطك قبل التمرين، والذي يتضمّن طلب prepareExercise()، ولنشاطك أثناء التمرين. ومع ذلك، لا تعدِّل الشاشة أثناء التمرين في وضع العرض المحيطي، لأنّ &quot;خدمات الصحة&quot; تجمع بيانات التمرين عندما تكون شاشة الجهاز في وضع العرض المحيطي لتوفير الطاقة، وبالتالي قد لا تكون المعلومات المعروضة حديثة. أثناء التمارين، اعرض بيانات منطقية للمستخدم، إما معلومات حديثة أو شاشة فارغة.

التحقّق من الإمكانات

يتوافق كل ExerciseType مع أنواع معيّنة من البيانات الخاصة بالمقاييس وأهداف التمارين الرياضية. تحقَّق من هذه الإمكانات عند بدء التشغيل، لأنّها قد تختلف حسب الجهاز. قد لا يتيح الجهاز نوعًا معيّنًا من التمارين أو وظيفة معيّنة، مثل الإيقاف المؤقت التلقائي. بالإضافة إلى ذلك، قد تتغيّر إمكانات الجهاز بمرور الوقت، مثلاً بعد تحديث البرنامج.

عند بدء تشغيل التطبيق، اطلب إذن الوصول إلى إمكانات الجهاز وخزِّن البيانات التالية وعالجها:

  • التمارين التي تتيحها المنصة
  • الميزات المتوافقة مع كل تمرين
  • أنواع البيانات المتوافقة مع كل تمرين
  • الأذونات المطلوبة لكل نوع من أنواع البيانات هذه

استخدِم ExerciseCapabilities.getExerciseTypeCapabilities() مع نوع التمرين الذي اخترته لمعرفة أنواع المقاييس التي يمكنك طلبها، وأهداف التمرين التي يمكنك ضبطها، والميزات الأخرى المتاحة لهذا النوع. يظهر ذلك في المثال التالي:

val healthClient = HealthServices.getClient(this /*context*/)
val exerciseClient = healthClient.exerciseClient
lifecycleScope.launch {
    val capabilities = exerciseClient.getCapabilitiesAsync().await()
    if (ExerciseType.RUNNING in capabilities.supportedExerciseTypes) {
        runningCapabilities =
            capabilities.getExerciseTypeCapabilities(ExerciseType.RUNNING)
    }
}

داخل ExerciseTypeCapabilities التي تم إرجاعها، supportedDataTypes تدرِج أنواع البيانات التي يمكنك طلب بياناتها. يختلف ذلك حسب الجهاز، لذا احرص على عدم طلب DataType غير متوافق، وإلا قد يتعذّر تنفيذ طلبك.

استخدِم الحقلَين supportedGoals و supportedMilestones لتحديد ما إذا كان التمرين يمكنه تحقيق هدف تمرين تريد إنشاءه.

إذا كان تطبيقك يتيح للمستخدم إمكانية استخدام ميزة "الإيقاف التلقائي"، عليك التأكّد من أنّ الجهاز يتيح هذه الوظيفة باستخدام supportsAutoPauseAndResume. ‫ExerciseClient يرفض الطلبات غير المتوافقة مع الجهاز.

يتحقّق المثال التالي من إمكانية استخدام نوع البيانات HEART_RATE_BPM، وإمكانية الهدف STEPS_TOTAL، ووظيفة الإيقاف المؤقت التلقائي:

// Whether we can request heart rate metrics.
supportsHeartRate = DataType.HEART_RATE_BPM in runningCapabilities.supportedDataTypes

// Whether we can make a one-time goal for aggregate steps.
val stepGoals = runningCapabilities.supportedGoals[DataType.STEPS_TOTAL]
supportsStepGoals = 
    (stepGoals != null && ComparisonType.GREATER_THAN_OR_EQUAL in stepGoals)

// Whether auto-pause is supported.
val supportsAutoPause = runningCapabilities.supportsAutoPauseAndResume

تسجيل تحديثات حالة التمرين

يتم إرسال إشعارات بشأن التمارين الرياضية إلى مستمع. يمكن لتطبيقك تسجيل مستمع واحد فقط في كل مرة. اضبط أداة الاستماع قبل بدء التمرين، كما هو موضّح في المثال التالي. لا يتلقّى المستمع سوى إشعارات بشأن التمارين التي يملكها تطبيقك.

val callback = object : ExerciseUpdateCallback {
    override fun onExerciseUpdateReceived(update: ExerciseUpdate) {
        val exerciseStateInfo = update.exerciseStateInfo
        val activeDuration = update.activeDurationCheckpoint
        val latestMetrics = update.latestMetrics
        val latestGoals = update.latestAchievedGoals
    }

    override fun onLapSummaryReceived(lapSummary: ExerciseLapSummary) {
        // For ExerciseTypes that support laps, this is called when a lap is marked.
    }

    override fun onAvailabilityChanged(
        dataType: DataType<*, *>,
        availability: Availability
    ) {
        // Called when the availability of a particular DataType changes.
        when {
            availability is LocationAvailability -> // Relates to Location/GPS.
            availability is DataTypeAvailability -> // Relates to another DataType.
        }
    }
}
exerciseClient.setUpdateCallback(callback)

إدارة مدة التمرين

تتيح "خدمات الصحة" تسجيل تمرين واحد كحد أقصى في المرّة الواحدة على جميع التطبيقات على الجهاز. إذا كان يتم تتبُّع تمرين وبدأ تطبيق آخر في تتبُّع تمرين جديد، سيتم إنهاء التمرين الأول.

قبل بدء التمرين، اتّبِع الخطوات التالية:

  • التحقّق مما إذا كان يتم تتبُّع تمرين معيّن حاليًا، والتفاعل وفقًا لذلك على سبيل المثال، اطلب من المستخدم التأكيد قبل تجاهل تمرين سابق وبدء تتبُّع تمرين جديد.

يوضّح المثال التالي كيفية التحقّق من وجود تمرين باستخدام getCurrentExerciseInfoAsync:

lifecycleScope.launch {
    val exerciseInfo = exerciseClient.getCurrentExerciseInfoAsync().await()
    when (exerciseInfo.exerciseTrackedStatus) {
        OTHER_APP_IN_PROGRESS -> // Warn user before continuing, will stop the existing workout.
        OWNED_EXERCISE_IN_PROGRESS -> // This app has an existing workout.
        NO_EXERCISE_IN_PROGRESS -> // Start a fresh workout.
    }
}

الأذونات

عند استخدام ExerciseClient، تأكَّد من أنّ تطبيقك يطلب الأذونات اللازمة ويحتفظ بها. إذا كان تطبيقك يستخدم بيانات LOCATION، تأكَّد من أنّ تطبيقك يطلب الأذونات المناسبة لذلك ويحتفظ بها أيضًا.

بالنسبة إلى جميع أنواع البيانات، قبل طلب prepareExercise() أو startExercise()، اتّبِع الخطوات التالية:

  • حدِّد الأذونات المناسبة لأنواع البيانات المطلوبة في ملف AndroidManifest.xml.
  • تأكَّد من أنّ المستخدم قد منح الأذونات اللازمة. لمزيد من المعلومات، يُرجى الاطّلاع على طلب أذونات التطبيق. ترفض &quot;خدمات الصحة&quot; الطلب إذا لم يتم منح الأذونات اللازمة مسبقًا.

بالنسبة إلى بيانات الموقع الجغرافي، اتّبِع الخطوات الإضافية التالية:

الاستعداد للتمرين

قد يستغرق بعض أجهزة الاستشعار، مثل نظام تحديد المواقع العالمي (GPS) أو معدّل نبضات القلب، وقتًا قصيرًا للإحماء، أو قد يريد المستخدم الاطّلاع على بياناته قبل بدء التمرين. تتيح طريقة prepareExerciseAsync() الاختيارية لأدوات الاستشعار هذه أن تستعد وتتلقّى البيانات بدون بدء المؤقت الخاص بالتمرين. لا تتأثر activeDuration بمدة التحضير هذه.

قبل الاتصال بـ prepareExerciseAsync()، يُرجى التحقّق ممّا يلي:

  • تحقَّق من إعدادات الموقع الجغرافي على مستوى المنصة. يتحكّم المستخدم في هذا الإعداد من خلال قائمة &quot;الإعدادات&quot; الرئيسية، وهو يختلف عن عملية التحقّق من الأذونات على مستوى التطبيق.

    إذا كان الإعداد غير مفعّل، أرسِل إشعارًا إلى المستخدم بأنّه رفض منح الإذن بالوصول إلى الموقع الجغرافي، واطلب منه تفعيله إذا كان تطبيقك يتطلّب ذلك.

  • تأكَّد من أنّ تطبيقك لديه أذونات وقت التشغيل لأجهزة استشعار الجسم (المستوى 35 أو أقل لواجهة برمجة التطبيقات) أو معدّل ضربات القلب (المستوى 36 أو أعلى لواجهة برمجة التطبيقات) والتعرّف على النشاط والموقع الجغرافي الدقيق. بالنسبة إلى الأذونات غير المتوفّرة، اطلب من المستخدم منح أذونات التشغيل، مع توفير السياق المناسب. إذا لم يمنح المستخدم إذنًا معيّنًا، عليك إزالة أنواع البيانات المرتبطة بهذا الإذن من طلب prepareExerciseAsync(). إذا لم يتم منح إذن استخدام مستشعر الجسم (معدّل نبضات القلب على مستوى واجهة برمجة التطبيقات 36 أو أعلى) أو إذن الوصول إلى الموقع الجغرافي، لا تستخدِم الدالة prepareExerciseAsync()، لأنّ الدالة prepare مخصّصة تحديدًا للحصول على معدّل نبضات قلب ثابت أو تحديد الموقع الجغرافي باستخدام نظام تحديد المواقع العالمي (GPS) قبل بدء تمرين. سيظل بإمكان التطبيق الحصول على المسافة المستندة إلى الخطوات والوتيرة والسرعة والمقاييس الأخرى التي لا تتطلّب هذه الأذونات.

اتّبِع الخطوات التالية للتأكّد من أنّ طلبك إلى prepareExerciseAsync() يمكن أن ينجح:

  • استخدِم AmbientLifecycleObserver للنشاط الذي يسبق التمرين ويتضمّن طلب الإعداد.
  • اتّصِل بـ prepareExerciseAsync() من خدمتك التي تعمل في المقدّمة. إذا لم يكن في خدمة وكان مرتبطًا بدورة حياة النشاط، قد يتم إيقاف عملية إعداد المستشعر بدون داعٍ.
  • اتّصِل بالرقم endExercise() لإيقاف أجهزة الاستشعار وتقليل استهلاك الطاقة إذا انتقل المستخدم إلى نشاط آخر غير نشاط ما قبل التمرين.

يوضّح المثال التالي كيفية الاتصال بـ prepareExerciseAsync():

val warmUpConfig = WarmUpConfig(
    ExerciseType.RUNNING,
    setOf(
        DataType.HEART_RATE_BPM,
        DataType.LOCATION
    )
)
// Only necessary to call prepareExerciseAsync if body sensor (API level 35
// or lower), heart rate (API level 36+), or location permissions are given.
exerciseClient.prepareExerciseAsync(warmUpConfig).await()

// Data and availability updates are delivered to the registered listener.

بعد أن يصبح التطبيق في حالة PREPARING، يتم إرسال إشعارات بشأن توفّر أجهزة الاستشعار في ExerciseUpdateCallback من خلال onAvailabilityChanged(). ويمكن بعد ذلك عرض هذه المعلومات للمستخدم ليقرّر ما إذا كان يريد بدء التمرين.

بدء التمرين

عندما تريد بدء تمرين، أنشئ ExerciseConfig لضبط نوع التمرين وأنواع البيانات التي تريد تلقّي مقاييس لها وأي أهداف أو مراحل للتمرين.

تتألف أهداف التمارين الرياضية من DataType وشرط. أهداف التمارين هي أهداف لمرة واحدة يتم تشغيلها عند استيفاء شرط معيّن، مثل عندما يركض المستخدم لمسافة معيّنة. يمكنك أيضًا تحديد إنجاز متعلّق بالتمارين الرياضية. يمكن تشغيل إشعارات الإنجازات الرياضية عدة مرات، مثلاً، في كل مرة يركض فيها المستخدم مسافة معيّنة تتجاوز المسافة المحدّدة.

يوضّح المثال التالي كيفية إنشاء هدف واحد من كل نوع:

const val CALORIES_THRESHOLD = 250.0
const val DISTANCE_THRESHOLD = 1_000.0 // meters

suspend fun startExercise() {
    // Types for which we want to receive metrics.
    val dataTypes = setOf(
        DataType.HEART_RATE_BPM,
        DataType.CALORIES_TOTAL,
        DataType.DISTANCE
    )

    // Create a one-time goal.
    val calorieGoal = ExerciseGoal.createOneTimeGoal(
        DataTypeCondition(
            dataType = DataType.CALORIES_TOTAL,
            threshold = CALORIES_THRESHOLD,
            comparisonType = ComparisonType.GREATER_THAN_OR_EQUAL
        )
    )

    // Create a milestone goal. To make a milestone for every kilometer, set the initial
    // threshold to 1km and the period to 1km.
    val distanceGoal = ExerciseGoal.createMilestone(
        condition = DataTypeCondition(
            dataType = DataType.DISTANCE_TOTAL,
            threshold = DISTANCE_THRESHOLD,
            comparisonType = ComparisonType.GREATER_THAN_OR_EQUAL
        ),
        period = DISTANCE_THRESHOLD
    )

    val config = ExerciseConfig(
        exerciseType = ExerciseType.RUNNING,
        dataTypes = dataTypes,
        isAutoPauseAndResumeEnabled = false,
        isGpsEnabled = true,
        exerciseGoals = mutableListOf<ExerciseGoal<Double>>(calorieGoal, distanceGoal)
    )
    exerciseClient.startExerciseAsync(config).await()
}

يمكنك أيضًا تسجيل عدد اللفات لجميع التمارين. توفّر "خدمات الصحة" ExerciseLapSummary مع مقاييس مجمّعة على مدار فترة اللفة.

يعرض المثال السابق استخدام isGpsEnabled، ويجب أن تكون القيمة صحيحة عند طلب بيانات الموقع الجغرافي. ومع ذلك، يمكن أن يساعد استخدام نظام تحديد المواقع العالمي (GPS) أيضًا في قياس مقاييس أخرى. إذا كانت ExerciseConfig تحدّد المسافة على أنّها DataType، يتم تلقائيًا استخدام الخطوات لتقدير المسافة. من خلال تفعيل نظام تحديد المواقع العالمي (GPS) بشكل اختياري، يمكن استخدام معلومات الموقع الجغرافي بدلاً من ذلك لتقدير المسافة.

إيقاف تمرين مؤقتًا واستئنافه وإنهاؤه

يمكنك إيقاف التمارين مؤقتًا واستئنافها وإنهاءها باستخدام الطريقة المناسبة، مثل pauseExerciseAsync() أو endExerciseAsync().

استخدِم الحالة من ExerciseUpdate كمصدر موثوق. لا يتم اعتبار التمرين متوقفًا مؤقتًا عند عرض نتيجة طلب pauseExerciseAsync()، بل عند ظهور هذه الحالة في الرسالة ExerciseUpdate. ويجب مراعاة ذلك بشكل خاص عند التعامل مع حالات واجهة المستخدم. إذا ضغط المستخدم على زر الإيقاف المؤقت، عطِّل زر الإيقاف المؤقت واتّصِل بالدالة pauseExerciseAsync() في خدمات الصحة. انتظِر إلى أن تصل &quot;خدمات الصحة&quot; إلى حالة الإيقاف المؤقت باستخدام ExerciseUpdate.exerciseStateInfo.state، ثم بدِّل الزر إلى &quot;استئناف&quot;. ويرجع ذلك إلى أنّ تحديثات حالة &quot;خدمات الصحة&quot; قد تستغرق وقتًا أطول من الضغط على الزر، لذا إذا ربطت جميع تغييرات واجهة المستخدم بعمليات الضغط على الزر، قد لا تتزامن واجهة المستخدم مع حالة &quot;خدمات الصحة&quot;.

يُرجى مراعاة ذلك في الحالات التالية:

  • ميزة "الإيقاف المؤقت التلقائي" مفعّلة: يمكن إيقاف التمرين مؤقتًا أو بدؤه بدون أي تدخل من المستخدم.
  • بدء تطبيق آخر للتمرين: قد يتم إنهاء تمرينك بدون تفاعل من المستخدم.

إذا تم إنهاء تمرين تطبيقك من خلال تطبيق آخر، يجب أن يتعامل تطبيقك مع عملية الإنهاء بشكل سليم:

  • حفظ حالة التمرين الجزئي حتى لا يتم محو تقدّم المستخدم
  • إزالة رمز "النشاط قيد التقدّم" وإرسال إشعار إلى المستخدم لإبلاغه بأنّ تطبيقًا آخر أوقف التمرين

عليك أيضًا التعامل مع الحالة التي يتم فيها إبطال الأذونات أثناء تمرين جارٍ. يتم إرسال هذا الرمز باستخدام الحالة isEnded، مع ExerciseEndReason بقيمة AUTO_END_PERMISSION_LOST. تعامَل مع هذه الحالة بطريقة مشابهة لحالة الإنهاء: احفظ الحالة الجزئية، وأزِل رمز "النشاط الجاري"، وأرسِل إشعارًا إلى المستخدم بشأن ما حدث.

يوضّح المثال التالي كيفية التحقّق من الإنهاء بشكل صحيح:

val callback = object : ExerciseUpdateCallback {
    override fun onExerciseUpdateReceived(update: ExerciseUpdate) {
        if (update.exerciseStateInfo.state.isEnded) {
            // Workout has either been ended by the user, or otherwise terminated
        }
        ...
    }
    ...
}

إدارة مدة النشاط

أثناء ممارسة التمارين الرياضية، يمكن للتطبيق عرض المدة النشطة للتمرين. يجب أن تتم مزامنة التطبيق و&quot;خدمات الصحة&quot; ووحدة التحكّم الدقيق (MCU) في الجهاز، وهي معالج منخفض الطاقة مسؤول عن تتبُّع التمارين، مع مدة النشاط الحالية نفسها. للمساعدة في إدارة ذلك، ترسل &quot;خدمات الصحة&quot; ActiveDurationCheckpoint يوفّر نقطة أساس يمكن للتطبيق بدء المؤقت منها.

بما أنّ مدة النشاط يتم إرسالها من وحدة التحكّم في الوسائط ويمكن أن تستغرق بعض الوقت للوصول إلى التطبيق، يحتوي ActiveDurationCheckpoint على سمتَين:

  • activeDuration: المدة التي كان فيها التمرين نشطًا
  • time: وقت احتساب مدة النشاط

وبالتالي، يمكن حساب المدة النشطة للتمرين في التطبيق من خلال ActiveDurationCheckpoint باستخدام المعادلة التالية:

(now() - checkpoint.time) + checkpoint.activeDuration

ويفسّر ذلك الفرق البسيط بين مدة النشاط التي يتم احتسابها على وحدة التحكّم الدقيقة (MCU) والوقت الذي تصل فيه إلى التطبيق. ويمكن استخدام ذلك لضبط عدّاد الوقت في التطبيق والمساعدة في ضمان توافق مؤقّت التطبيق تمامًا مع الوقت في "خدمات الصحة" ووحدة التحكّم الدقيقة (MCU).

إذا تم إيقاف التمرين مؤقتًا، ينتظر التطبيق إعادة تشغيل المؤقت في واجهة المستخدم إلى أن يتجاوز الوقت المحسوب الوقت المعروض في واجهة المستخدم. ويرجع ذلك إلى أنّ إشارة الإيقاف المؤقت تصل إلى &quot;خدمات الصحة&quot; ووحدة التحكّم الدقيق مع تأخير طفيف. على سبيل المثال، إذا تم إيقاف التطبيق مؤقتًا عند t=10 ثوانٍ، قد لا تقدّم "خدمات Health" تحديث PAUSED إلى التطبيق إلا عند t=10.2 ثانية.

العمل باستخدام بيانات من ExerciseClient

يتم تسليم مقاييس أنواع البيانات التي سجّلها تطبيقك في رسائل ExerciseUpdate.

لا يرسل المعالج الرسائل إلا عندما يكون الجهاز نشطًا أو عند بلوغ الحد الأقصى لمدة الإبلاغ، مثلاً كل 150 ثانية. لا تعتمد على معدّل التكرار ExerciseUpdate لتقديم عدّاد الوقت باستخدام activeDuration. يمكنك الاطّلاع على نموذج التمرين على GitHub للحصول على مثال حول كيفية تنفيذ عدّاد وقت مستقل.

عندما يبدأ المستخدم تمرينًا، يمكن عرض رسائل ExerciseUpdate بشكل متكرر، مثلاً كل ثانية. عندما يبدأ المستخدم التمرين، قد تنطفئ الشاشة. ويمكن لخدمات الصحة بعد ذلك تقديم البيانات بمعدّل أقل، ولكن مع استمرار أخذ العيّنات بالوتيرة نفسها، وذلك لتجنُّب تنشيط المعالج الرئيسي. عندما ينظر المستخدم إلى الشاشة، يتم على الفور إرسال أي بيانات في طور تجميعها إلى تطبيقك.

التحكّم في معدّل تجميع البيانات

في بعض السيناريوهات، قد تحتاج إلى التحكّم في معدّل تلقّي تطبيقك لأنواع معيّنة من البيانات عندما تكون الشاشة مطفأة. يتيح لك العنصر BatchingMode إلغاء السلوك التلقائي لتجميع البيانات في دفعات من أجل تلقّي عمليات تسليم البيانات بشكل متكرّر أكثر.

لضبط معدّل التجميع، أكمِل الخطوات التالية:

  1. تحقَّق مما إذا كان الجهاز يتيح استخدام تعريف BatchingMode معيّن:

    // Confirm BatchingMode support to control heart rate stream to phone.
    suspend fun supportsHrWorkoutCompanionMode(): Boolean {
        val capabilities = exerciseClient.getCapabilities()
        return BatchingMode.HEART_RATE_5_SECONDS in
                capabilities.supportedBatchingModeOverrides
    }
    
  2. حدِّد أنّ العنصر ExerciseConfig يجب أن يستخدم BatchingMode معيّنًا، كما هو موضّح في مقتطف الرمز التالي.

    val config = ExerciseConfig(
        exerciseType = ExerciseType.WORKOUT,
        dataTypes = setOf(
            DataType.HEART_RATE_BPM,
            DataType.TOTAL_CALORIES
        ),
        // ...
        batchingModeOverrides = setOf(BatchingMode.HEART_RATE_5_SECONDS)
    )
    
  3. يمكنك اختياريًا ضبط BatchingMode بشكل ديناميكي أثناء التمرين، بدلاً من استمرار سلوك التجميع المحدّد طوال مدة التمرين:

    val desiredModes = setOf(BatchingMode.HEART_RATE_5_SECONDS)
    exerciseClient.overrideBatchingModesForActiveExercise(desiredModes)
    
  4. لمحو BatchingMode المخصّص والعودة إلى السلوك التلقائي، مرِّر مجموعة فارغة إلى exerciseClient.overrideBatchingModesForActiveExercise().

الطوابع الزمنية

تمثّل النقطة الزمنية لكل نقطة بيانات المدة منذ بدء تشغيل الجهاز. لتحويل هذا إلى طابع زمني، اتّبِع الخطوات التالية:

val bootInstant =
    Instant.ofEpochMilli(System.currentTimeMillis() - SystemClock.elapsedRealtime())

يمكن بعد ذلك استخدام هذه القيمة مع getStartInstant() أو getEndInstant() لكل نقطة بيانات.

دقة البيانات

يمكن أن تتضمّن بعض أنواع البيانات معلومات دقة مرتبطة بكل نقطة بيانات. يتم تمثيل ذلك في السمة accuracy.

يمكن ملء الفئتَين HrAccuracy وLocationAccuracy لنوعَي البيانات HEART_RATE_BPM وLOCATION على التوالي. في حال توفُّرها، استخدِم السمة accuracy لتحديد ما إذا كانت كل نقطة بيانات تتمتّع بدقة كافية لتطبيقك.

تخزين البيانات وتحميلها

استخدِم Room للاحتفاظ بالبيانات التي يتم تقديمها من خلال "خدمات الصحة". يتم تحميل البيانات في نهاية التمرين باستخدام آلية مثل Work Manager. يساعد ذلك في التأكّد من تأجيل طلبات الشبكة لتحميل البيانات إلى حين انتهاء التمرين، ما يقلّل من استهلاك الطاقة أثناء التمرين ويبسّط العمل.

قائمة التحقق من الدمج

قبل نشر تطبيقك الذي يستخدم ExerciseClient في "خدمات الصحة"، راجِع قائمة التحقّق التالية للتأكّد من أنّ تجربة المستخدم تتجنّب بعض المشاكل الشائعة. تأكَّد مما يلي:

  • يتحقّق تطبيقك من إمكانات نوع التمرين وإمكانات الجهاز في كل مرة يتم فيها تشغيل التطبيق. بهذه الطريقة، يمكنك رصد الحالات التي لا يتوافق فيها جهاز أو تمرين معيّن مع أحد أنواع البيانات التي يحتاجها تطبيقك.
  • يجب طلب الأذونات اللازمة والاحتفاظ بها وتحديدها في ملف البيان. قبل الاتصال بـ prepareExerciseAsync()، يتأكّد تطبيقك من منح أذونات التشغيل.
  • يستخدم تطبيقك getCurrentExerciseInfoAsync() للتعامل مع الحالات التالية:
    • يتم حاليًا تتبُّع تمرين، ويحلّ تطبيقك محل التمرين السابق.
    • أنهى تطبيق آخر تمرينك. قد يحدث ذلك عندما يعيد المستخدم فتح التطبيق، حيث تظهر له رسالة توضّح أنّ التمرين قد توقّف لأنّ تطبيقًا آخر قد حلّ محلّه.
  • إذا كنت تستخدم بيانات LOCATION:
    • يحافظ تطبيقك على ForegroundService مع foregroundServiceType المقابل طوال مدة التمرين (بما في ذلك مكالمة التحضير).
    • تحقَّق من تفعيل نظام تحديد المواقع العالمي (GPS) على الجهاز باستخدام isProviderEnabled(LocationManager.GPS_PROVIDER)، واطلب من المستخدم فتح إعدادات الموقع الجغرافي إذا لزم الأمر.
    • بالنسبة إلى حالات الاستخدام التي تتطلّب الحصول على بيانات الموقع الجغرافي بزمن انتقال منخفض، ننصحك بدمج مزوّد الموقع الجغرافي المدمج (FLP) واستخدام بياناته كإصلاح أولي للموقع الجغرافي. عند توفّر معلومات موقع جغرافي أكثر استقرارًا من &quot;خدمات الصحة&quot;، استخدِمها بدلاً من FLP.
  • إذا كان تطبيقك يتطلّب تحميل البيانات، سيتم تأجيل أي طلبات شبكة لتحميل البيانات إلى حين انتهاء التمرين. وبخلاف ذلك، سيجري تطبيقك أي مكالمات ضرورية على الشبكة بشكل مقتصد طوال فترة التمرين.