מעקב אחר נתונים ברקע

עדכוני נתונים פסיביים מתאימים לאפליקציות שצריכות לעקוב אחרי נתונים של שירותי בריאות ברקע. הם מיועדים לתרחישי שימוש שנמשכים שעות, ימים או אפילו יותר. אם אתם צריכים לאחסן או לעבד נתוני בריאות וכושר כשהאפליקציה לא פועלת והמשתמש לא מבצע פעילות גופנית באופן מפורש, אתם יכולים להשתמש בלקוח הפסיבי של Health Service.

דוגמאות לשימוש בנתונים פסיביים אפשר למצוא בדוגמאות Passive Data ו-Passive Goals ב-GitHub.

הוספת יחסי תלות

כדי להוסיף תלות ב-Health Services, צריך להוסיף את מאגר Google Maven לפרויקט. מידע נוסף זמין במאמר בנושא מאגר Maven של Google.

בקובץ build.gradle ברמת המודול, מוסיפים את התלות הבאה:

Groovy

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

Kotlin

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

בדיקת היכולות

לפני שנרשמים לעדכוני נתונים, צריך לבדוק שהמכשיר יכול לספק את סוג הנתונים שהאפליקציה צריכה. בדיקת היכולות מאפשרת להפעיל או להשבית תכונות מסוימות, או לשנות את ממשק המשתמש של האפליקציה כדי לפצות על תכונות שלא זמינות.

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
}

הרשמה לנתונים פסיביים

אפשר לקבל נתונים פסיביים דרך שירות, קריאה חוזרת (callback) או שניהם. שירות מאפשר לאפליקציה לקבל נתונים ברקע כששום חלק מהאפליקציה לא גלוי בחזית. כשמקבלים נתונים ברקע, הם מועברים בקבוצות. הקריאה החוזרת מקבלת נתונים בקצב קצת יותר מהיר, אבל רק בזמן שהאפליקציה פועלת והקריאה החוזרת מקבלת הודעה בהצלחה.

בכל שיטה שתבחרו, קודם צריך ליצור PassiveListenerConfig שקובע אילו סוגי נתונים יתקבלו, כמו בדוגמה הבאה:

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

כדי לקבל נתונים באמצעות קריאה חוזרת (callback), צריך להגדיר ולרשום את הקריאה החוזרת, כמו בדוגמה הבאה:

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. נדרשת הרשאה לשירותי בריאות, שמאמתת שרק שירותי בריאות יכולים להיות מקושרים לשירות:

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

תרגום בזמן אמת

הנתונים שמתקבלים משירותי הבריאות הם נתונים שנאספים בקבוצות, ולכן יכול להיות שתקבלו נקודות נתונים מסוגים שונים, או כמה נקודות נתונים מאותו סוג, באותה קבוצה. כדי לקבוע את הסדר הנכון של האירועים, צריך להשתמש בחותמות הזמן שכלולות באובייקטים האלה ולא בזמן שבו הם התקבלו באפליקציה.

כדי לקבל חותמות זמן לכל DataPoint, קודם מחשבים את חותמת הזמן של ההפעלה, כמו בדוגמה הבאה:

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

אחר כך אפשר להעביר את הערך הזה אל getStartInstant() או אל getEndInstant().

שחזור הרשמות אחרי אתחול

רישום נתונים פסיבי לא נשמר אחרי הפעלה מחדש. כדי לקבל נתונים אחרי הפעלה מחדש של המכשיר, צריך ליצור מחדש את הרישומים באמצעות BroadcastReceiver שמאזין לשידור המערכת ACTION_BOOT_COMPLETED.

במקלט, אל תנסו לשחזר את הרישומים ישירות. במקום זאת, אפשר להעביר את הפונקציונליות הזו לעובד WorkManager. כשמפעילים את המכשיר, יכול להיות שיחלפו 10 שניות או יותר עד ששירותי הבריאות יאשרו בקשה לרישום נתונים פסיביים, וזה עלול לחרוג מזמן הביצוע המותר של BroadcastReceiver. לעומת זאת, ל-WorkManager workers יש מגבלת ביצוע של 10 דקות.

בקטע הקוד הבא מוצגת דוגמה ל-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) {
        // ...
    }
}

יעדים פסיביים

אתם יכולים להגדיר לקוח פסיבי שישלח לאפליקציה הודעה כשמגיעים ליעדים פסיביים, כמו השלמת 10,000 צעדים ביום.

כדי לעשות זאת, יוצרים מטרה עסקית, כמו בדוגמה הבאה:

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 -> {
            // ...
        }
    }
}