تتبُّع الخطوات

يوفّر Health Connect نوع بيانات الخطوات لتسجيل عدد الخطوات باستخدام StepsRecord، فالخطوات هي مقياس أساسي في تتبُّع الصحة واللياقة البدنية.

قراءة بيانات الخطوات على الأجهزة الجوّالة

في نظام التشغيل Android 14 (المستوى 34 لواجهة برمجة التطبيقات) والإصدار 20 أو الإصدارات الأحدث من حزمة SDK Extension، يوفّر تطبيق Health Connect ميزة احتساب الخطوات على الجهاز. إذا تم منح أي تطبيق إذن READ_STEPS، سيبدأ تطبيق Health Connect في تسجيل الخطوات من جهاز Android، وسيلاحظ المستخدمون أنّه تتم إضافة بيانات الخطوات تلقائيًا إلى إدخالات الخطوات في Health Connect.

للتأكّد من توفّر ميزة احتساب الخطوات على الجهاز فقط، تحقَّق من أنّ الجهاز يعمل بنظام التشغيل Android 14 (مستوى واجهة برمجة التطبيقات 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 بشكل ثابت، بل استخدام واجهة برمجة التطبيقات getCurrentDeviceDataSource() لاسترداد اسم SPN للجهاز الحالي.

بينما يتطلّب احتساب الخطوات على الجهاز فقط الإصدار 20 أو إصدارًا أحدث من حزمة تطوير البرامج (SDK)، تتوفّر واجهة برمجة التطبيقات getCurrentDeviceDataSource() على نظام التشغيل Android 14 (المستوى 34 لواجهة برمجة التطبيقات) مع الإصدار 11 أو إصدار أحدث من حزمة تطوير البرامج (SDK).

لا تتوفّر واجهة برمجة التطبيقات getCurrentDeviceDataSource() بعد في مكتبة Health Connect Jetpack. تستخدِم الأمثلة التالية واجهة برمجة تطبيقات إطار عمل Android بدلاً من ذلك:

import android.content.Context
import android.health.connect.HealthConnectManager

val healthConnectManager = context.getSystemService(HealthConnectManager::class.java)
val deviceDataSource = healthConnectManager?.getCurrentDeviceDataSource()
val currentDeviceSpn = deviceDataSource?.deviceDataOrigin?.packageName

إذا كان تطبيقك يحتاج إلى قراءة الخطوات المسجّلة على الجهاز فقط، أو إذا كان يعرض بيانات الخطوات مقسّمة حسب التطبيق أو الجهاز المصدر، عليك طلب البحث عن السجلات التي تكون فيها DataOrigin هي android أو تطابق رقم SPN الخاص بالجهاز. إذا كان تطبيقك يعرض معلومات تحديد المصدر لبيانات الخطوات، استخدِم metadata.device لتحديد الجهاز المصدر لكل سجلّ. بالنسبة إلى الخطوات التي تتم على الجهاز فقط والمحدّدة برقم تعريف مقدّم الخدمة (SPN) في البيانات المجمّعة، يمكنك استخدام البيانات الوصفية للجهاز، مثل model أو manufacturer من DeviceDataSource لتحديد المصدر، أو استخدام تصنيف عام، مثل "هاتفك"، للخطوات التي تتم على الجهاز فقط.

يوضّح المثال التالي كيفية قراءة بيانات عدد الخطوات المجمّعة على الجهاز فقط من خلال الفلترة حسب كل من android وSPN للجهاز الحالي:

import android.content.Context
import android.health.connect.HealthConnectManager
import android.os.Build
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

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

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

        // 2. Safely fetch the package name only if API is available and data exists
        val currentDeviceSpn = if (isDataSourceApiAvailable) {
            healthConnectManager?.getCurrentDeviceDataSource()?.deviceDataOrigin?.packageName
        } 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 بمعدّل لا يزيد عن مرة واحدة في الدقيقة.
  • تحديد المصدر: إنّ الخطوات التي تسجّلها هذه الميزة قبل يونيو 2026 يتم تحديد مصدرها على أنّه اسم حزمة android في DataOrigin، وبعد هذا التاريخ، يتم تحديد مصدرها على أنّه اسم حزمة خاص بالجهاز. اطّلِع على تغيير تحديد المصدر للخطوات المسجّلة على الجهاز.
  • التفعيل: لا تكون آلية احتساب الخطوات على الجهاز نشطة إلا عندما يمنح المستخدم تطبيقًا واحدًا على الأقل على الجهاز إذن READ_STEPS في Health Connect.

التحقّق من توفّر تطبيق Health Connect

قبل محاولة استخدام Health Connect، يجب أن يتأكّد تطبيقك من توفُّر Health Connect على جهاز المستخدم. قد لا يكون تطبيق Health Connect مثبَّتًا مسبقًا على جميع الأجهزة أو قد يكون غير مفعَّل. يمكنك التحقّق من توفّره باستخدام طريقة HealthConnectClient.getSdkStatus().

كيفية التحقّق من توفّر Health Connect

fun checkHealthConnectAvailability(context: Context) {
    val providerPackageName = "com.google.android.apps.healthdata" // Or get from HealthConnectClient.DEFAULT_PROVIDER_PACKAGE_NAME
    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(
        device = Device(type = Device.TYPE_WATCH),
        recordingMethod = Metadata.RECORDING_METHOD_AUTOMATICALLY_RECORDED
    )
)
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 */
}