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

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

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

إضافة التبعيات

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

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

رائع

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

Kotlin

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

بنية التطبيق

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

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

من خلال استخدام ForegroundService، يمكنك استخدام واجهة برمجة تطبيقات الأنشطة المستمرة لعرض مؤشر على مساحات عرض ساعتك، ما يتيح للمستخدم العودة سريعًا إلى التمرين.

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

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

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

يتيح كل 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.
  • تأكَّد من أنّ المستخدم قد منح الأذونات اللازمة. لمزيد من المعلومات، راجِع مقالة طلب أذونات التطبيق. ترفض "الخدمات الصحية" الطلب إذا لم يتم منح الأذونات اللازمة.

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

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

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

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

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

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

  • تأكَّد من أنّ تطبيقك لديه أذونات التشغيل الخاصة بأجهزة استشعار الجسم والتعرّف على النشاط وتحديد الموقع الجغرافي بدقة. لفقدان الأذونات، اطلب من المستخدم الحصول على أذونات التشغيل مع تقديم سياق ملائم. إذا لم يمنح المستخدم إذنًا محددًا، يمكنك إزالة أنواع البيانات المرتبطة بهذا الإذن من الطلب prepareExerciseAsync(). إذا لم يتم منح أي من أجهزة استشعار الجسم أو أذونات تحديد الموقع الجغرافي، لا تتّصل بـ prepareExerciseAsync() لأنّ الطلب مخصّص تحديدًا للحصول على معدّل مستقر لنظام تحديد المواقع العالمي (GPS) أو الحصول على معدّل مستقر لنظام تحديد المواقع العالمي (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 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()" على "الخدمات الصحية". انتظر حتى تصل "الخدمات الصحية" إلى حالة الإيقاف المؤقت باستخدام ExerciseUpdate.exerciseStateInfo.state، ثم بدِّل الزر لاستئناف التشغيل. وذلك لأنّ تحديثات حالة "الخدمات الصحية" قد تستغرق وقتًا أطول من تلك التي يتم الضغط عليها عند الضغط على الأزرار، لذلك إذا ربطت جميع تغييرات واجهة المستخدم بضغطات الأزرار، يمكن أن لا تتم مزامنة واجهة المستخدم مع حالة "الخدمات الصحية".

ضع ذلك في الاعتبار في الحالات التالية:

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

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

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

أيضًا، تعامل مع الحالة التي يتم فيها إبطال الأذونات أثناء تمرين مستمر. يتمّ إرسال هذه الرسالة باستخدام حالة 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
        }
        ...
    }
    ...
}

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

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

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

  • activeDuration: مدة النشاط الرياضي
  • time: عندما تم احتساب مدة النشاط

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

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

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

إذا تم إيقاف التمرين مؤقتًا، ينتظر التطبيق إعادة تشغيل المؤقت في واجهة المستخدم حتى يتخطى الوقت المحسوب ما تعرضه واجهة المستخدم حاليًا. يرجع ذلك إلى حدوث تأخير بسيط في وصول إشارة الإيقاف المؤقت إلى "الخدمات الصحية" ووحدة MCU. على سبيل المثال، إذا تم إيقاف التطبيق مؤقتًا عند t=10 ثوانٍ، قد لا ترسل "الخدمات الصحية" تحديث 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 للاحتفاظ بالبيانات التي يتم تسليمها من خلال "الخدمات الصحية". يتم تحميل البيانات في نهاية التمرين باستخدام آلية مثل مدير العمل. يضمن ذلك تأجيل مكالمات الشبكة لتحميل البيانات حتى انتهاء التمرين، وتقليل استهلاك الطاقة أثناء التمرين وتبسيط العمل.

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

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

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