[关闭]
@wrlqwe 2016-12-11T15:24:16.000000Z 字数 5478 阅读 1431

HealthKit摸索

iOS


HealthKit 设计用作 APP 间共享用户的健康数据,它约定了数据类型和单位,以便所有 APP 都能理解。

关于数据

HealthKit 会将数据加密后保存在 iPhone 设备的 HealthStore 里,这些数据包括以下几类:

关于 HealthKit class 结构设计

HKObject

HKObject 是 HealthKit 所有类的父类,包含这些属性:

  1. UUID 每个对象的唯一标示符。
  2. SourceRevision 数据的来源。来源可以是直接把数据存进HealthKit的设备,或者是应用。当一个对象保存进HealthKit中时,HealthKit会自动设置其来源。只有从HealthKit中获取的数据source属性才可用。
  3. Device 代表了创建数据的设备。
  4. Metadata 一个包含关于该对象额外信息的字典。元数据包含预定义和自定义的键。预定义的键用来帮助在应用间共享数据。自定义的键用来扩展HealthKit对象类型,为对象添加针对应用的数据。

HKSample

所有样本都继承自 HKSample ,它定义了以下属性

  1. sampleType 代表着Sample的类型,比如睡眠,计步等。
  2. startDate 起始时间
  3. endDate 结束时间

每个具体样本都以子类方式使用,它们包括以下几类:

  1. HKCategorySample 分类样本,包括睡眠统计、站立统计等
  2. HKQuantitySample 数量样本,包括身高、体重、步数等,绝大多数样本都定义在这里
  3. HKDocumentSample 文档样本,目前只有 HKCDADocumentSample 一个实现
  4. HKCorrelation 复合数据,包含一个或多个样本。
  5. HKWorkout 锻炼活动,像跑步、游泳,甚至游戏。Workout 通常有类型、时长、距离、和消耗能量这些属性。你还可以为一个 workout 关联许多详细的样本。不像 correlation ,这些样本是不包含在 workout 里的。但是,它们可以通过 workout 获取到。

classes

HKObjectType

每个 HKObject 都对应一个 HKObjectType, HKObjectType 有跟 HKObject 相似的类层次设计,在查询和构造 HKObject 时都要指定 HKObjectType, 所有 HKObjectType 的子类实例都由 HKObjectType 的 class 方法创建。

HK**TypeIdentifier

HKObjectType 的实例都由 HKObjectType + 对应的 Identifier 来创建,
HKSample 与他们的 HKObjectType、HKTypeIdentifier、Sample 的单位,都有严格的对应关系

集成

配置


  1. 项目配置里 Capabilities 开启 HealthKit。
  2. 为 Info.plist 添加 NSHealthShareUsageDescription 和 NSHealthUpdateUsageDescription 两项配置,它们分别会作为系统 Health 应用读写数据权限的描述信息。

    NSHealthShareUsageDescription
    需要读取Health数据的权限来让应用支持XX功能
    NSHealthUpdateUsageDescription
    需要写入Health数据的权限来让应用支持XX功能
  3. 项目中使用 HealthKit,在合适的时机请求权限
  1. func askForPermit(_ callback: @escaping UIResponseCallback = { _ in }) {
  2. var shareSet = Set<HKSampleType>()
  3. shareSet.insert(HKObjectType.categoryType(forIdentifier: .sleepAnalysis)!)
  4. shareSet.insert(HKObjectType.quantityType(forIdentifier: .stepCount)!)
  5. shareSet.insert(HKObjectType.workoutType())
  6. var readSet: Set<HKObjectType> = shareSet
  7. readSet.insert(HKObjectType.activitySummaryType())
  8. readSet.insert(HKObjectType.characteristicType(forIdentifier: .biologicalSex)!)
  9. readSet.insert(HKObjectType.categoryType(forIdentifier: .appleStandHour)!)
  10. //检查是否所有权限都被允许
  11. let allPermited = readSet.map { healthStore.authorizationStatus(for: $0) == .sharingAuthorized }.reduce(true) { lastResult, newValue in
  12. return lastResult && newValue
  13. }
  14. if !allPermited {
  15. //请求许可
  16. healthStore.requestAuthorization(toShare: shareSet, read: readSet, completion: { (succeed, error) in
  17. print("____\(succeed)\n\(error)")
  18. })
  19. }
  20. }

