Health Connect alpha04版本使用

一、Health Connect简介


Health Connect是一款新出的健康数据集成系统,可以通过提供的sdk统一管理数据,比如睡眠数据的读写、生命体征数据读写等等…
由于是当前的版本都是alpha版本,alpha04之前的版本存在较大缺陷,alpha03所需最低sdk版本是30(导致大部分手机都使用不了加入了该包的软件)。所幸在9月初,health connect新出alpha04版本,最低的sdk要求下调到了26,涵盖了市场上大部分Android版本。

附上alpha04的依赖:

implementation "androidx.health.connect:connect-client:1.0.0-alpha04"

以及一些可能会用到的依赖:

	implementation "androidx.compose.ui:ui:1.2.1"
    implementation "androidx.compose.material:material:1.2.1"
    implementation "androidx.compose.ui:ui-tooling-preview:1.2.1"
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1"
    implementation "androidx.activity:activity-compose:1.5.1"

    debugImplementation "androidx.compose.ui:ui-tooling:1.2.1"

    coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.2.2"

二、Health Connect的使用入门


tip:首先确保要使用的手机上安装了health connect软件,否则是使用不了的,health connect的安装包可以网上找一下,找不到就从我上传的资源下。

2.1 权限申明及注册
首先我们在资源文件values新建文件health_permissions.xml(名字不重要,可以随意取,代码里跟着改就行)。附上文件里的内容
在这里插入图片描述

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <array name="health_permissions">
        <item>androidx.health.permission.ActivitySession.READ</item>
        <item>androidx.health.permission.ActivitySession.WRITE</item>
        <item>androidx.health.permission.ActivityEvent.READ</item>
        <item>androidx.health.permission.ActivityEvent.WRITE</item>
        <item>androidx.health.permission.SleepSession.READ</item>
        <item>androidx.health.permission.SleepSession.WRITE</item>
        <item>androidx.health.permission.SleepStage.READ</item>
        <item>androidx.health.permission.SleepStage.WRITE</item>
        <item>androidx.health.permission.Steps.READ</item>
        <item>androidx.health.permission.Steps.WRITE</item>
        <item>androidx.health.permission.Speed.READ</item>
        <item>androidx.health.permission.Speed.WRITE</item>
        <item>androidx.health.permission.Distance.READ</item>
        <item>androidx.health.permission.Distance.WRITE</item>
        <item>androidx.health.permission.HeartRate.READ</item>
        <item>androidx.health.permission.HeartRate.WRITE</item>
        <item>androidx.health.permission.TotalEnergyBurned.READ</item>
        <item>androidx.health.permission.TotalEnergyBurned.WRITE</item>
        <item>androidx.health.permission.Weight.READ</item>
        <item>androidx.health.permission.Weight.WRITE</item>
        <item>androidx.health.permission.TotalCaloriesBurned.READ</item>
        <item>androidx.health.permission.TotalCaloriesBurned.WRITE</item>
        <item>androidx.health.permission.RespiratoryRate.WRITE</item>
        <item>androidx.health.permission.RespiratoryRate.READ</item>
        <item>androidx.health.permission.HeartRateVariabilityRmssd.WRITE</item>
        <item>androidx.health.permission.HeartRateVariabilityRmssd.READ</item>
        <item>androidx.health.permission.Nutrition.READ</item>
        <item>androidx.health.permission.Nutrition.WRITE</item>
    </array>
</resources>

完成上一步,进入到清单文件AndroidManifest中,在manifest节点下添加以下语句

    <queries>
        <package android:name="com.google.android.apps.healthdata" />
    </queries>

找到你准备使用health connect的活动,在该活动节点中申明权限文件位置名称,以下是我的示例,实际activity的包名根据你自己来

<activity
                android:name=".ui.activity.GoogleHealthActivity"
                android:exported="false"
                android:theme="@style/Theme.AppCompat.Light.NoActionBar">

            <meta-data
                    android:name="health_permissions"
                    android:resource="@array/health_permissions" />
            <intent-filter>
                <action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
            </intent-filter>
        </activity>

准备工作结束

三、Health Connect的数据读写


3.1 检查权限及health connect是否可用

health connect不可用的情况一般有两种:1.手机上未安装health connect软件。2.手机Android版本不满足health connect(但这种情况基本不现实)。

因此检查不可用情况基本是针对第一种情况,我们可以自己写个工具类,检查当前手机是否安装了此软件。

检查手机是否安装某个软件工具方法:

/**检测安装包是否已安装
     * packageName填入需要检测软件的包名即可
     * IFTTT软件包名为com.google.android.apps.healthdata
     * 代码为kotlin书写
     */
  @SuppressLint("QueryPermissionsNeeded")
  fun installAppList(context: Context, packageName: String): Boolean {
        val installedPackageList = context.packageManager.getInstalledPackages(0)
        installedPackageList.forEach {
            if (packageName == it.packageName)
                return true
        }
        return false
    }

除开这种比较通用的方法外,health connect也给出了相应的接口检测是否可用:

HealthConnectClient.isAvailable(context)

检查完可用性之后,我们需要检查health connect是否已授予数据读写权限,代码如下:

val permissions = setOf(
        HealthPermission.createReadPermission(SleepSessionRecord::class),
        HealthPermission.createWritePermission(SleepSessionRecord::class),
        HealthPermission.createReadPermission(SleepStageRecord::class),
        HealthPermission.createWritePermission(SleepStageRecord::class),
        HealthPermission.createReadPermission(HeartRateRecord::class),
        HealthPermission.createWritePermission(HeartRateRecord::class),
        HealthPermission.createReadPermission(TotalCaloriesBurnedRecord::class),
        HealthPermission.createReadPermission(ExerciseSessionRecord::class),
        HealthPermission.createWritePermission(HeartRateVariabilityRmssdRecord::class),
        HealthPermission.createReadPermission(HeartRateVariabilityRmssdRecord::class),
        HealthPermission.createReadPermission(NutritionRecord::class),
        HealthPermission.createWritePermission(RespiratoryRateRecord::class),
        HealthPermission.createReadPermission(RespiratoryRateRecord::class),
        HealthPermission.createWritePermission(NutritionRecord::class)
    )

