适用人群:Android 健康/运动类应用开发者
目标:把 Health Connect 的接入流程、配置项、关键代码和避坑点一次讲清楚
Health Connect 是 Android 侧统一的健康数据平台。对开发者来说,核心价值是:
HealthConnectClient。dependencies {
implementation "androidx.health.connect:connect-client:1.1.0-alpha11"
}
建议:实际项目里用最新稳定/推荐版本,避免长期停留在旧 alpha。
<queries>
<package android:name="com.google.android.apps.healthdata" />
</queries>
<uses-permission android:name="android.permission.health.READ_HEART_RATE"/>
<uses-permission android:name="android.permission.health.WRITE_HEART_RATE"/>
<uses-permission android:name="android.permission.health.READ_STEPS"/>
<uses-permission android:name="android.permission.health.WRITE_STEPS"/>
<uses-permission android:name="android.permission.health.READ_EXERCISE"/>
<uses-permission android:name="android.permission.health.WRITE_EXERCISE"/>
<uses-permission android:name="android.permission.health.READ_TOTAL_CALORIES_BURNED"/>
<uses-permission android:name="android.permission.health.WRITE_TOTAL_CALORIES_BURNED"/>
<uses-permission android:name="android.permission.health.READ_WEIGHT"/>
<uses-permission android:name="android.permission.health.WRITE_WEIGHT"/>
可选能力(按需声明):
<uses-permission android:name="android.permission.health.READ_HEALTH_DATA_IN_BACKGROUND" />
<uses-permission android:name="android.permission.health.READ_HEALTH_DATA_HISTORY" />
<!-- Android 13 及以下 -->
<intent-filter>
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
</intent-filter>
<!-- Android 14+ -->
<intent-filter>
<action android:name="android.intent.action.VIEW_PERMISSION_USAGE"/>
<category android:name="android.intent.category.HEALTH_PERMISSIONS"/>
</intent-filter>
这里要落地一个真实可访问的隐私说明页面,讲清楚“用什么数据、为什么用、如何处理、如何删除”。
private val healthConnectClient by lazy { HealthConnectClient.getOrCreate(context) }
suspend fun hasAllPermissions(permissions: Set<String>): Boolean {
return healthConnectClient.permissionController
.getGrantedPermissions()
.containsAll(permissions)
}
fun requestPermissionsActivityContract(): ActivityResultContract<Set<String>, Set<String>> {
return PermissionController.createRequestPermissionResultContract()
}
suspend fun writeWeightInput(weightInput: Double) {
val time = ZonedDateTime.now().withNano(0)
val weightRecord = WeightRecord(
metadata = Metadata.manualEntry(),
weight = Mass.kilograms(weightInput),
time = time.toInstant(),
zoneOffset = time.offset
)
healthConnectClient.insertRecords(listOf(weightRecord))
}
suspend fun readWeightInputs(start: Instant, end: Instant): List<WeightRecord> {
val request = ReadRecordsRequest(
recordType = WeightRecord::class,
timeRangeFilter = TimeRangeFilter.between(start, end)
)
return healthConnectClient.readRecords(request).records
}
suspend fun computeWeeklyAverage(start: Instant, end: Instant): Mass? {
val request = AggregateRequest(
metrics = setOf(WeightRecord.WEIGHT_AVG),
timeRangeFilter = TimeRangeFilter.between(start, end)
)
return healthConnectClient.aggregate(request)[WeightRecord.WEIGHT_AVG]
}
fun isFeatureAvailable(feature: Int): Boolean {
return healthConnectClient.features.getFeatureStatus(feature) ==
HealthConnectFeatures.FEATURE_STATUS_AVAILABLE
}
FEATURE_READ_HEALTH_DATA_IN_BACKGROUNDFEATURE_READ_HEALTH_DATA_HISTORY核心思路:
示例骨架:
suspend fun getChangesToken(): String {
return healthConnectClient.getChangesToken(
ChangesTokenRequest(setOf(ExerciseSessionRecord::class))
)
}
suspend fun getChanges(token: String): Flow<ChangesMessage> = flow {
var nextToken = token
do {
val response = healthConnectClient.getChanges(nextToken)
if (response.changesTokenExpired) throw IOException("Changes token expired")
emit(ChangesMessage.ChangeList(response.changes))
nextToken = response.nextChangesToken
} while (response.hasMore)
emit(ChangesMessage.NoMoreChanges(nextToken))
}
zoneOffset,否则跨时区展示会错位。dataOriginFilter,决定“只看本应用”还是“融合全来源”。getFeatureStatus 再开放 UI。你在接入时建议统一按以下规范做:
示例(仅示意):
try {
val records = healthConnectClient.readRecords(request).records
Log.i("HealthConnect", "readWeight success, size=${records.size}, range=$start~$end")
} catch (e: Exception) {
Log.e("HealthConnect", "readWeight failed, range=$start~$end", e)
throw e
}



