پایش داده‌ها در پس‌زمینه

به‌روزرسانی‌های داده‌های غیرفعال برای برنامه‌هایی مناسب است که نیاز به نظارت بر داده‌های «خدمات سلامت» در پس‌زمینه دارند. این مدل‌ها برای موارد استفاده‌ای درنظر گرفته شده‌اند که ساعت‌ها، روزها، یا حتی بیشتر طول می‌کشند. اگر لازم است داده‌های سلامتی و تناسب اندام را زمانی‌که برنامه‌تان درحال اجرا نیست و کاربر صریحاً درحال تمرین نیست ذخیره یا پردازش کنید، از کلاینت غیرفعال «خدمات سلامت» استفاده کنید.

برای مشاهده نمونه‌های استفاده از داده‌های منفعل، به نمونه‌های داده‌های منفعل و اهداف منفعل در 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")
}

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

قبل‌از ثبت‌نام برای به‌روزرسانی‌های داده، بررسی کنید که دستگاه بتواند نوع داده موردنیاز برنامه‌تان را ارائه دهد. بررسی قابلیت‌ها به شما امکان می‌دهد برخی‌از ویژگی‌ها را فعال یا غیرفعال کنید یا میانای کاربر برنامه‌تان را برای جبران ویژگی‌هایی که دردسترس نیستند اصلاح کنید.

val healthClient = HealthServices.getClient(this /*context*/)
val passiveMonitoringClient = healthClient.passiveMonitoringClient
lifecycleScope.launchWhenCreated {
    val capabilities = passiveMonitoringClient.capabilities.await()
    // Supported types for passive data collection
    supportsHeartRate =
        DataType.HEART_RATE_BPM in capabilities.supportedDataTypesPassiveMonitoring
    // Supported types for PassiveGoals
    supportsStepsGoal =
        DataType.STEPS_DAILY in capabilities.supportedDataTypesPassiveGoals
}

ثبت‌نام برای داده‌های منفعل

می‌توانید داده‌های غیرفعال را ازطریق سرویس، فراخوانی، یا هر دو دریافت کنید. سرویس به برنامه شما اجازه می‌دهد وقتی هیچ بخشی از برنامه‌تان در پیش‌زمینه قابل‌مشاهده نیست، داده‌ها را در پس‌زمینه دریافت کند. وقتی داده‌ها را در پس‌زمینه دریافت می‌کنید، به‌صورت دسته‌ای تحویل داده می‌شود. فراخوانی برگشتی داده‌ها را با سرعت کمی بیشتر دریافت می‌کند، اما فقط زمانی‌که برنامه درحال اجرا باشد و فراخوانی برگشتی با موفقیت مطلع شود.

از هر روشی که استفاده می‌کنید، ابتدا یک PassiveListenerConfig ایجاد کنید که تعیین می‌کند کدام نوع داده‌ها دریافت شوند، همان‌طور که در مثال زیر نشان داده شده است:

val passiveListenerConfig = PassiveListenerConfig.builder()
    .setDataTypes(setOf(DataType.HEART_RATE_BPM))
    .build()

برای دریافت داده بااستفاده از فراخوانی، فراخوانی را تعریف و ثبت کنید، همان‌طور که در مثال زیر نشان داده شده است:

val passiveListenerCallback: PassiveListenerCallback = object : PassiveListenerCallback {
    override fun onNewDataPointsReceived(dataPoints: DataPointContainer) {
        // TODO: Do something with dataPoints
    }
}

passiveMonitoringClient.setPassiveListenerCallback(
    passiveListenerConfig,
    passiveListenerCallback
)

// To remove the listener
passiveMonitoringClient.clearPassiveListenerCallbackAsync()

استفاده از سرویس مشابه است، اما به‌جای ایجاد کلاسی مشتق‌شده از PassiveListenerCallback، از PassiveListenerService مشتق کنید، همان‌طور که در مثال زیر نشان داده شده است:

class PassiveDataService : PassiveListenerService() {
    override fun onNewDataPointsReceived(dataPoints: DataPointContainer) {
        // TODO: Do something with dataPoints
    }
}

passiveMonitoringClient.setPassiveListenerServiceAsync(
    PassiveDataService::class.java,
    passiveListenerConfig
)

سپس، سرویس را در فایل AndroidManifest.xml خود اعلام کنید. به اجازه Health Services نیاز دارد که تأیید می‌کند فقط Health Services می‌تواند به سرویس متصل شود:

<service android:name=".PassiveDataService"
    android:permission="com.google.android.wearable.healthservices.permission.PASSIVE_DATA_BINDING"
    android:exported="true" />

تفسیر زمان

داده‌هایی که از Health Services دریافت می‌کنید دسته‌بندی شده‌اند، بنابراین ممکن است نقاط داده از انواع مختلف یا چند نقطه داده از یک نوع را در یک دسته دریافت کنید. برای تعیین ترتیب صحیح رویدادها، از مُهرهای زمان موجود در این اشیاء استفاده کنید، نه زمانی که برنامه‌تان آن‌ها را دریافت کرده است.