private fun initPermission() {
        if (!HealthConnectClient.isAvailable(this)) {
            return
        }
        val healthConnectClient = HealthConnectClient.getOrCreate(this)
        val requestPermissionActivityContract =
            healthConnectClient.permissionController.createRequestPermissionActivityContract()
        val requestPermissions =
            registerForActivityResult(requestPermissionActivityContract) { granted ->
                if (granted.containsAll(permissions)) {
                // 全部权限授权成功
                    Log.i("12345678", ": granted.containsAll")
                } else {
                 // 未全部授权,跳转到授权页面
                	requestPermissions.launch(permissions)
                    Log.i("12345678", ": not granted.containsAll")
                }
            }

3.2 数据的读取写入

这里将列举部分数据的读取写入操作,读取内容包括:总卡路里的消耗、锻炼记录、呼吸率、心率、心率变异率、营养、睡眠记录,写入内容包括:营养数据、心率、睡眠数据、心率变异率、呼吸率。

以下是代码:

DateUtils工具类

const val YYYY_MM_DD = "yyyy-MM-dd"
const val YYYY_MM_DD_1 = "MM/dd/yyyy"
const val YYYY_MM_DD_HH_mm_ss = "yyyy-MM-dd HH:mm:ss"
const val YYYY_MM_DD_HH_mm = "yyyy-MM-dd HH:mm"
const val hh_mm_aa = "hh:mm aa"
const val hh_mm = "hh:mm"
const val HH_mm = "HH:mm"

object DateUtil {
/获取YYYY_MM_DD_HH_mm_ss中的年、月、日、小时和分钟
    fun getListIntDateByYYYYMMDDHHMM2(dateStr: String):List<Int>{
        val listDate = mutableListOf<Int>()
        val dateParse = SimpleDateFormat(YYYY_MM_DD_HH_mm_ss).parse(dateStr)
        val formatTime = SimpleDateFormat(YYYY_MM_DD_HH_mm_ss).format(dateParse)
        listDate.add(formatTime.substring(0,4).toInt())
        listDate.add(formatTime.substring(5,7).toInt())
        listDate.add(formatTime.substring(8,10).toInt())
        listDate.add(formatTime.substring(11,13).toInt())
        listDate.add(formatTime.substring(14,16).toInt())
        return listDate
    }
//获取YYYY_MM_DD_HH_mm_ss中的小时和分钟
    fun getListIntDateByYYYYMMDDHHMM(dateStr: String):List<Int>{
        val listDate = mutableListOf<Int>()
        val dateParse = SimpleDateFormat(YYYY_MM_DD_HH_mm_ss).parse(dateStr)
        val formatTime = SimpleDateFormat(HH_mm).format(dateParse)
        listDate.add(formatTime.substring(0,formatTime.indexOf(":")).toInt())
        listDate.add(formatTime.substring(formatTime.indexOf(":")+1,formatTime.length).toInt())
        return listDate
    }
}

HexDump工具类

public class HexDump {
    public static String[] StringToStrArray(String sourceData)throws JSONException{
//        sourceData = "\"["+sourceData+"]\"";
//        JSONArray jsonArray = new JSONArray(sourceData);
        String[] strArr = sourceData.split(",");
        return strArr;
    }
}

数据读写工具类(其中有些Instant变量,我是存在本地中,你可以根据我给定的默认值来修改):

/ The minimum android level that can use Health Connect
const val MIN_SUPPORTED_SDK = Build.VERSION_CODES.O_MR1

/**
 * health connect 工具类
 */
class HealthConnectManager(private val context: Context) {
    private val healthConnectClient by lazy { HealthConnectClient.getOrCreate(context) }

    var availability = mutableStateOf(HealthConnectAvailability.NOT_SUPPORTED)
        private set

    init {
        checkAvailability()
    }

    fun checkAvailability() {
        availability.value = when {
            HealthConnectClient.isAvailable(context) -> HealthConnectAvailability.INSTALLED
            isSupported() -> HealthConnectAvailability.NOT_INSTALLED
            else -> HealthConnectAvailability.NOT_SUPPORTED
        }
    }

    /**
     * Determines whether all the specified permissions are already granted. It is recommended to
     * call [PermissionController.getGrantedPermissions] first in the permissions flow, as if the
     * permissions are already granted then there is no need to request permissions via
     * [PermissionController.createRequestPermissionActivityContract].
     * 是否允许了所有权限
     */
    suspend fun hasAllPermissions(permissions: Set<HealthPermission>): Boolean {
        return permissions == healthConnectClient.permissionController.getGrantedPermissions(
            permissions
        )
    }

    fun requestPermissionsActivityContract(): ActivityResultContract<Set<HealthPermission>, Set<HealthPermission>> {
        return healthConnectClient.permissionController.createRequestPermissionActivityContract()
    }

    /**
     * Obtains a list of [ExerciseSessionRecord]s in a specified time frame. An Exercise Session Record is a
     * period of time given to an activity, that would make sense to a user, e.g. "Afternoon run"
     * etc. It does not necessarily mean, however, that the user was *running* for that entire time,
     * more that conceptually, this was the activity being undertaken.
     * 读取运动数据节点
     */
    suspend fun readExerciseSessions(start: Instant, end: Instant): List<ExerciseSessionRecord> {
        val request = ReadRecordsRequest(
            recordType = ExerciseSessionRecord::class,
            timeRangeFilter = TimeRangeFilter.between(start, end),
            pageSize = 1
        )
        val response = healthConnectClient.readRecords(request)
        return response.records
    }

    /**
     * 读取营养信息
     */
    suspend fun getNutrition(start: Instant, end: Instant): List<NutritionRecord> {
        val request = ReadRecordsRequest(
            recordType = NutritionRecord::class,
            timeRangeFilter = TimeRangeFilter.Companion.between(start, end),
            pageSize = 1
        )
        val response = healthConnectClient.readRecords(request)
        return response.records
    }

/**
*写入营养数据,固定假数据可自行修改
*/
    suspend fun writeNutritionSession(start: ZonedDateTime, end: ZonedDateTime) {
        healthConnectClient.insertRecords(
            listOf(
                NutritionRecord(
                    biotin = Mass.grams(10.00),
                    caffeine = Mass.grams(10.00),
                    calcium = Mass.grams(10.00),
                    energy = Energy.calories(100.00),
                    energyFromFat = Energy.kilocalories(10.00),
                    chloride = Mass.grams(20.00),
                    cholesterol = Mass.grams(5.00),
                    chromium = Mass.grams(15.00),
                    copper = Mass.grams(51.00),
                    dietaryFiber = Mass.grams(45.00),
                    folate = Mass.grams(25.00),
                    folicAcid = Mass.grams(54.00),
                    iodine = Mass.grams(15.00),
                    iron = Mass.grams(47.00),
                    magnesium = Mass.grams(52.00),
                    manganese = Mass.grams(15.00),
                    molybdenum = Mass.grams(48.00),
                    startTime = start.toInstant(),
                    startZoneOffset = start.offset,
                    endTime = end.toInstant(),
                    endZoneOffset = end.offset
                )
            )
        )
    }

    /**
     * Writes an [ExerciseSessionRecord] to Health Connect, and additionally writes underlying data for
     * the session too, such as [StepsRecord], [DistanceRecord] etc.
     * 写入运动数据,随机假数据
     */
    suspend fun writeExerciseSession(start: ZonedDateTime, end: ZonedDateTime) {
        healthConnectClient.insertRecords(
            listOf(
                ExerciseSessionRecord(
                    startTime = start.toInstant(),
                    startZoneOffset = start.offset,
                    endTime = end.toInstant(),
                    endZoneOffset = end.offset,
                    exerciseType = ExerciseSessionRecord.ExerciseType.RUNNING,
                    title = "My Run #${Random.nextInt(0, 60)}"
                ),
                StepsRecord(
                    startTime = start.toInstant(),
                    startZoneOffset = start.offset,
                    endTime = end.toInstant(),
                    endZoneOffset = end.offset,
                    count = (1000 + 1000 * Random.nextInt(3)).toLong()
                ),
                // Mark a 5 minute pause during the workout
                ExerciseEventRecord(
                    startTime = start.toInstant().plus(10, ChronoUnit.MINUTES),
                    startZoneOffset = start.offset,
                    endTime = start.toInstant().plus(15, ChronoUnit.MINUTES),
                    endZoneOffset = end.offset,
                    eventType = ExerciseEventRecord.EventType.PAUSE
                ),
                DistanceRecord(
                    startTime = start.toInstant(),
                    startZoneOffset = start.offset,
                    endTime = end.toInstant(),
                    endZoneOffset = end.offset,
                    distance = Length.meters((1000 + 100 * Random.nextInt(20)).toDouble())
                ),
                TotalCaloriesBurnedRecord(
                    startTime = start.toInstant(),
                    startZoneOffset = start.offset,
                    endTime = end.toInstant(),
                    endZoneOffset = end.offset,
                    energy = Energy.calories((140 + Random.nextInt(20)) * 0.01)
                )
            ) + buildHeartRateSeries(start, end) + buildSpeedSeries(start, end)
        )
    }

    /**
     * Deletes an [ExerciseSessionRecord] and underlying data.
     * 根据uid删除运动数据
     */
    suspend fun deleteExerciseSession(uid: String) {
        val exerciseSession = healthConnectClient.readRecord(ExerciseSessionRecord::class, uid)
        healthConnectClient.deleteRecords(
            ExerciseSessionRecord::class,
            uidsList = listOf(uid),
            clientRecordIdsList = emptyList()
        )
        val timeRangeFilter = TimeRangeFilter.between(
            exerciseSession.record.startTime,
            exerciseSession.record.endTime
        )
        val rawDataTypes: Set<KClass<out Record>> = setOf(
            HeartRateRecord::class,
            SpeedRecord::class,
            DistanceRecord::class,
            StepsRecord::class,
            TotalCaloriesBurnedRecord::class,
            ExerciseEventRecord::class
        )
        rawDataTypes.forEach { rawType ->
            healthConnectClient.deleteRecords(rawType, timeRangeFilter)
        }
    }

    /**
     * Reads aggregated data and raw data for selected data types, for a given [ExerciseSessionRecord].
     * 这个我也不知道是啥,直接是官方拿的
     */
    suspend fun readAssociatedSessionData(
        uid: String
    ): ExerciseSessionData {
        val exerciseSession = healthConnectClient.readRecord(ExerciseSessionRecord::class, uid)
        // Use the start time and end time from the session, for reading raw and aggregate data.
        val timeRangeFilter = TimeRangeFilter.between(
            startTime = exerciseSession.record.startTime,
            endTime = exerciseSession.record.endTime
        )
        val aggregateDataTypes = setOf(
            ExerciseSessionRecord.ACTIVE_TIME_TOTAL,
            StepsRecord.COUNT_TOTAL,
            DistanceRecord.DISTANCE_TOTAL,
            TotalCaloriesBurnedRecord.ENERGY_TOTAL,
            HeartRateRecord.BPM_AVG,
            HeartRateRecord.BPM_MAX,
            HeartRateRecord.BPM_MIN,
            SpeedRecord.SPEED_AVG,
            SpeedRecord.SPEED_MAX,
            SpeedRecord.SPEED_MIN
        )
        // Limit the data read to just the application that wrote the session. This may or may not
        // be desirable depending on the use case: In some cases, it may be useful to combine with
        // data written by other apps.
        val dataOriginFilter = setOf(exerciseSession.record.metadata.dataOrigin)
        val aggregateRequest = AggregateRequest(
            metrics = aggregateDataTypes,
            timeRangeFilter = timeRangeFilter,
            dataOriginFilter = dataOriginFilter
        )
        val aggregateData = healthConnectClient.aggregate(aggregateRequest)
        val speedData = readData<SpeedRecord>(timeRangeFilter, dataOriginFilter)
        val heartRateData = readData<HeartRateRecord>(timeRangeFilter, dataOriginFilter)

        return ExerciseSessionData(
            uid = uid,
            totalActiveTime = aggregateData[ExerciseSessionRecord.ACTIVE_TIME_TOTAL],
            totalSteps = aggregateData[StepsRecord.COUNT_TOTAL],
            totalDistance = aggregateData[DistanceRecord.DISTANCE_TOTAL],
            totalEnergyBurned = aggregateData[TotalCaloriesBurnedRecord.ENERGY_TOTAL],
            minHeartRate = aggregateData[HeartRateRecord.BPM_MIN],
            maxHeartRate = aggregateData[HeartRateRecord.BPM_MAX],
            avgHeartRate = aggregateData[HeartRateRecord.BPM_AVG],
            heartRateSeries = heartRateData,
            speedRecord = speedData,
            minSpeed = aggregateData[SpeedRecord.SPEED_MIN],
            maxSpeed = aggregateData[SpeedRecord.SPEED_MAX],
            avgSpeed = aggregateData[SpeedRecord.SPEED_AVG],
        )
    }

    /**
     * Deletes all existing sleep data.
     * 删除所有睡眠数据
     */
    suspend fun deleteAllSleepData() {
        val now = Instant.now()
        healthConnectClient.deleteRecords(SleepStageRecord::class, TimeRangeFilter.before(now))
        healthConnectClient.deleteRecords(SleepSessionRecord::class, TimeRangeFilter.before(now))
    }

    /**
     * Generates a week's worth of sleep data using both a [SleepSessionRecord] to describe the overall
     * period of sleep, and additionally multiple [SleepStageRecord] periods which cover the entire
     * [SleepSessionRecord]. For the purposes of this sample, the sleep stage data is generated randomly.
     */

    /**
     * 向health connect写入后台返回的睡眠时间
     * 以及睡眠各阶段
     */
    suspend fun generateSleepData() {
        val startHour = DateUtil.getListIntDateByYYYYMMDDHHMM(
            AppCache.instance.getData(Constants.SleepTime, "2022-09-13 22:47:00")
                .toString()
        )[0]
        val startMinute = DateUtil.getListIntDateByYYYYMMDDHHMM(
            AppCache.instance.getData(Constants.SleepTime, "2022-09-13 22:47:00")
                .toString()
        )[1]

        val endHour = DateUtil.getListIntDateByYYYYMMDDHHMM(
            AppCache.instance.getData(Constants.wakeTime, "2022-09-14 05:29:00")
                .toString()
        )[0]
        val endMinute = DateUtil.getListIntDateByYYYYMMDDHHMM(
            AppCache.instance.getData(Constants.wakeTime, "2022-09-14 05:29:00")
                .toString()
        )[1]

        val records = mutableListOf<Record>()
        // Make yesterday the last day of the sleep data
        val lastDay = ZonedDateTime.now().minusDays(0).truncatedTo(ChronoUnit.DAYS)
        val notes = context.resources.getStringArray(R.array.sleep_notes_array)
        // Create 7 days-worth of sleep data

        val wakeUp = lastDay.minusDays(0)
            .withHour(endHour)
            .withMinute(endMinute)
        val bedtime = wakeUp.minusDays(1)
            .withHour(startHour)
            .withMinute(startMinute)
        val sleepSession = SleepSessionRecord(
            notes = notes[Random.nextInt(0, notes.size)],
            startTime = bedtime.toInstant(),
            startZoneOffset = bedtime.offset,
            endTime = wakeUp.toInstant(),
            endZoneOffset = wakeUp.offset
        )
        val sleepStages = generateSleepStages(bedtime, wakeUp)
        records.add(sleepSession)
        records.addAll(sleepStages)

        healthConnectClient.insertRecords(records)
    }

    /**
     * Reads sleep sessions for the previous seven days (from yesterday) to show a week's worth of
     * sleep data.
     *
     * In addition to reading [SleepSessionRecord]s, for each session, the duration is calculated to
     * demonstrate aggregation, and the underlying [SleepStageRecord] data is also read.
     * 获取前七天睡眠数据
     */
    suspend fun readSleepSessions(): List<SleepSessionData> {
        val lastDay = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS)
            .minusDays(1)
            .withHour(12)
        val firstDay = lastDay
            .minusDays(7)

        val sessions = mutableListOf<SleepSessionData>()
        val sleepSessionRequest = ReadRecordsRequest(
            recordType = SleepSessionRecord::class,
            timeRangeFilter = TimeRangeFilter.between(firstDay.toInstant(), lastDay.toInstant()),
            ascendingOrder = false,
            pageSize = 1
        )
        val sleepSessions = healthConnectClient.readRecords(sleepSessionRequest)
        sleepSessions.records.forEach { session ->
            val sessionTimeFilter = TimeRangeFilter.between(session.startTime, session.endTime)
            val durationAggregateRequest = AggregateRequest(
                metrics = setOf(SleepSessionRecord.SLEEP_DURATION_TOTAL),
                timeRangeFilter = sessionTimeFilter
            )
            val aggregateResponse = healthConnectClient.aggregate(durationAggregateRequest)
            val stagesRequest = ReadRecordsRequest(
                recordType = SleepStageRecord::class,
                timeRangeFilter = sessionTimeFilter
            )
            val stagesResponse = healthConnectClient.readRecords(stagesRequest)
            sessions.add(
                SleepSessionData(
                    uid = session.metadata.uid,
                    title = session.title,
                    notes = session.notes,
                    startTime = session.startTime,
                    startZoneOffset = session.startZoneOffset,
                    endTime = session.endTime,
                    endZoneOffset = session.endZoneOffset,
                    duration = aggregateResponse[SleepSessionRecord.SLEEP_DURATION_TOTAL],
                    stages = stagesResponse.records
                )
            )
        }
        return sessions
    }

    suspend fun readHeartSessions(): List<HeartSessionData> {
        val lastDay = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS)
            .minusDays(1)
            .withHour(12)
        val firstDay = lastDay
            .minusDays(1)

        val sessions = mutableListOf<HeartSessionData>()
        val heartSessionsRequest = ReadRecordsRequest(
            recordType = HeartRateRecord::class,
            timeRangeFilter = TimeRangeFilter.between(firstDay.toInstant(), lastDay.toInstant()),
            ascendingOrder = false
        )
        val heartSessions = healthConnectClient.readRecords(heartSessionsRequest)

        heartSessions.records.forEach { session ->
            val sessionTimeFilter = TimeRangeFilter.between(session.startTime, session.endTime)
            val durationAggregateRequest = AggregateRequest(
                metrics = setOf(HeartRateRecord.BPM_AVG),
                timeRangeFilter = sessionTimeFilter
            )
            val aggregateResponse = healthConnectClient.aggregate(durationAggregateRequest)
            val stagesRequest = ReadRecordsRequest(
                recordType = HeartRateRecord::class,
                timeRangeFilter = sessionTimeFilter
            )
            val stagesResponse = healthConnectClient.readRecords(stagesRequest)
            sessions.add(
                HeartSessionData(
                    uid = session.metadata.uid,
                    startTime = session.startTime,
                    startZoneOffset = session.startZoneOffset,
                    endTime = session.endTime,
                    endZoneOffset = session.endZoneOffset,
                    ave = aggregateResponse[HeartRateRecord.BPM_AVG],
                    stages = stagesResponse.records
                )
            )
        }
        return sessions
    }

    /**
     * Writes [WeightRecord] to Health Connect.
     */
    suspend fun writeWeightInput(weight: WeightRecord) {
        val records = listOf(weight)
        healthConnectClient.insertRecords(records)
    }

    /**
     * 向Health Connect写入SleepSessionRecord
     */
    suspend fun writeSleepSessionRecord(sleepSessionRecord: SleepSessionRecord) {
        val recordsRequest = listOf(sleepSessionRecord)
        healthConnectClient.insertRecords(recordsRequest)
    }

    /**
     * 向Health Connect写入SleepStageRecord
     */
    suspend fun writeSleepStageRecord(sleepStageRecord: SleepStageRecord) {
        val records = listOf(sleepStageRecord)
        healthConnectClient.insertRecords(records)
    }

    /**
     * 向Health Connect写入呼吸率数据
     */
    suspend fun writeRespiratoryRateRecord(respiratoryRateRecord: RespiratoryRateRecord) {
        val records = listOf(respiratoryRateRecord)
        healthConnectClient.insertRecords(records)
    }

    /**
     * 向Health Connect写入心率数据
     */
    suspend fun writeHeartRateRecord(heartRateRecord: HeartRateRecord) {
        try {
            val records = listOf(heartRateRecord)
            Log.e("12345678", "writeHeartRateRecord: " + records.size)
            healthConnectClient.insertRecords(records)
        } catch (e: Exception) {
            Log.e("12345678", "写入exception: " + e.message)
        }
    }

    /**
     * 向Health Connect写入心率变异率数据
     */
    suspend fun writeHeartRateVariabilityRmssdRecord(heartRateVariabilityRmssdRecord: HeartRateVariabilityRmssdRecord) {
        val records = listOf(heartRateVariabilityRmssdRecord)
        healthConnectClient.insertRecords(records)
    }

    /**
     * Reads in existing [WeightRecord]s.
     */
    suspend fun readWeightInputs(start: Instant, end: Instant): List<WeightRecord> {
        val request = ReadRecordsRequest(
            recordType = WeightRecord::class,
            timeRangeFilter = TimeRangeFilter.between(start, end)
        )
        val response = healthConnectClient.readRecords(request)
        return response.records
    }

    /**
     * Returns the weekly average of [WeightRecord]s.
     */
    suspend fun computeWeeklyAverage(start: Instant, end: Instant): Mass? {
        val request = AggregateRequest(
            metrics = setOf(WeightRecord.WEIGHT_AVG),
            timeRangeFilter = TimeRangeFilter.between(start, end)
        )
        val response = healthConnectClient.aggregate(request)
        return response[WeightRecord.WEIGHT_AVG]
    }

    /**
     * Deletes a [WeightRecord]s.
     */
    suspend fun deleteWeightInput(uid: String) {
        healthConnectClient.deleteRecords(
            WeightRecord::class,
            uidsList = listOf(uid),
            clientRecordIdsList = emptyList()
        )
    }

    /**
     * Obtains a changes token for the specified record types.
     */
    suspend fun getChangesToken(dataTypes: Set<KClass<out Record>>): String {
        val request = ChangesTokenRequest(dataTypes)
        return healthConnectClient.getChangesToken(request)
    }

    /**
     * Creates a [Flow] of change messages, using a changes token as a start point. The flow will
     * terminate when no more changes are available, and the final message will contain the next
     * changes token to use.
     */
    suspend fun getChanges(token: String): Flow<ChangesMessage> = flow {
        var nextChangesToken = token
        do {
            val response = healthConnectClient.getChanges(nextChangesToken)
            if (response.changesTokenExpired) {
                // As described here: https://developer.android.com/guide/health-and-fitness/health-connect/data-and-data-types/differential-changes-api
                // tokens are only valid for 30 days. It is important to check whether the token has
                // expired. As well as ensuring there is a fallback to using the token (for example
                // importing data since a certain date), more importantly, the app should ensure
                // that the changes API is used sufficiently regularly that tokens do not expire.
                throw IOException("Changes token has expired")
            }
            emit(ChangesMessage.ChangeList(response.changes))
            nextChangesToken = response.nextChangesToken
        } while (response.hasMore)
        emit(ChangesMessage.NoMoreChanges(nextChangesToken))
    }


    /**
     * Creates a random list of sleep stages that spans the specified [start] to [end] time.
     */
    private fun generateSleepStages(
        start: ZonedDateTime,
        end: ZonedDateTime
    ): List<SleepStageRecord> {
        val strArray = HexDump.StringToStrArray(
            AppCache.instance.getData(
                Constants.SleepStageRecord,
                "2022-09-13 22:47:00|2,2022-09-13 22:57:00|1,2022-09-13 22:59:00|2,2022-09-13 23:03:00|1,2022-09-13 23:25:00|2,2022-09-13 23:27:00|1,2022-09-13 23:29:00|2,2022-09-13 23:33:00|1,2022-09-14 00:03:00|2,2022-09-14 00:07:00|1,2022-09-14 00:09:00|2,2022-09-14 00:11:00|1,2022-09-14 00:17:00|2,2022-09-14 00:25:00|1,2022-09-14 00:31:00|2,2022-09-14 00:33:00|1,2022-09-14 00:37:00|2,2022-09-14 00:39:00|1,2022-09-14 00:47:00|2,2022-09-14 00:49:00|1,2022-09-14 00:55:00|2,2022-09-14 01:01:00|1,2022-09-14 01:35:00|2,2022-09-14 03:35:00|3,2022-09-14 04:01:00|2,2022-09-14 05:09:00|3,2022-09-14 05:17:00|2,2022-09-14 05:19:00|3,2022-09-14 05:21:00|2,2022-09-14 05:29:00|3"
            )
                .toString()

        )

        val sleepStages = mutableListOf<SleepStageRecord>()
        var stageStart = start
        strArray.forEachIndexed { index, s ->
            if (index == 0) {
                sleepStages.add(
                    SleepStageRecord(
                        stage = generateSleepStage(s),
                        startTime = stageStart.toInstant(),
                        startZoneOffset = stageStart.offset,
                        endTime = stageStart.toInstant().plusSeconds(60),
                        endZoneOffset = stageStart.offset
                    )
                )
            } else {
                val startYear = DateUtil.getListIntDateByYYYYMMDDHHMM2(
                    strArray[index - 1]
                )[0]
                val startMonth = DateUtil.getListIntDateByYYYYMMDDHHMM2(
                    strArray[index - 1]
                )[1]
                val startDay = DateUtil.getListIntDateByYYYYMMDDHHMM2(
                    strArray[index - 1]
                )[2]
                val startHour = DateUtil.getListIntDateByYYYYMMDDHHMM2(
                    strArray[index - 1]
                )[3]
                val startMinute = DateUtil.getListIntDateByYYYYMMDDHHMM2(
                    strArray[index - 1]
                )[4]

                val endYear = DateUtil.getListIntDateByYYYYMMDDHHMM2(
                    s
                )[0]
                val endMonth = DateUtil.getListIntDateByYYYYMMDDHHMM2(
                    s
                )[1]
                val endDay = DateUtil.getListIntDateByYYYYMMDDHHMM2(
                    s
                )[2]
                val endHour = DateUtil.getListIntDateByYYYYMMDDHHMM2(
                    s
                )[3]
                val endMinute = DateUtil.getListIntDateByYYYYMMDDHHMM2(
                    s
                )[4]

//                Log.e(
//                    "12345678test",
//                    "start:$startYear-$startMonth-$startDay-$startHour-$startMinute   end:$endYear-$endMonth-$endDay-$endHour-$endMinute"
//                )

                val endTime = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS)
                    .withYear(endYear)
                    .withMonth(endMonth)
                    .withDayOfMonth(endDay)
                    .withHour(endHour)
                    .withMinute(endMinute)
                val startTime = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS)
                    .withYear(startYear)
                    .withMonth(startMonth)
                    .withDayOfMonth(startDay)
                    .withHour(startHour)
                    .withMinute(startMinute)

                if (startTime.toInstant().isBefore(endTime.toInstant())) {
                    sleepStages.add(
                        SleepStageRecord(
                            stage = generateSleepStage(s),
                            startTime = startTime.toInstant(),
                            startZoneOffset = stageStart.offset,
                            endTime = endTime.toInstant(),
                            endZoneOffset = stageStart.offset
                        )
                    )
                }
            }
        }
        return sleepStages
    }

    /**
     * Convenience function to reuse code for reading data.
     */
    private suspend inline fun <reified T : Record> readData(
        timeRangeFilter: TimeRangeFilter,
        dataOriginFilter: Set<DataOrigin> = setOf()
    ): List<T> {
        val request = ReadRecordsRequest(
            recordType = T::class,
            dataOriginFilter = dataOriginFilter,
            timeRangeFilter = timeRangeFilter
        )
        return healthConnectClient.readRecords(request).records
    }

    private fun buildHeartRateSeries(
        sessionStartTime: ZonedDateTime,
        sessionEndTime: ZonedDateTime
    ): HeartRateRecord {
        val samples = mutableListOf<HeartRateRecord.Sample>()
        var time = sessionStartTime
        while (time.isBefore(sessionEndTime)) {
            samples.add(
                HeartRateRecord.Sample(
                    time = time.toInstant(),
                    beatsPerMinute = (80 + Random.nextInt(80)).toLong()
                )
            )
            time = time.plusSeconds(30)
        }
        return HeartRateRecord(
            startTime = sessionStartTime.toInstant(),
            startZoneOffset = sessionStartTime.offset,
            endTime = sessionEndTime.toInstant(),
            endZoneOffset = sessionEndTime.offset,
            samples = samples
        )
    }

    private fun buildSpeedSeries(
        sessionStartTime: ZonedDateTime,
        sessionEndTime: ZonedDateTime
    ) = SpeedRecord(
        startTime = sessionStartTime.toInstant(),
        startZoneOffset = sessionStartTime.offset,
        endTime = sessionEndTime.toInstant(),
        endZoneOffset = sessionEndTime.offset,
        samples = listOf(
            SpeedRecord.Sample(
                time = sessionStartTime.toInstant(),
                speed = Velocity.metersPerSecond(2.5)
            ),
            SpeedRecord.Sample(
                time = sessionStartTime.toInstant().plus(5, ChronoUnit.MINUTES),
                speed = Velocity.metersPerSecond(2.7)
            ),
            SpeedRecord.Sample(
                time = sessionStartTime.toInstant().plus(10, ChronoUnit.MINUTES),
                speed = Velocity.metersPerSecond(2.9)
            )
        )
    )

    private fun isSupported() = Build.VERSION.SDK_INT >= MIN_SUPPORTED_SDK

    // Represents the two types of messages that can be sent in a Changes flow.
    sealed class ChangesMessage {
        data class NoMoreChanges(val nextChangesToken: String) : ChangesMessage()
        data class ChangeList(val changes: List<Change>) : ChangesMessage()
    }
}