写入数据

首先构建相应的 Sample,一般 Sample 都是用各自的子类,由它所允许的 HKObjectType 和 对应的值构建,通过 HKHealthStore 的 save 方法写入。

下面就记录了一个持续十分钟的小睡:

  1. func makeCategory(_ callback: @escaping UIResponseCallback = { _ in }) {
  2. //构造一个起止时间
  3. let dates = makeDateFrom(ago: 160, lasts: 10)
  4. let sample = HKCategorySample(type: HKObjectType.categoryType(forIdentifier: .sleepAnalysis)!, value: HKCategoryValueSleepAnalysis.asleep.rawValue,
  5. start: dates.startDate, end: dates.endDate)
  6. healthStore.save(sample) { complete, error in
  7. let succeed = complete ? "成功" : "失败"
  8. ui(callback, "\(#function)\n\(succeed)\n\(error)")
  9. }
  10. }

下面是一个游泳的 Workout,它记录了连续32分钟,包含两个运动片段和中间2分钟休息:

  1. func makeSwimmingWorkoutTest(_ callback: @escaping UIResponseCallback = { _ in }) {
  2. let intervalDates = makeDateFrom(ago: 66, lasts: 15)
  3. let dates = makeDateFrom(ago: 66, lasts: 32)
  4. let event0 = HKWorkoutEvent(type: .resume, date: intervalDates.startDate)
  5. let event1 = HKWorkoutEvent(type: .pause, date: intervalDates.endDate)
  6. let event2 = HKWorkoutEvent(type: .resume, date: Date(timeInterval: 60 * 2, since: intervalDates.endDate))
  7. let event3 = HKWorkoutEvent(type: .pause, date: dates.endDate)
  8. let workout = HKWorkout(activityType: .swimming, start: dates.startDate, end: dates.endDate, workoutEvents: [event0, event1, event2, event3], totalEnergyBurned: nil, totalDistance: HKQuantity(unit: HKUnit.meter(), doubleValue: 750), totalSwimmingStrokeCount: HKQuantity(unit: HKUnit.count(), doubleValue: 240), device: nil, metadata: nil)
  9. healthStore.save(workout) { complete, error in
  10. let succeed = complete ? "成功" : "失败"
  11. ui(callback, "_____\(succeed)\n\(error)")
  12. }
  13. }

其他种类的 Sample 也类似。

查询数据

HealthKit 提供了多种查询数据的方式,API里都以Query结尾,它们有的可以查询Sample, 有的可以查询概要信息,有的可以查询统计信息,等等。

下面是 HealthKit 提供的查询方式:

其中我认为比较重要的有 HKActivitySummaryQuery、HKSampleQuery 和 HKStatisticsQuery,它们分别对应三类查询请求,分别是概要信息、Sample 详情和统计信息。

概要信息按天统计,反映出用户每天的运动情况
Sample 详情则给开发者详细的信息,以便单独处理,merge 数据
统计信息则可以借由 HealthKit 的 merge 算法,直接返回处理后的数据

在开发中,统计信息应该是当下大多数应用统计步数的方式,下面是获取当天步数的方式:

  1. func queryTodayStepCountByStatisticsQuery(_ callback: @escaping UIResponseCallback = { _ in }) {
  2. let now = Date()
  3. let today = now.startOfThisDay()
  4. let predicate = HKQuery.predicateForSamples(withStart: today, end: now, options: [HKQueryOptions.strictStartDate])
  5. let query = HKStatisticsQuery(quantityType: HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)!, quantitySamplePredicate: predicate, options: [.cumulativeSum, .separateBySource]) { [unowned self] query, statistics, error in
  6. guard let statistics = statistics else {
  7. return
  8. }
  9. ui(callback, self.readStatistics(statistics))
  10. }
  11. healthStore.execute(query)
  12. }

其中 statistics 包含了每个 Source 的统计结果,开发者既可以直接使用 statistics.sumQuantity() 获取全部步数,也可以使用 statistics.sumQuantity(for: source) 取用一部分源的数据。
目前qq运动步数使用的就是前者,而微信则处理灵活些,笔者尝试作弊步数,一开始微信运动会计数,几分钟后刷新步数,则在微信上又恢复如初,应该是过滤掉了这个 source 的结果,但是具体算法还不确定。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注