ثبت تمرین با ExerciseClient

«خدمات بهداشتی» ازطریق ExerciseClient از برنامه‌های تمرین پشتیبانی می‌کند. با ExerciseClient، برنامه‌تان می‌تواند زمان درحال انجام بودن تمرین را کنترل کند، اهداف تمرین اضافه کند، و به‌روزرسانی‌هایی درباره وضعیت تمرین، رویدادهای تمرین، یا سنجه‌های دیگر دریافت کند. برای اطلاعات بیشتر، فهرست کامل انواع تمرین را که «خدمات سلامت» پشتیبانی می‌کند ببینید.

نمونه تمرین را در GitHub ببینید.

افزودن وابستگی‌ها

برای افزودن وابستگی به «خدمات بهداشتی»، باید مخزن Google Maven را به پروژه‌تان اضافه کنید. برای اطلاعات بیشتر، به مخزن Maven در Google مراجعه کنید.

سپس، در فایل build.gradle سطح ماژول، وابستگی زیر را اضافه کنید:

شیار

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

کاتلین

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

ساختار برنامه

هنگام ساختن برنامه تمرین با «خدمات سلامت»، از ساختار برنامه زیر استفاده کنید:

  • صفحه‌ها و پیمایش را در فعالیت اصلی نگه دارید.
  • مدیریت کردن وضعیت تمرین، داده‌های حسگر، فعالیت جاری، و داده‌ها با سرویس پیش‌زمینه‌ای.
  • داده‌ها را با Room ذخیره کنید و از WorkManager برای بارگذاری داده‌ها استفاده کنید.

هنگام آماده شدن برای تمرین و درطول تمرین، ممکن است فعالیت شما به دلایل مختلفی متوقف شود. ممکن است کاربر به برنامه دیگری برود یا به صفحه ساعت برگردد. ممکن است سیستم چیزی را روی فعالیتتان نمایش دهد، یا ممکن است صفحه‌نمایش پس‌از مدتی عدم فعالیت خاموش شود. از 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() است و برای فعالیت تمرینی خود استفاده کنید. بااین‌حال، نمایشگر را درطول تمرین در حالت محیطی به‌روز نکنید: دلیلش این است که «خدمات سلامت» داده‌های تمرین را وقتی صفحه‌نمایش دستگاه در حالت محیطی است برای صرفه‌جویی در مصرف باتری دسته‌بندی می‌کند، بنابراین اطلاعات نمایش‌داده‌شده ممکن است جدید نباشد. درطول تمرینات، داده‌هایی را نشان دهید که برای کاربر منطقی باشد، یا اطلاعات به‌روز را نمایش دهید یا صفحه‌نمایش خالی.

بررسی قابلیت‌ها

هر 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 در دستگاه بااستفاده از isProviderEnabled(LocationManager.GPS_PROVIDER) فعال باشد. درصورت لزوم، از کاربر بخواهید تنظیمات مکان را باز کند.
  • مطمئن شوید که ForegroundService با foregroundServiceType مناسب درطول تمرین حفظ شود.

آماده شدن برای تمرین

برخی‌از حسگرها، مانند GPS یا ضربان قلب، ممکن است برای آماده شدن به زمان کوتاهی نیاز داشته باشند، یا ممکن است کاربر بخواهد داده‌های خود را قبل‌از شروع تمرین ببیند. روش اختیاری prepareExerciseAsync() به این حسگرها اجازه می‌دهد گرم شوند و داده‌ها بدون شروع زمان‌سنج تمرین دریافت شوند. activeDuration تحت‌تأثیر این زمان آماده‌سازی قرار نمی‌گیرد.

قبل‌از تماس با prepareExerciseAsync()، موارد زیر را بررسی کنید:

  • تنظیم مکان در سراسر پلاتفرم را بررسی کنید. کاربر این تنظیم را در منو اصلی «تنظیمات» کنترل می‌کند؛ این تنظیم با بررسی اجازه‌های سطح برنامه متفاوت است.

    اگر تنظیم خاموش است، به کاربر اطلاع دهید که دسترسی به مکان را رد کرده است و اگر برنامه‌تان به مکان نیاز دارد، از او بخواهید آن را فعال کند.

  • تأیید کنید که برنامه‌تان اجازه‌های زمان اجرا برای حسگرهای بدن (سطح API‏ ۳۵ یا پایین‌تر) یا ضربان قلب (سطح API‏ ۳۶+)، تشخیص فعالیت، و مکان دقیق را دارد. برای اجازه‌های مفقود، از کاربر درخواست اجازه‌های زمان اجرا کنید، و بافت کافی ارائه دهید. اگر کاربر اجازه خاصی را اعطا نکرد، انواع داده‌های مرتبط با آن اجازه را از فراخوانی prepareExerciseAsync() بردارید. اگر نه حسگر بدن (ضربان قلب در سطح API 36+) و نه اجازه‌های مکان داده نشده‌اند، 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 (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() را در «خدمات بهداشتی» فراخوانی کنید. بااستفاده از 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 می‌رسد. برای مثال، اگر برنامه در ثانیه ۱۰ متوقف شود، «خدمات بهداشتی» ممکن است به‌روزرسانی PAUSED را تا ثانیه ۱۰٫۲ به برنامه ارائه ندهد.

کار کردن با داده‌های ExerciseClient

سنجه‌های مربوط به انواع داده‌هایی که برنامه‌تان برای آن‌ها ثبت‌نام کرده است در پیام‌های ExerciseUpdate ارائه می‌شود.

پردازنده پیام‌ها را فقط زمانی ارسال می‌کند که بیدار باشد یا حداکثر دوره گزارش‌دهی فرا رسیده باشد، مثلاً هر ۱۵۰ ثانیه. برای جلو بردن زمان‌سنج با activeDuration، به بسامد ExerciseUpdate اتکا نکنید. برای دیدن نمونه‌ای از نحوه پیاده‌سازی زمان‌سنج مستقل، نمونه تمرین را در 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) و استفاده از داده‌های آن به‌عنوان مکان‌یابی اولیه را مدنظر قرار دهید. وقتی اطلاعات مکان پایدارتری از «خدمات سلامت» دردسترس است، به‌جای «ارائه‌دهنده تلفیقی مکان» از آن استفاده کنید.
  • اگر برنامه‌تان به بارگذاری داده نیاز دارد، هرگونه تماس شبکه‌ای برای بارگذاری داده تا پایان تمرین به‌تعویق می‌افتد. درغیراین‌صورت، درطول تمرین، برنامه‌تان هرگونه تماس ضروری شبکه را به‌ندرت برقرار می‌کند.