如需针对个人健康记录 (PHR) 使用 Android 框架 API,您必须满足以下条件:
- 使用 Android Studio 配置环境
- 实例化
HealthConnectManager
- 处理权限
- 创建数据源
使用 Android Studio 配置环境
如需访问最新的 API,您还需要访问最新的相关工具:
- 安装最新版本的 Android Studio。
- 在 Android Studio 中,依次前往 Tools > SDK Manager。
- 在“SDK Platforms”标签页中,选择 Android Baklava。
- 此版本包含 API 级别 36 和 PHR API。
- 在“SDK Tools”标签页中,选择最新的 Android SDK Build-Tools。
- 点击“确定”以安装 SDK。
- 使用最新版本的 Android Gradle 插件。
声明 API 级别
如需访问 PHR API,您需要以适当的 Android 版本为目标平台。可通过在 app/build.gradle
中声明 API 级别来实现此目的。
...
compileSdk = 36
defaultConfig {
targetSdk = 36
...
如需详细了解如何配置 API 级别,请参阅 uses-sdk-element 和设置 Android 16 SDK 文档。
声明权限
在 AndroidManifest.xml
中添加了新的个人健康记录权限。
请仅为您打算在应用中使用的那些数据类型声明权限。代码块中列出了与医疗相关的权限的完整列表。不包括 Health Connect 的非个人健康记录相关权限。
...
<!-- Medical permissions -->
<uses-permission android:name="android.permission.health.WRITE_MEDICAL_DATA"/>
<uses-permission android:name="android.permission.health.READ_MEDICAL_DATA_ALLERGIES_INTOLERANCES"/>
<uses-permission android:name="android.permission.health.READ_MEDICAL_DATA_CONDITIONS"/>
<uses-permission android:name="android.permission.health.READ_MEDICAL_DATA_IMMUNIZATIONS"/>
<uses-permission android:name="android.permission.health.READ_MEDICAL_DATA_LABORATORY_RESULTS"/>
<uses-permission android:name="android.permission.health.READ_MEDICAL_DATA_MEDICATIONS"/>
<uses-permission android:name="android.permission.health.READ_MEDICAL_DATA_PERSONAL_DETAILS"/>
<uses-permission android:name="android.permission.health.READ_MEDICAL_DATA_PRACTITIONER_DETAILS"/>
<uses-permission android:name="android.permission.health.READ_MEDICAL_DATA_PREGNANCY"/>
<uses-permission android:name="android.permission.health.READ_MEDICAL_DATA_PROCEDURES"/>
<uses-permission android:name="android.permission.health.READ_MEDICAL_DATA_SOCIAL_HISTORY"/>
<uses-permission android:name="android.permission.health.READ_MEDICAL_DATA_VISITS"/>
<uses-permission android:name="android.permission.health.READ_MEDICAL_DATA_VITAL_SIGNS"/>
...
如果应用尝试使用需要权限的 API,而该权限未在应用的清单中声明,系统会抛出 SecurityException
。
对于类型为 SecurityException
的异常,exception.localizedMessage
中会显示一条有用的错误消息。
实例化 HealthConnectManager
HealthConnectManager
是负责处理权限互动以及对 Health Connect 本地数据库的读写操作的类。我们将在后续部分中介绍 HealthConnectManager
的实例方法。由于 HealthConnectManager 会公开系统服务,因此您无法直接实例化此类,而必须使用 getSystemService
。特别要注意的是,系统服务与其实例化的上下文紧密耦合,不应在该上下文之外提供访问权限。
import android.health.connect.HealthConnectManager
...
val healthConnectManager: HealthConnectManager = requireNotNull(applicationContext.getSystemService(HealthConnectManager::class.java))
处理权限
应用的用户必须向您的应用授予访问 Health Connect 数据的权限。为此,请启动具有指定权限的新 activity,并使用授予的权限的生成列表。用户可以在新 activity 的界面中选择需要向您的应用授予的权限。请仅针对您的应用打算使用的那些数据类型请求权限。
val MEDICAL_PERMISSIONS = arrayOf(
"android.permission.health.READ_MEDICAL_DATA_ALLERGIES_INTOLERANCES",
"android.permission.health.READ_MEDICAL_DATA_CONDITIONS",
"android.permission.health.READ_MEDICAL_DATA_IMMUNIZATIONS",
"android.permission.health.READ_MEDICAL_DATA_LABORATORY_RESULTS",
"android.permission.health.READ_MEDICAL_DATA_MEDICATIONS",
"android.permission.health.READ_MEDICAL_DATA_PERSONAL_DETAILS",
"android.permission.health.READ_MEDICAL_DATA_PRACTITIONER_DETAILS",
"android.permission.health.READ_MEDICAL_DATA_PREGNANCY",
"android.permission.health.READ_MEDICAL_DATA_PROCEDURES",
"android.permission.health.READ_MEDICAL_DATA_SOCIAL_HISTORY",
"android.permission.health.READ_MEDICAL_DATA_VISITS",
"android.permission.health.READ_MEDICAL_DATA_VITAL_SIGNS",
"android.permission.health.WRITE_MEDICAL_DATA",
)
...
private lateinit var mRequestPermissionLauncher: ActivityResultLauncher<Array<String>>
...
mRequestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
permissionMap: Map<String, Boolean> ->
requestPermissionResultHandler(permissionMap)
}
}
...
private fun requestPermissionResultHandler(permissionMap: Map<String, Boolean>) {
// Evaluate permissionMap and handle any missing permissions
}
...
mRequestPermissionLauncher.launch(MEDICAL_PERMISSIONS)
如果尝试在没有必要权限的情况下进行读写,将会导致 HealthConnectException
。
对于类型为 HealthConnectException
的异常,exception.localizedMessage
中会显示一条有用的错误消息。
创建数据源
如需将健康数据写入 Health Connect,您的应用必须先创建一个数据源来存储信息。数据源通常表示特定的 API 或医疗系统。
在本例中,我们创建了一个名为 My Hospital
的数据源,并指定了 FHIR 版本。
import android.health.connect.CreateMedicalDataSourceRequest
import android.health.connect.HealthConnectManager
import android.health.connect.datatypes.FhirVersion
import android.health.connect.datatypes.MedicalDataSource
import android.net.Uri
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.os.asOutcomeReceiver
import kotlinx.coroutines.suspendCancellableCoroutine
...
private suspend fun createMedicalDataSource(
fhirBaseUri: Uri,
displayName: String,
fhirVersion: FhirVersion,
): String {
val dataSource =
suspendCancellableCoroutine<MedicalDataSource> { continuation ->
healthConnectManager.createMedicalDataSource(
CreateMedicalDataSourceRequest.Builder(fhirBaseUri,
displayName,
fhirVersion).build(),
Runnable::run,
continuation.asOutcomeReceiver(),
)
}
Log.d("CREATE_DATA_SOURCE", "Created source: $dataSource")
return "Created data source: $displayName"
}
...
createMedicalDataSource(
Uri.parse("example.fhir.com/R4/123"),
"My Hospital",
FhirVersion.parseFhirVersion("4.0.1"),
)
写入记录
在 JSON 中准备 FHIR 记录示例。网络上有各种采用 FHIR 格式的示例数据来源。
{
"resourceType": "Immunization",
"id": "immunization-1",
"status": "completed",
"vaccineCode": {
"coding": [
{
"system": "http://hl7.org/fhir/sid/cvx",
"code": "115"
},
{
"system": "http://hl7.org/fhir/sid/ndc",
"code": "58160-842-11"
}
],
"text": "Tdap"
},
"patient": {
"reference": "Patient/patient_1",
"display": "Example, Anne"
},
"encounter": {
"reference": "Encounter/encounter_unk",
"display": "GP Visit"
},
"occurrenceDateTime": "2018-05-21",
"primarySource": true,
"manufacturer": {
"display": "Sanofi Pasteur"
},
"lotNumber": "1",
"site": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v3-ActSite",
"code": "LA",
"display": "Left Arm"
}
],
"text": "Left Arm"
},
"route": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v3-RouteOfAdministration",
"code": "IM",
"display": "Injection, intramuscular"
}
],
"text": "Injection, intramuscular"
},
"doseQuantity": {
"value": 0.5,
"unit": "mL"
},
"performer": [
{
"function": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0443",
"code": "AP",
"display": "Administering Provider"
}
],
"text": "Administering Provider"
},
"actor": {
"reference": "Practitioner/practitioner_1",
"type": "Practitioner",
"display": "Dr Maria Hernandez"
}
}
]
}
插入数据:
import android.health.connect.UpsertMedicalResourceRequest
import android.health.connect.datatypes.MedicalResource
...
private suspend fun loadJSONFromAsset(assetName: String): String {
...
private suspend fun upsertMedicalResources(
requests: List<UpsertMedicalResourceRequest>
): List<MedicalResource> {
Log.d("UPSERT_RESOURCES", "Writing ${requests.size} resources")
val resources =
suspendCancellableCoroutine<List<MedicalResource>> { continuation ->
healthConnectManager.upsertMedicalResources(
requests,
Runnable::run,
continuation.asOutcomeReceiver(),
)
}
Log.d("UPSERT_RESOURCES", "Wrote ${resources.size} resources")
return resources
}
...
private suspend fun insertResource(insertedDataSourceId: String, resource: String): String {
val insertedResources =
upsertMedicalResources(
listOf(
UpsertMedicalResourceRequest.Builder(
insertedDataSourceId,
FhirVersion.parseFhirVersion("4.0.1"),
resource,
)
.build()
)
)
return insertedResources.joinToString(
separator = "\n",
transform = MedicalResource::toString,
)
}
...
val immunizationResource =
loadJSONFromAsset("immunization_1.json")
insertResource(dataSource.id, immunizationResource)
upsertMedicalResources
将 UpsertMedicalResourceRequest
列表作为参数。如果调用中传递的任何单个 UpsertMedicalResourceRequest
都未能插入,则系统不会将整个 UpsertMedicalResourceRequest
列表提交到代码库。
如果任何请求包含无效的 MedicalDataSource
ID,API 将抛出 IllegalArgumentException
。如果任何请求因任何其他原因被视为无效,调用方将收到 HealthConnectException
。
特定请求的唯一键是数据源 ID、FHIR 资源类型和 FHIR 资源 ID 的组合。如果请求中的这三个项与现有记录匹配,系统就会触发更新。否则,系统会创建新记录。
读取记录
按类型读取记录,然后根据需要处理结果。
import android.health.connect.ReadMedicalResourcesInitialRequest
import android.health.connect.ReadMedicalResourcesResponse
import android.health.connect.datatypes.MedicalResource.MEDICAL_RESOURCE_TYPE_IMMUNIZATIONS
...
private suspend fun readImmunization(): List<MedicalResource> {
var receiver: OutcomeReceiver<ReadMedicalResourcesResponse, HealthConnectException>
val request: ReadMedicalResourcesInitialRequest =
ReadMedicalResourcesInitialRequest.Builder(MEDICAL_RESOURCE_TYPE_IMMUNIZATIONS).build()
val resources =
suspendCancellableCoroutine<ReadMedicalResourcesResponse> { continuation ->
receiver = continuation.asOutcomeReceiver()
healthConnectManager.readMedicalResources(request, Runnable::run, receiver)
}
.medicalResources
Log.d("READ_MEDICAL_RESOURCES", "Read ${resources.size} resources")
return resources
}