Доставка фона в Healthkit, когда приложение не работает
Может ли доставка по службе HealthKit запускать приложение, если оно не запущено? В частности, в конечном состоянии?
Ответы
Ответ 1
После полного дня тестирования (iOS 9.2) я могу подтвердить, что HealthKit
доставка фона РАБОТАЕТ во всех следующих состояниях приложения:
- фон (в фоновом режиме и исполняемый код),
- приостановлено (в фоновом режиме, но не выполняется код),
- завершено (принудительно убит пользователем или очищается системой).
Имейте в виду: часть 1
Некоторые типы данных HealthKit
имеют минимальную частоту обновления HKUpdateFrequencyHourly
. Тем не менее, даже если вы настроили доставку фона с частотой HKUpdateFrequencyImmediate
, вы не будете получать обновления чаще, чем каждый час или около того.
К сожалению, в документации о минимальных частотах на типы данных нет информации, но мой опыт работы с Fitness types
был следующим:
- Активная энергия: ежечасно,
- Расстояние на велосипеде: немедленное,
- Полеты Climbed: немедленный,
- NikeFuel: немедленный,
- Шаги: ежечасно,
- Ходьба + Запуск: почасовой,
- Тренировки: немедленный.
Примечание: immediate
НЕ означает в реальном времени, а скорее "некоторое время спустя", когда образцы данных активности были записаны в базу данных/хранилище HealthKit
.
Имейте в виду: часть 2
Если устройство заблокировано с кодом доступа, никто из вас не будет наблюдать наблюдателей за отправкой. Это преднамеренно из-за проблем с конфиденциальностью (подробнее: https://developer.apple.com/library/ios/documentation/HealthKit/Reference/HealthKit_Framework/).
Тем не менее, как только пользователь разблокирует устройство, будут вызваны ваши наблюдатели доставки HealthKit
(если, конечно, прошло минимальное частотное время).
Пример кода:
Взгляните на ответ Виктора Сиглера. Хотя, вы можете пропустить все три шага с самого начала своего ответа, поскольку они не требуются и не нужны для доставки HealthKit
для работы.
Ответ 2
Этот ответ немного поздний, но я надеюсь, что это поможет людям понять, как успешно работать с HKObserverQuery
.
Прежде всего HKObserverQuery
отлично работает в фоновом режиме и когда приложение закрыто на всех. Но сначала вам нужно установить некоторые параметры, чтобы все было нормально.
- Вам нужно установить Режимы фона в возможностях вашего приложения. См. Рисунок ниже:
![enter image description here]()
- Затем вам нужно добавить
Required Background Modes
в свой info.plist
, как на следующем рисунке:
![enter image description here]()
-
Вам нужно установить Background Fetch
следующим образом:
3,1. В меню панели "Схемы" выберите iOS Simulator или Device.
3,2. В том же меню выберите "Редактировать схему".
3.3. В левом столбце выберите "Выполнить".
3.4. Перейдите на вкладку "Параметры".
3,5. Установите флажок "Фоновая выборка" и нажмите "Закрыть".
![enter image description here]()
Затем вы можете получать уведомления, когда приложение находится в фоновом режиме или закрыто, используя следующий код:
import UIKit
import HealthKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let healthKitStore:HKHealthStore = HKHealthStore()
func startObservingHeightChanges() {
let sampleType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeight)
var query: HKObserverQuery = HKObserverQuery(sampleType: sampleType, predicate: nil, updateHandler: self.heightChangedHandler)
healthKitStore.executeQuery(query)
healthKitStore.enableBackgroundDeliveryForType(sampleType, frequency: .Immediate, withCompletion: {(succeeded: Bool, error: NSError!) in
if succeeded{
println("Enabled background delivery of weight changes")
} else {
if let theError = error{
print("Failed to enable background delivery of weight changes. ")
println("Error = \(theError)")
}
}
})
}
func heightChangedHandler(query: HKObserverQuery!, completionHandler: HKObserverQueryCompletionHandler!, error: NSError!) {
// Here you need to call a function to query the height change
// Send the notification to the user
var notification = UILocalNotification()
notification.alertBody = "Changed height in Health App"
notification.alertAction = "open"
notification.soundName = UILocalNotificationDefaultSoundName
UIApplication.sharedApplication().scheduleLocalNotification(notification)
completionHandler()
}
func authorizeHealthKit(completion: ((success:Bool, error:NSError!) -> Void)!) {
// 1. Set the types you want to read from HK Store
let healthKitTypesToRead = [
HKObjectType.characteristicTypeForIdentifier(HKCharacteristicTypeIdentifierDateOfBirth),
HKObjectType.characteristicTypeForIdentifier(HKCharacteristicTypeIdentifierBloodType),
HKObjectType.characteristicTypeForIdentifier(HKCharacteristicTypeIdentifierBiologicalSex),
HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBodyMass),
HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeight),
HKObjectType.workoutType()
]
// 2. Set the types you want to write to HK Store
let healthKitTypesToWrite = [
HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBodyMassIndex),
HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierActiveEnergyBurned),
HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceWalkingRunning),
HKQuantityType.workoutType()
]
// 3. If the store is not available (for instance, iPad) return an error and don't go on.
if !HKHealthStore.isHealthDataAvailable() {
let error = NSError(domain: "any.domain.com", code: 2, userInfo: [NSLocalizedDescriptionKey:"HealthKit is not available in this Device"])
if( completion != nil ) {
completion(success:false, error:error)
}
return;
}
// 4. Request HealthKit authorization
healthKitStore.requestAuthorizationToShareTypes(Set(healthKitTypesToWrite), readTypes: Set(healthKitTypesToRead)) { (success, error) -> Void in
if( completion != nil ) {
dispatch_async(dispatch_get_main_queue(), self.startObservingHeightChanges)
completion(success:success,error:error)
}
}
}
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: .Alert | .Badge | .Sound, categories: nil))
self.authorizeHealthKit { (authorized, error) -> Void in
if authorized {
println("HealthKit authorization received.")
}
else {
println("HealthKit authorization denied!")
if error != nil {
println("\(error)")
}
}
}
return true
}
//Rest of the defaults methods of AppDelegate.swift
}
В приведенном выше методе HKObserver
активируется, если авторизация HealthKit предоставляется пользователем и затем активирует уведомления.
Надеюсь, это поможет вам.
Ответ 3
В iOS 8.1 это делается. Однако вам нужно убедиться, что вы обновили свои запросы наблюдателей в делетете приложения application:didFinishLaunchingWithOptions:
. Исправлена ошибка 8.0, из-за которой вообще не удалось получить фоновое уведомление HealthKit.
EDIT:
В AppDelegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//create/get your HKHealthStore instance (called healthStore here)
//get permission to read the data types you need.
//define type, frequency, and predicate (called type, frequency, and predicate here, appropriately)
UIBackgroundTaskIdentifier __block taskID = [application beginBackgroundTaskWithExpirationHandler:^{
if (taskID != UIBackgroundTaskInvalid) {
[application endBackgroundTask:taskID];
taskID = UIBackgroundTaskInvalid;
}
}];
[healthStore enableBackgroundDeliveryForType:type frequency:frequency withCompletion:^(BOOL success, NSError *error) {}];
HKQuery *query = [[HKObserverQuery alloc] initWithSampleType:healthType predicate:predicate updateHandler:
^void(HKObserverQuery *query, HKObserverQueryCompletionHandler completionHandler, NSError *error)
{
//If we don't call the completion handler right away, Apple gets mad. They'll try sending us the same notification here 3 times on a back-off algorithm. The preferred method is we just call the completion handler. Makes me wonder why they even HAVE a completionHandler if we're expected to just call it right away...
if (completionHandler) {
completionHandler();
}
//HANDLE DATA HERE
if (taskID != UIBackgroundTaskInvalid) {
[application endBackgroundTask:taskID];
taskID = UIBackgroundTaskInvalid;
}
}];
[healthStore executeQuery:query];
}