קריאת נתונים גולמיים

בדוגמה הבאה אפשר לראות איך לקרוא נתונים גולמיים כחלק מתהליך העבודה הנפוץ.

קריאת נתונים

‫Health Connect מאפשר לאפליקציות לקרוא נתונים ממאגר הנתונים כשהאפליקציה פועלת בחזית וברקע:

  • קריאות ברקע: בדרך כלל אפשר לקרוא נתונים מ-Health Connect כשהאפליקציה פועלת ברקע. במקרים כאלה, כדאי להשתמש בשירות שפועל בחזית כדי להריץ את הפעולה, למקרה שהמשתמש או המערכת ימקמו את האפליקציה ברקע במהלך פעולת קריאה.

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

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

כדי לקרוא רשומות, יוצרים ReadRecordsRequest ומספקים אותו כשמתקשרים אל readRecords.

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

val response = healthConnectClient.readRecords(
    ReadRecordsRequest(
        HeartRateRecord::class,
        timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
    )
)
response.records.forEach { record ->
    /* Process records */
}

אפשר גם לקרוא את הנתונים שלכם בצורה מצטברת באמצעות aggregate.

suspend fun readStepsAggregate(startTime: Instant, endTime: Instant): Long {
    val response = healthConnectClient.aggregate(
        AggregateRequest(
            metrics = setOf(StepsRecord.COUNT_TOTAL),
            timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
        )
    )
    return response[StepsRecord.COUNT_TOTAL] ?: 0L
}

קריאה של נתוני הצעדים בנייד

ב-Android 14 (רמת API‏ 34) ובגרסה 20 ומעלה של SDK Extension,‏ Health Connect מספק ספירת צעדים במכשיר. אם אפליקציה כלשהי קיבלה את ההרשאה READ_STEPS, ‏ Health Connect מתחיל לתעד את הצעדים מהמכשיר עם Android, והמשתמשים רואים את נתוני הצעדים שנוספו אוטומטית לרשומות צעדים ב-Health Connect.

כדי לבדוק אם יש במכשיר אפשרות לספירת צעדים, צריך לוודא שמערכת ההפעלה היא Android 14 (רמת API‏ 34) לפחות, ושיש במכשיר לפחות SDK extension גרסה 20:

val isStepTrackingAvailable =
    Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE &&
        SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 20

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

שינוי השיוך לשלבים במכשיר

החל מהעדכון של יוני 2026, השיוך של צעדים שנמדדים באופן מקורי על ידי Health Connect יהיה לשם חבילה סינתטי (SPN), כמו com.android.healthconnect.phone.jd5bdd37e1a8d3667a05d0abebfc4a89e.

בעבר, שלבים מובנים שויכו לשם החבילה android. נתוני השלבים ההיסטוריים שנרשמו לפני יוני 2026 ישמרו את שם החבילה android.

מספרי ה-SPN הם ספציפיים למכשיר ומוגבלים לכל אפליקציה בנפרד כדי להגן על פרטיות המשתמשים:

  • יציב: ה-SPN של המכשיר הנוכחי יציב לאפליקציה שלכם.
  • במסגרת האפליקציה: אפליקציות שונות באותו מכשיר רואות SPN שונה לנתוני הצעדים במכשיר.

שאילתה לגבי צעדים במכשיר

מכיוון ש-SPN הם ספציפיים למכשיר ומוגבלים להיקף מסוים, אסור להגדיר ערכי SPN בהארדקוד. במקום זאת, משתמשים ב-API‏ getCurrentDeviceDataSource כדי לאחזר את ה-SPN של המכשיר הנוכחי.

למרות שספירת צעדים במכשיר דורשת גרסת SDK 20 ומעלה, API‏ getCurrentDeviceDataSource() זמין ב-Android 14 (רמת API‏ 34) עם גרסת SDK 22 ומעלה. כדי להשתמש ב-API הזה, צריך להגדיר את compileSdkExtension לערך 22 ומעלה בקובץ build.gradle.kts או build.gradle ברמת המודול:

