一、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
}