/**
 * Health Connect requires that the underlying Healthcore APK is installed on the device.
 * [HealthConnectAvailability] represents whether this APK is indeed installed, whether it is not
 * installed but supported on the device, or whether the device is not supported (based on Android
 * version).
 */
enum class HealthConnectAvailability {
    INSTALLED,
    NOT_INSTALLED,
    NOT_SUPPORTED
}

读取方法(自己自行插入到要使用的活动中)
有些地方代码重复了,可以看着优化掉或者放到读写工具类中

fun getData(client: HealthConnectClient,
        requestPermissions: ActivityResultLauncher<Set<HealthPermission>>){
        lifecycleScope.launch {
            val granted = client.permissionController.getGrantedPermissions(permissions)
            if (granted.containsAll(permissions)) {
                    Log.i("12345678", ": checkPermissionsAndRun granted.containsAll")
                    writeNutritionToConnect()
                    getCaloryInfo(client)
                    getExerciseRecord(client)
                    getRespiratoryRateRecord(client)
                    getHeartRateRecord(client)
                    getHeartRssmd(client)
                    getNutritionRecord(client)
                    getSleepRecord(client)
                    writeHeartRateToConnect(client)
                    writeSleepDataToConnect()
                    writeRmssdToConnect(client)
                    writeRespiratoryToConnect(client)
            } else {
                Log.i("12345678", ": checkPermissionsAndRun not granted.containsAll")
                requestPermissions.launch(permissions)
            }
        }
		    /*
    * 从health connect中读取睡眠数据
    */
    suspend fun getSleepRecord(client: HealthConnectClient) {
        val formatter = DateTimeFormatter.ofPattern("eee, d LLL")
        val lastDay = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS)
            .minusDays(1)
            .withHour(12)
        val firstDay = lastDay
            .minusDays(3)
        val sleepSessionRequest = ReadRecordsRequest(
            recordType = SleepSessionRecord::class,
            timeRangeFilter = TimeRangeFilter.between(
                firstDay.toInstant(),
                lastDay.toInstant()
            ),
            ascendingOrder = false
        )
        val sleepSessions = client.readRecords(sleepSessionRequest)
        if (sleepSessions.records.isNotEmpty()) {
            val healthConnectManager = HealthConnectManager(applicationContext)
            sleepSessionsList.value = healthConnectManager.readSleepSessions()
            sleepSessionsList.value.forEach {
                Log.e(
                    "12345678",
                    "日期分割:${
                        dateTimeWithOffsetOrDefault(it.startTime, it.startZoneOffset).format(
                            formatter
                        )
                    }  ${it.duration} "
                )
                sleepContent = "SleepSessionRecord:{"
                it.stages.forEach { it1 ->
                    sleepContent += "\"" + it1.stage + "\":\"" + it1.startTime + "   " + it1.startZoneOffset + "   " + it1.endTime + "   " + it1.endZoneOffset + "\","
                }
                sleepContent = sleepContent.substring(0, sleepContent.length - 1)
                sleepContent += "}"
                Log.e("12345678", sleepContent)
            }
        }
    }

    /*
    * 从health connect中读取心率数据
    */
    suspend fun getHeartRateRecord(
        client: HealthConnectClient
    ) {
        lifecycleScope.launch {
            // Permissions already granted
            val lastDay = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS)
                .minusDays(0)
                .withHour(12)
            val firstDay = lastDay
                .minusDays(1)
            val heartRateSessionRequest = ReadRecordsRequest(
                recordType = HeartRateRecord::class,
                timeRangeFilter = TimeRangeFilter.between(
                    firstDay.toInstant(),
                    lastDay.toInstant()
                ),
                pageSize = 1,
                ascendingOrder = false
            )
            val heartRateSessions = client.readRecords(heartRateSessionRequest)
            mapHeart["HeartRateRecord"] = ""
            if (heartRateSessions.records.isNotEmpty()) {
                heartRateSessions.records.forEach {
                    mapHeart["HeartRateRecord"] = Gson().toJson(it)
                }
            } else {
                Log.e("12345678", "getHeartRateRecord: empty")
            }

        }
    }

    suspend fun writeNutritionToConnect() {
        val lastDay = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS)
            .minusDays(0)
            .withHour(12)
        val firstDay = lastDay
            .minusDays(1)

        val healthConnectManager = HealthConnectManager(applicationContext)
        healthConnectManager.writeNutritionSession(firstDay, lastDay)
    }

    /*
    * 从health connect中读取心率变异率数据
    */
    fun getHeartRssmd(
        client: HealthConnectClient
    ) {
        lifecycleScope.launch {
            // Permissions already granted
            val lastDay = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS)
                .minusDays(0)
                .withHour(12)
            val firstDay = lastDay
                .minusDays(1)
            val heartRateRssmdSessionRequest = ReadRecordsRequest(
                recordType = HeartRateVariabilityRmssdRecord::class,
                timeRangeFilter = TimeRangeFilter.between(
                    firstDay.toInstant(),
                    lastDay.toInstant()
                ),
                ascendingOrder = false,
                pageSize = 1
            )
            mapHealth["HeartRateVariabilityRmssdRecord"] = ""
            val heartRateRssmdSessions = client.readRecords(heartRateRssmdSessionRequest)
            Log.i("12345678", ": heartRateRsmmdSessions长度:" + heartRateRssmdSessions.records.size)
            heartRateRssmdSessions.records.forEach { session ->
                mapHealth["HeartRateVariabilityRmssdRecord"] = Gson().toJson(session)
            }
        }
    }

    /*
    * 从health connect中读取呼吸率数据
    */
    private fun getRespiratoryRateRecord(client: HealthConnectClient) {
        lifecycleScope.launch {
            // Permissions already granted
            val lastDay = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS)
                .minusDays(0)
                .withHour(12)
            val firstDay = lastDay
                .minusDays(1)
            val respiratoryRateSessionRequest = ReadRecordsRequest(
                recordType = RespiratoryRateRecord::class,
                timeRangeFilter = TimeRangeFilter.between(
                    firstDay.toInstant(),
                    lastDay.toInstant()
                ),
                ascendingOrder = false,
                pageSize = 1
            )
            mapHealth["RespiratoryRateRecord"] = ""
            val respiratoryRateSessions = client.readRecords(respiratoryRateSessionRequest)
            Log.i("12345678", ": RespiratoryRateSessions长度:" + respiratoryRateSessions.records.size)
            respiratoryRateSessions.records.forEach { session ->
                mapHealth["RespiratoryRateRecord"] = Gson().toJson(session)
            }
        }
    }

    /**
     * 从health connect中读取卡路里炼数据
     */
    suspend fun getCaloryInfo(client: HealthConnectClient) {
        lifecycleScope.launch {
            val formatter = DateTimeFormatter.ofPattern("eee, d LLL")
            // Permissions already granted
            val lastDay = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS)
                .minusDays(0)
                .withHour(12)
            val firstDay = lastDay
                .minusDays(1)
            val recordRequest = ReadRecordsRequest(
                recordType = TotalCaloriesBurnedRecord::class,
                timeRangeFilter = TimeRangeFilter.between(
                    firstDay.toInstant(),
                    lastDay.toInstant()
                ),
                ascendingOrder = false,
                pageSize = 1
            )
            val sessions = client.readRecords(recordRequest)
            mapHealth["ActiveCaloriesBurnedRecord"] = ""
            if (sessions.records.isNotEmpty()) {
                Log.e("12345678", "getCaloryInfo: not empty")
                sessions.records.forEach {
//                    Log.e("12345678", "getCaloryInfo: " + it.energy.inKilocalories)
                    mapHealth["ActiveCaloriesBurnedRecord"] = Gson().toJson(it)
                }
            } else {
                Log.e("12345678", "getCaloryInfo: empty")
            }

        }
    }

    /**
     * 从health connect中读取锻炼数据
     */
    suspend fun getExerciseRecord(client: HealthConnectClient) {
        val formatter = DateTimeFormatter.ofPattern("eee, d LLL")
        val lastDay = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS)
            .minusDays(0)
            .withHour(12)
        val firstDay = lastDay
            .minusDays(1)
        val sessionRequest = ReadRecordsRequest(
            recordType = ExerciseSessionRecord::class,
            timeRangeFilter = TimeRangeFilter.between(
                firstDay.toInstant(),
                lastDay.toInstant()
            ),
            ascendingOrder = false,
            pageSize = 1
        )
        mapHealth["ExerciseSessionRecord"] = ""
        val sessions = client.readRecords(sessionRequest)
        if (sessions.records.isNotEmpty()) {
            val healthConnectManager = HealthConnectManager(applicationContext)
            exerciseSessionsList.value =
                healthConnectManager.readExerciseSessions(firstDay.toInstant(), lastDay.toInstant())
            exerciseSessionsList.value.forEach {
                mapHealth["ExerciseSessionRecord"] = Gson().toJson(it)
            }
        } else {
            Log.e("12345678", "getExerciseRecord:empty ")
        }
    }

    /**
     * 向health connect中写入心率数据
     */
    suspend fun writeHeartRateToConnect(client: HealthConnectClient) {
        val startHour = DateUtil.getListIntDateByYYYYMMDDHHMM(
            AppCache.instance.getData(Constants.SleepTime, "2022-09-13 22:47:00")
                .toString()
        )[0]
        val startMinute = DateUtil.getListIntDateByYYYYMMDDHHMM(
            AppCache.instance.getData(Constants.SleepTime, "2022-09-13 22:47:00")
                .toString()
        )[1]

        val endHour = DateUtil.getListIntDateByYYYYMMDDHHMM(
            AppCache.instance.getData(Constants.wakeTime, "2022-09-14 05:29:00")
                .toString()
        )[0]
        val endMinute = DateUtil.getListIntDateByYYYYMMDDHHMM(
            AppCache.instance.getData(Constants.wakeTime, "2022-09-14 05:29:00")
                .toString()
        )[1]
        val lastDay = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS)
            .minusDays(0)
            .withHour(endHour)
            .withMinute(endMinute)
        val firstDay = lastDay
            .minusDays(1)
            .withHour(startHour)
            .withMinute(startMinute)


        val healthConnectManager = HealthConnectManager(applicationContext)
        val heartRateRecord = HeartRateRecord(
            firstDay.toInstant(), ZoneOffset.UTC, lastDay.toInstant(),
            ZoneOffset.UTC,
            generateHeartSample(startTime = firstDay.toInstant(), endTime = lastDay.toInstant())
        )
        healthConnectManager.writeHeartRateRecord(heartRateRecord)
    }

    /**
     * 向health connect中写入rmssd数据
     */
    suspend fun writeRmssdToConnect(client: HealthConnectClient) {
        val startHour = DateUtil.getListIntDateByYYYYMMDDHHMM(
            AppCache.instance.getData(Constants.SleepTime, "2022-09-13 22:47:00")
                .toString()
        )[0]
        val startMinute = DateUtil.getListIntDateByYYYYMMDDHHMM(
            AppCache.instance.getData(Constants.SleepTime, "2022-09-13 22:47:00")
                .toString()
        )[1]

        val firstDay = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS)
            .minusDays(1)
            .withHour(startHour)
            .withMinute(startMinute)

        val healthConnectManager = HealthConnectManager(applicationContext)
        val heartRateVariabilityRmssdRecord = HeartRateVariabilityRmssdRecord(
            AppCache.instance.getData(Constants.HeartRateVariabilityRmssdRecord, 37.00) as Double,
            firstDay.toInstant(),
            firstDay.offset
        )
        healthConnectManager.writeHeartRateVariabilityRmssdRecord(heartRateVariabilityRmssdRecord)
    }

    suspend fun writeRespiratoryToConnect(client: HealthConnectClient) {
        val startHour = DateUtil.getListIntDateByYYYYMMDDHHMM(
            AppCache.instance.getData(Constants.SleepTime, "2022-09-13 22:47:00")
                .toString()
        )[0]
        val startMinute = DateUtil.getListIntDateByYYYYMMDDHHMM(
            AppCache.instance.getData(Constants.SleepTime, "2022-09-13 22:47:00")
                .toString()
        )[1]

        val firstDay = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS)
            .minusDays(1)
            .withHour(startHour)
            .withMinute(startMinute)

        val healthConnectManager = HealthConnectManager(applicationContext)
        val respiratoryRateRecord = RespiratoryRateRecord(
            AppCache.instance.getData(Constants.RespiratoryRateRecord, 15.00) as Double,
            firstDay.toInstant(),
            firstDay.offset
        )
        healthConnectManager.writeRespiratoryRateRecord(respiratoryRateRecord)
        AppCache.instance.clearHealthInfoCache()
    }

    /**
     * 根据后端返回的心率数据生成HeartRateRecord.Sample数组
     * 每组间隔5分钟
     */
    private fun generateHeartSample(
        startTime: Instant,
        endTime: Instant
    ): List<HeartRateRecord.Sample> {
        val samples = mutableListOf<HeartRateRecord.Sample>()
        val heartRates =
            HexDump.StringToIntArray(
                AppCache.instance.getData(
                    Constants.HeartStageRecord,
                    "[59,58,60]"
                ) as String?
            )
        var i: Int = 0
        heartRates.forEach {
            samples.add(
                HeartRateRecord.Sample(
                    startTime.plusSeconds((300 * i).toLong()),
                    it.toLong()
                )
            )
            i += 1
        }

        return samples
    }

    /**
     * 向health connect写入后台返回的睡眠数据
     */
    private suspend fun writeSleepDataToConnect() {
        val healthConnectManager = HealthConnectManager(applicationContext)
        healthConnectManager.generateSleepData()
    }

    /**
     * 从health connect读取营养信息
     */
    private suspend fun getNutritionRecord(client: HealthConnectClient) {
        val lastDay = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS)
            .minusDays(0)
            .withHour(12)
        val firstDay = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS)
            .minusDays(1)
        val sessionRequest = ReadRecordsRequest(
            recordType = NutritionRecord::class,
            timeRangeFilter = TimeRangeFilter.between(
                firstDay.toInstant(),
                lastDay.toInstant()
            ),
            ascendingOrder = false,
            pageSize = 1
        )
        mapNutrition["NutritionRecord"] = ""
        val sessions = client.readRecords(sessionRequest)
        if (sessions.records.isNotEmpty()) {
            val healthConnectManager = HealthConnectManager(applicationContext)
            nutritionSessionsList.value =
                healthConnectManager.getNutrition(firstDay.toInstant(), lastDay.toInstant())
            nutritionSessionsList.value.forEach {
                mapNutrition["NutritionRecord"] = Gson().toJson(it)
            }
        } else {
            Log.e("12345678", "getExerciseRecord:empty ")
        }
    }
}

