מעקב אחר הצעדים

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

המדריך הזה תואם לגרסה 1.1.0‎-alpha12 של Health Connect ואילך.

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

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

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

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 של המכשיר הנוכחי.

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

Kotlin

android {
    compileSdk = 35
    compileSdkExtension = 22
}

מגניב

android {
    compileSdk 35
    compileSdkExtension 22
}

ממשק ה-API של getCurrentDeviceDataSource() עדיין לא זמין בספריית Jetpack של Health Connect. בדוגמאות הבאות נעשה שימוש ב-Android framework API, שדורש קריאה חוזרת (callback) של Executor ו-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 של המכשיר. אם האפליקציה מציגה שיוך (Attribution) לנתוני צעדים, אפשר להשתמש ב-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.

איך בודקים אם אפליקציית Health Connect זמינה

לפני שמנסים להשתמש ב-Health Connect, האפליקציה צריכה לוודא שהשירות זמין במכשיר של המשתמש. יכול להיות שאפליקציית Health Connect לא מותקנת מראש בחלק מהמכשירים או שהיא מושבתת. אפשר לבדוק את הזמינות באמצעות ה-method‏ HealthConnectClient.getSdkStatus().

איך בודקים אם אפליקציית Health Connect זמינה

fun checkHealthConnectAvailability(context: Context) {
    val providerPackageName = "com.google.android.apps.healthdata"
    val availabilityStatus = HealthConnectClient.getSdkStatus(context, providerPackageName)

    if (availabilityStatus == HealthConnectClient.SDK_UNAVAILABLE) {
      // Health Connect is not available. Guide the user to install/enable it.
      // For example, show a dialog.
      return // early return as there is no viable integration
    }
    if (availabilityStatus == HealthConnectClient.SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED) {
      // Health Connect is available but requires an update.
      // Optionally redirect to package installer to find a provider, for example:
      val uriString = "market://details?id=$providerPackageName&url=healthconnect%3A%2F%2Fonboarding"
      context.startActivity(
        Intent(Intent.ACTION_VIEW).apply {
          setPackage("com.android.vending")
          data = Uri.parse(uriString)
          putExtra("overlay", true)
          putExtra("callerId", context.packageName)
        }
      )
      return
    }
    // Health Connect is available, obtain a HealthConnectClient instance
    val healthConnectClient = HealthConnectClient.getOrCreate(context)
    // Issue operations with healthConnectClient
}

בהתאם לסטטוס שמוחזר על ידי getSdkStatus(), אתם יכולים להנחות את המשתמש להתקין או לעדכן את Health Connect מחנות Google Play, אם יש צורך.

ההרשאות הנדרשות

הגישה לנתוני הצעדים מוגנת על ידי ההרשאות הבאות:

  • android.permission.health.READ_STEPS
  • android.permission.health.WRITE_STEPS

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

זו ההרשאה שצריך להצהיר עליה כדי שיהיה אפשר לכתוב צעדים:

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

כדי לקרוא את הצעדים, צריך לבקש את ההרשאות הבאות:

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

בקשת הרשאות מהמשתמש

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

val permissions =
    setOf(
        HealthPermission.getReadPermission(StepsRecord::class),
        HealthPermission.getWritePermission(StepsRecord::class)
    )
אפשר להשתמש ב-getGrantedPermissions כדי לבדוק אם האפליקציה כבר קיבלה את ההרשאות הנדרשות. אם לא, משתמשים ב-createRequestPermissionResultContract כדי לבקש את ההרשאות האלה. הפעולה הזו תציג את מסך ההרשאות של Health Connect.
val permissions = setOf(
        HealthPermission.getReadPermission(StepsRecord::class),
        HealthPermission.getWritePermission(StepsRecord::class),
        HealthPermission.getReadPermission(HeartRateRecord::class),
        HealthPermission.getWritePermission(HeartRateRecord::class)
    )

val requestPermissionsLauncher = rememberLauncherForActivityResult(
    contract = PermissionController.createRequestPermissionResultContract()
) { grantedPermissions ->
    if (grantedPermissions.containsAll(permissions)) {
        coroutineScope.launch { snackbarHostState.showSnackbar("Permissions granted!") }
    } else {
        coroutineScope.launch { snackbarHostState.showSnackbar("Permissions denied.") }
    }
}
המשתמשים יכולים לתת או לבטל הרשאות בכל שלב, ולכן האפליקציה צריכה לבדוק אם ההרשאות ניתנו בכל פעם לפני שהיא משתמשת בהן, ולטפל במקרים שבהם הרשאה כלשהי בוטלה.

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

כל StepsRecord מכיל את הפרטים הבאים:

  • ‫count: מספר הצעדים שבוצעו במרווח הזמן, בתור Long.
  • ‫startTime: שעת ההתחלה של מרווח הזמן למדידה.
  • ‫endTime: שעת הסיום של מרווח המדידה.
  • ‫startZoneOffset: ההסטה מאזור הזמן של שעת ההתחלה.
  • ‫endZoneOffset: ההסטה מאזור הזמן של שעת הסיום.

צבירת נתונים נתמכת

אלה הערכים המצטברים שאפשר להזין ב-StepsRecord:

אלה הערכים המצטברים שאפשר להזין ב-StepsCadenceRecord:

דוגמת שימוש

בקטעים הבאים מוסבר איך לקרוא ולכתוב נתונים ב-StepsRecord.

כתיבה של נתוני הצעדים

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

val zoneOffset = ZoneOffset.systemDefault().rules.getOffset(startTime)
val stepsRecord = StepsRecord(
    count = 120,
    startTime = startTime,
    endTime = endTime,
    startZoneOffset = zoneOffset,
    endZoneOffset = zoneOffset,
    metadata = Metadata.autoRecorded(
        device = Device(type = Device.TYPE_WATCH)
    )
)
healthConnectClient.insertRecords(listOf(stepsRecord))

קריאת נתונים נצברים

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

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
}

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

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

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