مُهرهای زمان را برای هر DataPoint با محاسبه مُهر زمان راه‌اندازی به‌دست آورید، همان‌طور که در مثال زیر نشان داده شده است:

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

سپس این مقدار می‌تواند به getStartInstant() یا getEndInstant() منتقل شود.

بازیابی ثبت‌ها پس‌از راه‌اندازی

ثبت داده‌های منفعل در راه‌اندازی‌های مجدد حفظ نمی‌شود. برای دریافت داده‌ها پس‌از بازراه‌اندازی دستگاه، ثبت‌نام‌هایتان را بااستفاده از BroadcastReceiver که به همه‌فرستی سیستم ACTION_BOOT_COMPLETED گوش می‌دهد دوباره ایجاد کنید.

در گیرنده، سعی نکنید ثبت‌نام‌ها را مستقیماً بازیابی کنید. درعوض، این عملکرد را به یک کارگر WorkManager واگذار کنید. وقتی دستگاه درحال راه‌اندازی است، ممکن است ۱۰ ثانیه یا بیشتر طول بکشد تا «خدمات سلامتی» درخواست ثبت داده‌های غیرفعال را تأیید کند و این ممکن است از زمان اجرای مجاز BroadcastReceiver فراتر رود. درمقابل، کارگران WorkManager محدودیت اجرای ۱۰ دقیقه‌ای دارند.

قطعه کد زیر نشان می‌دهد که BroadcastReceiver ممکن است چگونه باشد:

class StartupReceiver : BroadcastReceiver() {

   override fun onReceive(context: Context, intent: Intent) {
       if (intent.action != Intent.ACTION_BOOT_COMPLETED) return


       // TODO: Check permissions first
       WorkManager.getInstance(context).enqueue(
           OneTimeWorkRequestBuilder<RegisterForPassiveDataWorker>().build()
       )
   }
}

class RegisterForPassiveDataWorker(
   private val appContext: Context,
   workerParams: WorkerParameters
) : Worker(appContext, workerParams) {

   override fun doWork(): Result {
       runBlocking {
           HealthServices.getClient(appContext)
                .passiveMonitoringClient
                .setPassiveListenerCallback(...)
       }
       return Result.success()
   }
}

برای اینکه سیستم را طوری تنظیم کنید که این کد را هنگام راه‌اندازی دستگاه اجرا کند، دو تغییر در فایل AndroidManifest.xml ایجاد کنید.

ابتدا، اجازه زیر را به‌عنوان فرزند <manifest> اضافه کنید:

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

دوم، فیلتر قصد گیرنده زیر را به‌عنوان فرزند <application> اضافه کنید:

<receiver
    android:name=".StartupReceiver"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

حالت فعالیت

کلاینت غیرفعال همچنین می‌تواند اطلاعات سطح بالایی درباره وضعیت کاربر ارائه دهد، مثلاً اینکه آیا کاربر خواب است یا نه. برای دریافت این به‌روزرسانی‌ها، این مراحل را دنبال کنید:

  1. اجازه ACTIVITY_RECOGNITION را درخواست کنید.
  2. setShouldUserActivityInfoBeRequested(true) را در سازنده PassiveListenerConfig فراخوانی کنید.

روش onUserActivityInfoReceived() را در فراخوانی یا سرویس خود لغو کنید و از UserActivityInfo برگردانده‌شده استفاده کنید، همان‌طور که در مثال زیر نشان داده شده است:

override fun onUserActivityInfoReceived(info: UserActivityInfo) {
    val stateChangeTime: Instant = info.stateChangeTime // may be in the past!
    val userActivityState: UserActivityState = info.userActivityState
    if (userActivityState == UserActivityState.USER_ACTIVITY_ASLEEP) {
        // ...
    }
}

هدف‌های تابع

می‌توانید یک کلاینت غیرفعال را پیکربندی کنید تا وقتی اهداف غیرفعال محقق می‌شوند، مثلاً وقتی کاربر ۱۰٬۰۰۰ قدم در روز برمی‌دارد، به برنامه اطلاع دهد.

برای انجام این کار، هدفی ایجاد کنید، همان‌طور که در مثال زیر نشان داده شده است:

val dailyStepsGoal by lazy {
    val condition = DataTypeCondition(
        dataType = DataType.STEPS_DAILY,
        threshold = 10_000, // Trigger every 10000 steps
        comparisonType = ComparisonType.GREATER_THAN_OR_EQUAL
    )
    PassiveGoal(condition)
}

این هدف را به PassiveListenerConfig خود اضافه کنید، همان‌طور که در مثال زیر نشان داده شده است:

val passiveListenerConfig = PassiveListenerConfig.builder()
    .setDailyGoals(setOf(dailyStepsGoal))
    .build()

روش onGoalCompleted() را در فراخوانی یا سرویس خود لغو کنید و از PassiveGoal برگردانده‌شده استفاده کنید، همان‌طور که در مثال زیر نشان داده شده است:

override fun onGoalCompleted(goal: PassiveGoal) {
    when (goal.dataTypeCondition.dataType) {
        DataType.STEPS_DAILY -> {
            // ...
        }
    }
}