fun dateTimeWithOffsetOrDefault(time: Instant, offset: ZoneOffset?): ZonedDateTime =
    if (offset != null) {
        ZonedDateTime.ofInstant(time, offset)
    } else {
        ZonedDateTime.ofInstant(time, ZoneId.systemDefault())
    }

health connect的读写操作就是以上这些了,但是还没完,对于华为的鸿蒙系统health connect不够兼容,直接读取会导致程序闪退,我对此也做了个适配。

四、Health Connect适配鸿蒙

经过本人的测试,发现如果鸿蒙系统读写数据时,没将Health Connect 软件挂在后台会导致整个程序的闪退,因此在我们读取时要先拉起Health Connect软件。

代码(PS:最好在拉起软件之后加一个时延,避免导致软件还未拉起时读写数据导致闪退):

 /**检测安装包是否已安装
     * packageName填入需要检测软件的包名即可
     * IFTTT软件包名为com.google.android.apps.healthdata
     */
    @SuppressLint("QueryPermissionsNeeded")
    fun installAppList(context: Context, packageName: String): Boolean {
        val installedPackageList = context.packageManager.getInstalledPackages(0)
        installedPackageList.forEach {
            if (packageName == it.packageName)
                return true
        }
        return false
    }

    /**
     * 启动第三方apk
     * 直接打开  每次都会启动到启动界面,每次都会干掉之前的,从新启动
     * XXXXX : 包名
     */
    /**
     * 启动第三方apk
     *
     * 如果已经启动apk,则直接将apk从后台调到前台运行(类似home键之后再点击apk图标启动),如果未启动apk,则重新启动
     */
    fun launchAPK3(context: Context, packageName: String?) {
        val intent =
            getAppOpenIntentByPackageName(context, packageName)
        context.startActivity(intent)
    }

    fun getAppOpenIntentByPackageName(context: Context, packageName: String?): Intent? {
        var mainAct: String? = null
        val pkgMag = context.packageManager
        val intent = Intent(Intent.ACTION_MAIN)
        intent.addCategory(Intent.CATEGORY_LAUNCHER)
        intent.flags = Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED or Intent.FLAG_ACTIVITY_NEW_TASK
        @SuppressLint("WrongConstant") val list: List<ResolveInfo> = pkgMag.queryIntentActivities(
            intent,
            PackageManager.GET_ACTIVITIES
        )
        for (i in list.indices) {
            val info: ResolveInfo = list[i]
            if (info.activityInfo.packageName.equals(packageName)) {
                mainAct = info.activityInfo.name
                break
            }
        }
        if (TextUtils.isEmpty(mainAct)) {
            return null
        }
        intent.component = ComponentName(packageName!!, mainAct!!)
        return intent
    }
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值