يوفّر 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" إذا لزم الأمر.
مدى توفّر الميزة
لتحديد ما إذا كان جهاز المستخدم يتيح خطط التدريب في 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( 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( 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( 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()
)