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

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

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

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

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

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

رائع

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

Kotlin

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

بنية التطبيق

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

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

من خلال استخدام ForegroundService، يمكنك استخدام Onbound Activity API لعرض مؤشر على أسطح ساعتك، ما يتيح للمستخدم العودة بسرعة إلى التمرين.

ومن الضروري أن تطلب بيانات الموقع الجغرافي بشكل مناسب في الخدمة التي تعمل في المقدّمة. في ملف البيان، حدِّد 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) قبل بدء التمرين. لا يزال بإمكان التطبيق الحصول على المسافة والسرعة والسرعة بناءً على الخطوات ومقاييس أخرى لا تتطلب هذه الأذونات.

افعل ما يلي لضمان نجاح مكالمتك مع 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
        }
        ...
    }
    ...
}

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

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

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

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

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

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

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

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

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

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

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

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

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