Kotlin

android {
    compileSdk = 35
    compileSdkExtension = 22
}

מגניב

android {
    compileSdk 35
    compileSdkExtension 22
}

ממשק ה-API של getCurrentDeviceDataSource() עדיין לא זמין בספריית Health Connect Jetpack. בדוגמאות הבאות נעשה שימוש ב-Android framework API, שנדרשים בו Executor וקריאה חוזרת (callback) של OutcomeReceiver:

import android.content.Context
import android.health.connect.DeviceDataSource
import android.health.connect.HealthConnectException
import android.health.connect.HealthConnectManager
import android.os.OutcomeReceiver

val healthConnectManager = context.getSystemService(HealthConnectManager::class.java)
healthConnectManager?.getCurrentDeviceDataSource(
    context.mainExecutor,
    object : OutcomeReceiver<DeviceDataSource, HealthConnectException> {
        override fun onResult(result: DeviceDataSource) {
            val currentDeviceSpn = result.deviceDataOrigin.packageName
        }

        override fun onError(error: HealthConnectException) {
            // Handle error
        }
    }
)

אם האפליקציה צריכה לקרוא את הנתונים של הצעדים במכשיר, או אם היא מציגה נתוני צעדים שמפורטים לפי אפליקציית המקור או המכשיר, צריך לשלוח שאילתה לגבי רשומות שבהן DataOrigin הוא android או תואם ל-SPN של המכשיר. אם האפליקציה מציגה שיוך לנתוני צעדים, צריך להשתמש ב-metadata.device כדי לזהות את מכשיר המקור של רשומות נפרדות. לשלבים במכשיר שמזוהים על ידי SPN בנתונים מצטברים, אפשר להשתמש במטא-נתונים של המכשיר, כמו model או manufacturer מ-DeviceDataSource, לצורך שיוך, או להשתמש בתווית כללית כמו 'הטלפון שלך' לשלבים במכשיר.

בדוגמה הבאה אפשר לראות איך קוראים נתונים מצטברים של ספירת הצעדים במכשיר על ידי סינון לפי android וגם לפי ה-SPN של המכשיר הנוכחי:

import android.content.Context
import android.health.connect.DeviceDataSource
import android.health.connect.HealthConnectException
import android.health.connect.HealthConnectManager
import android.os.Build
import android.os.OutcomeReceiver
import android.os.ext.SdkExtensions
import androidx.health.connect.client.HealthConnectClient
import androidx.health.connect.client.records.StepsRecord
import androidx.health.connect.client.records.metadata.DataOrigin
import androidx.health.connect.client.request.AggregateRequest
import androidx.health.connect.client.time.TimeRangeFilter
import java.time.Instant
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlinx.coroutines.suspendCancellableCoroutine

suspend fun readDeviceStepsByTimeRange(
    healthConnectClient: HealthConnectClient,
    context: Context,
    startTime: Instant,
    endTime: Instant
) {
    // 1. Check if SDK Extension 22+ is available for getCurrentDeviceDataSource()
    val isDataSourceApiAvailable =
        Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE &&
            SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 22

    try {
        val healthConnectManager = context.getSystemService(HealthConnectManager::class.java)

        // 2. Safely fetch the package name only if the API is available
        val currentDeviceSpn = if (isDataSourceApiAvailable && healthConnectManager != null) {
            suspendCancellableCoroutine { continuation ->
                healthConnectManager.getCurrentDeviceDataSource(
                    context.mainExecutor,
                    object : OutcomeReceiver<DeviceDataSource, HealthConnectException> {
                        override fun onResult(result: DeviceDataSource) {
                            continuation.resume(result.deviceDataOrigin.packageName)
                        }

                        override fun onError(error: HealthConnectException) {
                            continuation.resumeWithException(error)
                        }
                    }
                )
            }
        } else {
            null
        }

        val dataOriginFilters = mutableSetOf(DataOrigin("android"))

        // 3. Explicit null-safety check using .let
        currentDeviceSpn?.let {
            dataOriginFilters.add(DataOrigin(it))
        }

        val response = healthConnectClient.aggregate(
            AggregateRequest(
                metrics = setOf(StepsRecord.COUNT_TOTAL),
                timeRangeFilter = TimeRangeFilter.between(startTime, endTime),
                dataOriginFilter = dataOriginFilters
            )
        )

        val stepCount = response[StepsRecord.COUNT_TOTAL]

    } catch (e: Exception) {
        // Now this catch block only handles actual runtime exceptions,
        // rather than Errors from missing methods.
    }
}

