يتوافق هذا الدليل مع الإصدار 1.1.0-alpha12 من Health Connect أو الإصدارات الأحدث.
يوفّر 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" 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" إذا لزم الأمر.
مدى توفّر الميزة
لتحديد ما إذا كان جهاز المستخدم يتيح ميزة "خطط التدريب" في Health Connect، تحقَّق من توفُّرFEATURE_PLANNED_EXERCISE على جهاز المستخدم:
if (healthConnectClient
.features
.getFeatureStatus(
HealthConnectFeatures.FEATURE_PLANNED_EXERCISE
) == HealthConnectFeatures.FEATURE_STATUS_AVAILABLE) {
// Feature is available
} else {
// Feature isn't available
}
الأذونات المطلوبة
يتم حماية الوصول إلى التمارين المخطط لها من خلال الأذونات التالية:
android.permission.health.READ_PLANNED_EXERCISEandroid.permission.health.WRITE_PLANNED_EXERCISE
لإضافة ميزة التمارين المخطَّط لها إلى تطبيقك، ابدأ بطلب
أذونات لنوع البيانات PlannedExerciseSession.
في ما يلي الإذن الذي يجب الإفصاح عنه لتتمكّن من كتابة التمارين الرياضية المخطَّط لها:
<application>
<uses-permission
android:name="android.permission.health.WRITE_PLANNED_EXERCISE" />
...
</application>
لقراءة التمارين الرياضية المخطَّط لها، عليك طلب الأذونات التالية:
<application>
<uses-permission
android:name="android.permission.health.READ_PLANNED_EXERCISE" />
...
</application>
طلب الأذونات من المستخدم
بعد إنشاء مثيل عميل، يحتاج تطبيقك إلى طلب أذونات من المستخدم. يجب السماح للمستخدمين بمنح الأذونات أو رفضها في أي وقت. لإجراء ذلك، أنشئ مجموعة من الأذونات لأنواع البيانات المطلوبة. تأكَّد من تضمين الأذونات في المجموعة في بيان Android أولاً.
val permissions = setOf( HealthPermission.getReadPermission(HeartRateRecord::class), HealthPermission.getWritePermission(HeartRateRecord::class), HealthPermission.getReadPermission(PlannedExerciseSessionRecord::class), HealthPermission.getWritePermission(PlannedExerciseSessionRecord::class), HealthPermission.getReadPermission(ExerciseSessionRecord::class), HealthPermission.getWritePermission(ExerciseSessionRecord::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.") } } }
الأذونات ذات الصلة
ترتبط خطط التدريب بجلسات التمارين. لذلك، يجب أن يمنح المستخدم الإذن باستخدام كل نوع سجلّ مرتبط بخطة تدريب من أجل الاستفادة بشكل كامل من هذه الميزة في Health Connect.
على سبيل المثال، إذا كانت خطة التدريب تقيس معدّل نبضات قلب المستخدم أثناء سلسلة من الجري، قد يحتاج المطوّر إلى الإفصاح عن الأذونات التالية ويحتاج المستخدم إلى منحها من أجل كتابة جلسة التمرين وقراءة النتائج لتقييمها لاحقًا:
android.permission.health.READ_EXERCISEandroid.permission.health.READ_EXERCISE_ROUTESandroid.permission.health.READ_HEART_RATEandroid.permission.health.WRITE_EXERCISEandroid.permission.health.WRITE_EXERCISE_ROUTEandroid.permission.health.WRITE_HEART_RATE
ومع ذلك، غالبًا ما يكون التطبيق الذي ينشئ خطط التدريب ويقيّم الأداء مقارنةً بالخطط ليس هو نفسه التطبيق الذي يستخدم خطط التدريب ويكتب بيانات التمارين الفعلية. استنادًا إلى نوع التطبيق، قد لا تكون جميع أذونات القراءة والكتابة مطلوبة. على سبيل المثال، قد تحتاج فقط إلى هذه الأذونات لكل نوع من التطبيقات:
| تطبيق خطة التدريب | تطبيق التمارين الرياضية |
|---|---|
WRITE_PLANNED_EXERCISE |
READ_PLANNED_EXERCISE |
READ_EXERCISE |
WRITE_EXERCISE |
READ_EXERCISE_ROUTES |
WRITE_EXERCISE_ROUTE |
READ_HEART_RATE |
WRITE_HEART_RATE |
المعلومات المضمّنة في سجلّ جلسة تمرين مخطَّط لها
- عنوان الجلسة
- قائمة بفترات التمارين المخطط لها
- وقت بدء الجلسة وانتهائها
- نوع التمرين
- ملاحظات حول النشاط
- البيانات الوصفية.
- رقم تعريف جلسة التمرين المكتملة: تتم كتابة هذا الرقم تلقائيًا بعد اكتمال جلسة تمرين مرتبطة بجلسة التمرين المخطط لها هذه.
المعلومات المضمّنة في سجلّ فترة تمرين مخطَّط لها
تحتوي مجموعة التمارين المخطَّط لها على قائمة بخطوات التمرين، وذلك لإتاحة تكرار مجموعات مختلفة من الخطوات (على سبيل المثال، تنفيذ تسلسل من تمارين ثني الذراعين وتمارين بيربي وتمارين المعدة خمس مرات متتالية).
- وصف الحظر
- قائمة بخطوات التمرين المخطَّط لها
- عدد التكرارات
المعلومات المضمّنة في سجلّ خطوات التمرين المخطَّط له
- وصف الخطوة
- فئة التمرين:
- نوع التمرين:
- قائمة الأداء المستهدف
- هدف الإكمال:
عمليات التجميع المتوافقة
لا تتوفّر عمليات تجميع متوافقة لنوع البيانات هذا.
مثال للاستخدام
لنفترض أنّ مستخدمًا يخطّط للجري لمدة 90 دقيقة بعد يومَين من الآن. ستتضمّن هذه الجولة ثلاث لفات حول بحيرة مع معدّل نبضات قلب مستهدف يتراوح بين 90 و110 نبضة في الدقيقة.
- يحدّد المستخدم جلسة تمرين مخطَّط لها في تطبيق خطة تدريب على النحو التالي:
- تاريخا البدء والانتهاء المخطَّط لهما للتشغيل
- نوع التمرين (الجري)
- عدد الجولات (التكرارات)
- الأداء المستهدف لمعدّل نبضات القلب (بين 90 و110 نبضة في الدقيقة)
- يتم تجميع هذه المعلومات في مجموعات وخطوات تمارين، ويتم تسجيلها
في Health Connect من خلال تطبيق خطة التدريب على شكل
PlannedExerciseSessionRecord. - ينفّذ المستخدم الجلسة المخطَّط لها (الجري).
- يتم تسجيل بيانات التمرين المرتبطة بالجلسة إما:
- من خلال جهاز قابل للارتداء أثناء الجلسة على سبيل المثال، معدّل نبضات القلب.
تتم كتابة هذه البيانات في Health Connect كنوع سجلّ للنشاط. في هذه الحالة،
HeartRateRecord. - يدويًا من قِبل المستخدم بعد الجلسة على سبيل المثال، الإشارة إلى
بداية ونهاية التشغيل الفعلي. تتم كتابة هذه البيانات في Health
Connect كـ
ExerciseSessionRecord.
- من خلال جهاز قابل للارتداء أثناء الجلسة على سبيل المثال، معدّل نبضات القلب.
تتم كتابة هذه البيانات في Health Connect كنوع سجلّ للنشاط. في هذه الحالة،
- وفي وقت لاحق، يقرأ تطبيق خطة التدريب البيانات من Health Connect لتقييم الأداء الفعلي مقارنةً بالأهداف التي حدّدها المستخدم في جلسة التمرين المخطط لها.
خطِّط للتمارين وحدِّد أهدافك
قد يخطّط المستخدم لممارسة التمارين الرياضية في المستقبل ويضع أهدافًا لها. اكتب هذه البيانات في Health Connect على أنّها جلسة تمرين مخطَّط لها.
في المثال الموضّح في مثال على الاستخدام، يخطّط المستخدم للجري لمدة 90 دقيقة بعد يومين من الآن. سيتضمّن هذا الجري ثلاث لفات حول بحيرة مع معدّل نبضات قلب مستهدف يتراوح بين 90 و110 نبضة في الدقيقة.
قد تجد مقتطفًا كهذا في معالج النموذج لتطبيق يسجل جلسات التمارين المخطط لها في Health Connect. ويمكن العثور عليها أيضًا في نقطة الاستيعاب لعمليات الدمج، مثلاً مع خدمة تقدّم التدريب.
// Verify the user has granted all necessary permissions for this task val grantedPermissions = healthConnectClient.permissionController.getGrantedPermissions() if (!grantedPermissions.contains( HealthPermission.getWritePermission(PlannedExerciseSessionRecord::class))) { // The user hasn't granted the app permission to write planned exercise session data. Log.w("HealthConnect", "Write permission for PlannedExerciseSessionRecord not granted.") return } val plannedExerciseSessionRecord = PlannedExerciseSessionRecord( startTime = startTime, endTime = endTime, exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_RUNNING, blocks = listOf( PlannedExerciseBlock( repetitions = 1, steps = listOf( PlannedExerciseStep( exerciseType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_RUNNING, exercisePhase = PlannedExerciseStep.EXERCISE_PHASE_ACTIVE, completionGoal = ExerciseCompletionGoal.RepetitionsGoal(repetitions = 3), performanceTargets = listOf( ExercisePerformanceTarget.HeartRateTarget( minHeartRate = 90.0, maxHeartRate = 110.0 ) ) ), ), description = "Three laps around the lake" ) ), title = "Run at lake", notes = null, metadata = Metadata.activelyRecorded( device = Device(type = Device.Companion.TYPE_PHONE), ), startZoneOffset = null, endZoneOffset = null, ) try { // Attempt to insert the record val response = healthConnectClient.insertRecords(listOf(plannedExerciseSessionRecord)) // If execution reaches here, the insert succeeded. // Safely extract the ID using firstOrNull() val insertedPlannedExerciseSessionId = response.recordIdsList.firstOrNull() if (insertedPlannedExerciseSessionId != null) { Log.d("HealthConnect", "Successfully inserted planned exercise session ID: $insertedPlannedExerciseSessionId") } else { Log.w("HealthConnect", "Insertion succeeded but no record IDs were returned.") } } catch (e: Exception) { // Handle API failures, database errors, or system issues safely without crashing Log.e("HealthConnect", "Failed to insert planned exercise session record", e) }
تسجيل بيانات التمارين والأنشطة
بعد يومين، يسجّل المستخدم جلسة التمرين الفعلية. يمكنك كتابة هذه البيانات في Health Connect على أنّها جلسة تمرين.
في هذا المثال، تطابقت مدة جلسة المستخدم مع المدة المخطط لها بالضبط.
قد يظهر المقتطف التالي في معالج النماذج لتطبيق يسجّل جلسات التمارين الرياضية في Health Connect. وقد تظهر أيضًا في معالجات نقل البيانات وتصديرها لجهاز قابل للارتداء قادر على اكتشاف جلسات التمرين وتسجيلها.
تمت إعادة استخدام insertedPlannedExerciseSessionId هنا من المثال السابق. في
تطبيق حقيقي، سيتم تحديد المعرّف من خلال اختيار المستخدم لجلسة تمرين
مخطط لها من قائمة الجلسات الحالية.
// Verify the user has granted all necessary permissions for this task val grantedPermissions = healthConnectClient.permissionController.getGrantedPermissions() if (!grantedPermissions.contains( HealthPermission.getWritePermission(ExerciseSessionRecord::class))) { // The user doesn't granted the app permission to write exercise session data. return } val sessionDuration = Duration.ofMinutes(90) val sessionEndTime = Instant.now() val sessionStartTime = sessionEndTime.minus(sessionDuration) val exerciseSessionRecord = ExerciseSessionRecord( startTime = sessionStartTime, startZoneOffset = ZoneOffset.UTC, endTime = sessionEndTime, endZoneOffset = ZoneOffset.UTC, exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_RUNNING, segments = listOf( ExerciseSegment( startTime = sessionStartTime, endTime = sessionEndTime, repetitions = 3, segmentType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_RUNNING ) ), title = "Run at lake", plannedExerciseSessionId = insertedPlannedExerciseSessionId, metadata = Metadata.activelyRecorded( device = Device(type = Device.Companion.TYPE_PHONE) ) ) val insertedExerciseSessions = healthConnectClient.insertRecords(listOf(exerciseSessionRecord))
ويسجّل جهاز قابل للارتداء أيضًا معدّل نبضات القلب طوال فترة الجري. يمكن استخدام المقتطف التالي لإنشاء سجلات ضمن النطاق المستهدف.
في التطبيق الفعلي، قد تظهر الأجزاء الأساسية من هذا المقتطف في معالج الرسائل الواردة من جهاز قابل للارتداء، والذي يكتب القياسات في Health Connect عند جمعها.
// Verify the user has granted all necessary permissions for this task val grantedPermissions = healthConnectClient.permissionController.getGrantedPermissions() if (!grantedPermissions.contains( HealthPermission.getWritePermission(HeartRateRecord::class))) { // The user doesn't granted the app permission to write heart rate record data. return } val samples = mutableListOf<HeartRateRecord.Sample>() var currentTime = sessionStartTime while (currentTime.isBefore(sessionEndTime)) { val bpm = Random.nextInt(21) + 90 val heartRateRecord = HeartRateRecord.Sample( time = currentTime, beatsPerMinute = bpm.toLong(), ) samples.add(heartRateRecord) currentTime = currentTime.plusSeconds(180) } val heartRateRecord = HeartRateRecord( startTime = sessionStartTime, startZoneOffset = ZoneOffset.UTC, endTime = sessionEndTime, endZoneOffset = ZoneOffset.UTC, samples = samples, metadata = Metadata.activelyRecorded( device = Device(type = Device.Companion.TYPE_WATCH) ) ) val insertedHeartRateRecords = healthConnectClient.insertRecords(listOf(heartRateRecord))
تقييم الأداء المستهدف
في اليوم التالي لتمرين المستخدم، يمكنك استرداد التمرين المسجَّل والتحقّق من أي أهداف تمرين مخطَّط لها وتقييم أنواع البيانات الإضافية لتحديد ما إذا تم تحقيق الأهداف المحدَّدة.
من المحتمل أن يتم العثور على مقتطف مثل هذا في مهمة دورية لتقييم الأداء المستهدف أو عند تحميل قائمة بالتمارين وعرض إشعار حول الأداء المستهدف في أحد التطبيقات.
// Verify the user has granted all necessary permissions for this task val grantedPermissions = healthConnectClient.permissionController.getGrantedPermissions() if (!grantedPermissions.containsAll( listOf( HealthPermission.getReadPermission(ExerciseSessionRecord::class), HealthPermission.getReadPermission(PlannedExerciseSessionRecord::class), HealthPermission.getReadPermission(HeartRateRecord::class) ) ) ) { // The user doesn't granted the app permission to read exercise session record data. return } val searchDuration = Duration.ofDays(1) val searchEndTime = Instant.now() val searchStartTime = searchEndTime.minus(searchDuration) val response = healthConnectClient.readRecords( ReadRecordsRequest<ExerciseSessionRecord>( timeRangeFilter = TimeRangeFilter.between(searchStartTime, searchEndTime) ) ) for (exerciseRecord in response.records) { val plannedExerciseRecordId = exerciseRecord.plannedExerciseSessionId val plannedExerciseRecord = if (plannedExerciseRecordId == null) null else healthConnectClient.readRecord( PlannedExerciseSessionRecord::class, plannedExerciseRecordId ).record if (plannedExerciseRecord != null) { val aggregateRequest = AggregateRequest( metrics = setOf(HeartRateRecord.BPM_AVG), timeRangeFilter = TimeRangeFilter.between( exerciseRecord.startTime, exerciseRecord.endTime ), ) val aggregationResult = healthConnectClient.aggregate(aggregateRequest) val maxBpm = aggregationResult[HeartRateRecord.BPM_MAX] val minBpm = aggregationResult[HeartRateRecord.BPM_MIN] if (maxBpm != null && minBpm != null) { plannedExerciseRecord.blocks.forEach { block -> block.steps.forEach { step -> step.performanceTargets.forEach { target -> when (target) { is ExercisePerformanceTarget.HeartRateTarget -> { val minTarget = target.minHeartRate val maxTarget = target.maxHeartRate if( minBpm >= minTarget && maxBpm <= maxTarget ) { // Success! } } // Handle more target types } } } } } } } }
جلسات التمارين الرياضية
يمكن أن تتضمّن جلسات التمارين أي نشاط، من الجري إلى كرة الريشة.
كتابة بيانات جلسات التمارين الرياضية
في ما يلي كيفية إنشاء طلب إدراج يتضمّن جلسة:
suspend fun writeExerciseSession(healthConnectClient: HealthConnectClient) {
healthConnectClient.insertRecords(
listOf(
ExerciseSessionRecord(
startTime = START_TIME,
startZoneOffset = START_ZONE_OFFSET,
endTime = END_TIME,
endZoneOffset = END_ZONE_OFFSET,
exerciseType = ExerciseSessionRecord.ExerciseType.RUNNING,
title = "My Run",
metadata = Metadata.manualEntry()
),
// ... other records
)
)
}
قراءة بيانات جلسة تمرين
في ما يلي مثال على كيفية قراءة جلسة تمرين:
suspend fun readExerciseSessions(
healthConnectClient: HealthConnectClient,
startTime: Instant,
endTime: Instant
) {
val response =
healthConnectClient.readRecords(
ReadRecordsRequest(
ExerciseSessionRecord::class,
timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
)
)
for (exerciseRecord in response.records) {
// Process each exercise record
// Optionally pull in with other data sources of the same time range.
val distanceRecord =
healthConnectClient
.readRecords(
ReadRecordsRequest(
DistanceRecord::class,
timeRangeFilter =
TimeRangeFilter.between(
exerciseRecord.startTime,
exerciseRecord.endTime
)
)
)
.records
}
}
كتابة بيانات النوع الفرعي
يمكن أن تتألف الجلسات أيضًا من بيانات اختيارية للنوع الفرعي، ما يثري الجلسة بمعلومات إضافية.
على سبيل المثال، يمكن أن تتضمّن جلسات التمارين الفئات ExerciseSegment وExerciseLap وExerciseRoute:
val segments = listOf(
ExerciseSegment(
startTime = Instant.parse("2022-01-02T10:10:10Z"),
endTime = Instant.parse("2022-01-02T10:10:13Z"),
segmentType = ActivitySegmentType.BENCH_PRESS,
repetitions = 373
)
)
val laps = listOf(
ExerciseLap(
startTime = Instant.parse("2022-01-02T10:10:10Z"),
endTime = Instant.parse("2022-01-02T10:10:13Z"),
length = 0.meters
)
)
ExerciseSessionRecord(
exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_CALISTHENICS,
startTime = Instant.parse("2022-01-02T10:10:10Z"),
endTime = Instant.parse("2022-01-02T10:10:13Z"),
startZoneOffset = ZoneOffset.UTC,
endZoneOffset = ZoneOffset.UTC,
segments = segments,
laps = laps,
route = route,
metadata = Metadata.manualEntry()
)