ספירת צעדים במכשיר

  • שימוש בחיישנים: אפליקציית Health Connect משתמשת בחיישן TYPE_STEP_COUNTER של SensorManager. החיישן הזה מותאם לצריכת חשמל נמוכה, ולכן הוא אידיאלי למעקב רציף אחרי צעדים ברקע.
  • גרנולריות הנתונים: כדי לחסוך בסוללה, נתוני הצעדים בדרך כלל נכתבים למסד הנתונים של Health Connect בקבוצות, ולא בתדירות גבוהה יותר מפעם בדקה.
  • שיוך (Attribution): שלבים שתועדו על ידי התכונה הזו לפני יוני 2026 משויכים לשם החבילה android ב-DataOrigin. אחרי התאריך הזה, הן משויכות למספר SPN ספציפי למכשיר. מידע נוסף זמין במאמר בנושא שינוי השיוך של שלבים במכשיר.
  • הפעלה: מנגנון ספירת הצעדים במכשיר פעיל רק אם לפחות אפליקציה אחת במכשיר קיבלה את ההרשאה READ_STEPS ב-Health Connect.

דוגמה לקריאה ברקע

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

<application>
  <uses-permission android:name="android.permission.health.READ_HEALTH_DATA_IN_BACKGROUND" />
...
</application>

בדוגמה הבאה אפשר לראות איך קוראים נתונים של מספר הצעדים ברקע של משתמש בפרק זמן מסוים באמצעות WorkManager:

class ScheduleWorker(appContext: Context, workerParams: WorkerParameters) :
    CoroutineWorker(appContext, workerParams) {

    override suspend fun doWork(): Result {
        val healthConnectClient = HealthConnectClient.getOrCreate(applicationContext)
        // Perform background read logic here
        return Result.success()
    }
}
fun enqueueBackgroundReadWorker(context: Context, healthConnectClient: HealthConnectClient) {
    if (healthConnectClient
            .features
            .getFeatureStatus(
                HealthConnectFeatures.FEATURE_READ_HEALTH_DATA_IN_BACKGROUND
            ) == HealthConnectFeatures.FEATURE_STATUS_AVAILABLE
    ) {

        val periodicWorkRequest = PeriodicWorkRequestBuilder<ScheduleWorker>(1, TimeUnit.HOURS)
            .build()

        WorkManager.getInstance(context).enqueueUniquePeriodicWork(
            "read_health_connect",
            ExistingPeriodicWorkPolicy.KEEP,
            periodicWorkRequest
        )
    }
}

לפרמטר ReadRecordsRequest יש ערך ברירת מחדל pageSize של 1,000. אם מספר הרשומות בבקשה אחת של readResponse חורג מהערך של pageSize, צריך לחזור על כל הדפים של התשובה כדי לאחזר את כל הרשומות באמצעות pageToken. עם זאת, חשוב להיזהר כדי לא לחרוג ממגבלות הקצב.

דוגמה לקריאה של pageToken

מומלץ להשתמש ב-pageToken לקריאת רשומות כדי לאחזר את כל הנתונים שזמינים מהתקופה המבוקשת.

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

val type = HeartRateRecord::class
val endTime = Instant.now()
val startTime = endTime.minus(Duration.ofDays(7))

try {
    var pageToken: String? = null
    do {
        val readResponse =
            healthConnectClient.readRecords(
                ReadRecordsRequest(
                    recordType = type,
                    timeRangeFilter = TimeRangeFilter.between(
                        startTime,
                        endTime
                    ),
                    pageToken = pageToken
                )
            )
        val records = readResponse.records
        // Do something with records
        pageToken = readResponse.pageToken
    } while (pageToken != null)
} catch (quotaError: IllegalStateException) {
    // Backoff
}
מידע על שיטות מומלצות לקריאת מערכי נתונים גדולים זמין במאמר בנושא תכנון למניעת הגבלת קצב של יצירת בקשות.

קריאת נתונים שנכתבו בעבר

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

יש כמה הגבלות על קריאה:

  • ב-Android מגרסה 14 ואילך

    • אין מגבלה היסטורית על אפליקציה שקוראת את הנתונים שלה.
    • מגבלה של 30 יום על אפליקציה שקוראת נתונים אחרים.
  • ב-Android מגרסה 13 ומטה

    • מגבלה של 30 יום על קריאת נתונים באפליקציה.

כדי להסיר את ההגבלות, צריך לבקש הרשאת קריאה.

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

בדוגמה הבאה אפשר לראות איך מציינים שם חבילה כשקוראים רשומות של קצב הלב:

try {
    val response =  healthConnectClient.readRecords(
        ReadRecordsRequest(
            recordType = HeartRateRecord::class,
            timeRangeFilter = TimeRangeFilter.between(startTime, endTime),
            dataOriginFilter = setOf(DataOrigin("com.my.package.name"))
        )
    )
    for (record in response.records) {
        // Process each record
    }
} catch (e: Exception) {
    // Run error handling here
}

קריאת מזהה מכשיר ייחודי (UDI)

במקרים של רשומות שמגיעות ממכשירים ברמה רפואית, אפליקציות קריאה יכולות לחלץ את החלק של מזהה המכשיר (DI) מתוך מזהה המכשיר הייחודי (UDI) ממטא-הנתונים של המכשיר. הוראות לעיבוד המידע הזה ולמיפוי שלו מול מסדי נתונים רגולטוריים מופיעות במדריך בנושא מטא-נתונים.

קריאת נתונים מלפני יותר מ-30 יום

כברירת מחדל, כל האפליקציות יכולות לקרוא נתונים מ-Health Connect עד 30 ימים לפני מועד מתן ההרשאה הראשונה.

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

היסטוריית ההרשאות של אפליקציה שנמחקה

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

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

טיפול בחריגים

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

בכל שיטה ב-HealthConnectClient מפורטים החריגים שיכולים להיות מוצגים. באופן כללי, האפליקציה צריכה לטפל בחריגים הבאים:

טבלה 1: חריגים ב-Health Connect ושיטות מומלצות
חריג תיאור שיטה מומלצת
IllegalStateException אחד מהתרחישים הבאים התרחש:

  • השירות Health Connect לא זמין.
  • הבקשה לא תקינה. לדוגמה, בקשה מצטברת בקטגוריות תקופתיות שבהן נעשה שימוש באובייקט Instant עבור timeRangeFilter.

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

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

RemoteException אירעו שגיאות בשירות הבסיסי שאליו ה-SDK מתחבר, או בתקשורת איתו.

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

  • חשוב לבצע סנכרון קבוע בין מאגר הנתונים של האפליקציה לבין Health Connect.
  • כדי לטפל בבעיות בבקשות, כדאי להשתמש באסטרטגיות של ניסיונות חוזרים והשהיה מעריכית (exponential backoff).

SecurityException נתקלים בבעיות כשהבקשות דורשות הרשאות שלא ניתנו. כדי למנוע את זה, חשוב לוודא שהצהרתם על השימוש בסוגי הנתונים של Health Connect באפליקציה שפרסמתם. בנוסף, עליכם להצהיר על הרשאות Health Connect בקובץ המניפסט ובפעילות.