当app在后台时,为什么HKAnchoredObjectQuery与enableBackgroundDeliveryForType总是会触发?

时间:2015-10-07 21:09:56

标签: ios health-kit

我尝试了一下以熟悉HKAnchoredObjectQuery并在我的应用处于非活动状态时获得结果。 我启动应用程序,切换到Apple Health,输入血糖结果;有时立即调用结果处理程序(由打印到控制台证明),但有时候处理程序不会被调用,直到我切换回我的应用程序。删除的结果以及添加的结果也是如此。有人有任何指导吗?

此代码的大部分内容来自thedigitalsean的问题,此处适用于在应用程序处于后台并登录到控制台时获取更新。请参阅:Healthkit HKAnchoredObjectQuery in iOS 9 not returning HKDeletedObject

class HKClient : NSObject {

  var isSharingEnabled: Bool = false
  let healthKitStore:HKHealthStore? = HKHealthStore()
  let glucoseType : HKObjectType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBloodGlucose)!

  override init(){
      super.init()
  }

  func requestGlucosePermissions(authorizationCompleted: (success: Bool, error: NSError?)->Void) {

      let dataTypesToRead : Set<HKObjectType> = [ glucoseType ]

      if(!HKHealthStore.isHealthDataAvailable())
      {
          // let error = NSError(domain: "com.test.healthkit", code: 2, userInfo: [NSLocalizedDescriptionKey: "Healthkit is not available on this device"])
          self.isSharingEnabled = false
          return
      }

      self.healthKitStore?.requestAuthorizationToShareTypes(nil, readTypes: dataTypesToRead){(success, error) -> Void in
          self.isSharingEnabled = true
          authorizationCompleted(success: success, error: error)
      }
  }

  func getGlucoseSinceAnchor(anchor:HKQueryAnchor?, maxResults:uint, callback: ((source: HKClient, added: [String]?, deleted: [String]?, newAnchor: HKQueryAnchor?, error: NSError?)->Void)!) {
      let queryEndDate = NSDate(timeIntervalSinceNow: NSTimeInterval(60.0 * 60.0 * 24))
      let queryStartDate = NSDate.distantPast()
      let sampleType: HKSampleType = glucoseType as! HKSampleType
      let predicate: NSPredicate = HKAnchoredObjectQuery.predicateForSamplesWithStartDate(queryStartDate, endDate: queryEndDate, options: HKQueryOptions.None)
      var hkAnchor: HKQueryAnchor

      if(anchor != nil){
          hkAnchor = anchor!
      } else {
          hkAnchor = HKQueryAnchor(fromValue: Int(HKAnchoredObjectQueryNoAnchor))
      }

      let onAnchorQueryResults : ((HKAnchoredObjectQuery, [HKSample]?, [HKDeletedObject]?, HKQueryAnchor?, NSError?) -> Void)! = {
          (query:HKAnchoredObjectQuery, addedObjects:[HKSample]?, deletedObjects:[HKDeletedObject]?, newAnchor:HKQueryAnchor?, nsError:NSError?) -> Void in

          var added = [String]()
          var deleted = [String]()

          if (addedObjects?.count > 0){
              for obj in addedObjects! {
                  let quant = obj as? HKQuantitySample
                  if(quant?.UUID.UUIDString != nil){
                      let val = Double( (quant?.quantity.doubleValueForUnit(HKUnit(fromString: "mg/dL")))! )
                      let msg : String = (quant?.UUID.UUIDString)! + " " + String(val)
                      added.append(msg)
                  }
              }
          }

          if (deletedObjects?.count > 0){
              for del in deletedObjects! {
                  let value : String = del.UUID.UUIDString
                  deleted.append(value)
              }
          }

          if(callback != nil){
              callback(source:self, added: added, deleted: deleted, newAnchor: newAnchor, error: nsError)
          }
      }

      // remove predicate to see deleted objects
      let anchoredQuery = HKAnchoredObjectQuery(type: sampleType, predicate: nil, anchor: hkAnchor, limit: Int(maxResults), resultsHandler: onAnchorQueryResults)

      // added - query should be always running
      anchoredQuery.updateHandler = onAnchorQueryResults

      // added - allow query to pickup updates when app is in backgroun
      healthKitStore?.enableBackgroundDeliveryForType(sampleType, frequency: .Immediate) {
          (success, error) in
          if (!success) {print("enable background error")}
          }

      healthKitStore?.executeQuery(anchoredQuery)
  }

  let AnchorKey = "HKClientAnchorKey"
  func getAnchor() -> HKQueryAnchor? {
      let encoded = NSUserDefaults.standardUserDefaults().dataForKey(AnchorKey)
      if(encoded == nil){
          return nil
      }
      let anchor = NSKeyedUnarchiver.unarchiveObjectWithData(encoded!) as? HKQueryAnchor
      return anchor
  }

  func saveAnchor(anchor : HKQueryAnchor) {
      let encoded = NSKeyedArchiver.archivedDataWithRootObject(anchor)
      NSUserDefaults.standardUserDefaults().setValue(encoded, forKey: AnchorKey)
      NSUserDefaults.standardUserDefaults().synchronize()
  }
}


class ViewController: UIViewController {
  let debugLabel = UILabel(frame: CGRect(x: 10,y: 20,width: 350,height: 600))

  override func viewDidLoad() {
      super.viewDidLoad()

      self.view = UIView();
      self.view.backgroundColor = UIColor.whiteColor()


      debugLabel.textAlignment = NSTextAlignment.Center
      debugLabel.textColor = UIColor.blackColor()
      debugLabel.lineBreakMode = NSLineBreakMode.ByWordWrapping
      debugLabel.numberOfLines = 0
      self.view.addSubview(debugLabel)

      let hk = HKClient()
      hk.requestGlucosePermissions(){
          (success, error) -> Void in

          if(success){
              let anchor = hk.getAnchor()

              hk.getGlucoseSinceAnchor(anchor, maxResults: 0)
                  { (source, added, deleted, newAnchor, error) -> Void in
                      var msg : String = String()

                      if(deleted?.count > 0){
                          msg += "Deleted: \n" + (deleted?[0])!
                          for s in deleted!{
                              msg += s + "\n"
                          }
                      }

                      if (added?.count > 0) {
                          msg += "Added: "
                          for s in added!{
                              msg += s + "\n"
                          }
                      }

                      if(error != nil) {
                          msg = "Error = " + (error?.description)!
                      }

                      if(msg.isEmpty)
                      {
                          msg = "No changes"
                      }
                      debugPrint(msg)

                      if(newAnchor != nil && newAnchor != anchor){
                          hk.saveAnchor(newAnchor!)
                      }

                      dispatch_async(dispatch_get_main_queue(), { () -> Void in
                          self.debugLabel.text = msg
                      })
              }
          }
      }
  }

  override func didReceiveMemoryWarning() {
      super.didReceiveMemoryWarning()
      // Dispose of any resources that can be recreated.
  }
}

我还在各种应用程序状态更改中添加了print()。控制台日志的一个示例(这是在XCode的iPhone 6s设备上运行)显示有时在我输入背景之后但在重新进入前景之前调用处理程序,有时在重新进入前景之后调用该处理程序。

app did become active
"No changes"
app will resign active
app did enter background
app will enter foreground
"Added: E0340084-6D9A-41E4-A9E4-F5780CD2EADA 99.0\n"
app did become active
app will resign active
app did enter background
"Added: CEBFB656-0652-4109-B994-92FAA45E6E55 98.0\n"
app will enter foreground
"Added: E2FA000A-D6D5-45FE-9015-9A3B9EB1672C 97.0\n"
app did become active
app will resign active
app did enter background
"Deleted: \nD3124A07-23A7-4571-93AB-5201F73A4111D3124A07-23A7-4571-93AB-5201F73A4111\n92244E18-941E-4514-853F-D890F4551D76\n"
app will enter foreground
app did become active
app will resign active
app did enter background
app will enter foreground
"Added: 083A9DE4-5EF6-4992-AB82-7CDDD1354C82 96.0\n"
app did become active
app will resign active
app did enter background
app will enter foreground
"Added: C7608F9E-BDCD-4CBC-8F32-94DF81306875 95.0\n"
app did become active
app will resign active
app did enter background
"Deleted: \n15D5DC92-B365-4BB1-A40C-B870A48A70A415D5DC92-B365-4BB1-A40C-B870A48A70A4\n"
"Deleted: \n17FB2A43-0828-4830-A229-7D7DDC6112DB17FB2A43-0828-4830-A229-7D7DDC6112DB\n"
"Deleted: \nCEBFB656-0652-4109-B994-92FAA45E6E55CEBFB656-0652-4109-B994-92FAA45E6E55\n"
app will enter foreground
"Deleted: \nE0340084-6D9A-41E4-A9E4-F5780CD2EADAE0340084-6D9A-41E4-A9E4-F5780CD2EADA\n"
app did become active

1 个答案:

答案 0 :(得分:3)

我建议使用HKObserverQuery并仔细设置。

有一种算法可以监视启用后台传递时如何以及何时调用HKObserverQuery的“完成”处理程序。不幸的是,这个细节很模糊。 Apple Dev论坛上的某个人称之为“3次罢工”规则,但Apple没有发布任何我可以找到的关于它的行为的文档。

https://forums.developer.apple.com/thread/13077

我注意到的一件事是,如果您的应用使用HKObserverQuery响应后台传递,创建HKAnchoredObjectQuery,并在该HKAnchoredObjectQuery中设置UpdateHandler,则此UpdateHandler通常会导致多次触发回调。我怀疑也许是因为这些额外的回调正在执行之后你已经告诉Apple你已经完成了你的工作以响应后台交付,你多次调用完成处理程序,也许他们会给你一些“积分”并打电话给你不太常见的是不良行为。

通过执行以下操作获得一致的回调,我获得了最大的成功:

  1. 使用ObserverQuery并确保调用“完成”处理程序一次并在工作结束时调用。
  2. 在后台运行时,我的HKAnchoredObjectQuery中没有设置更新处理程序(帮助实现1)。
  3. 专注于使我的查询处理程序,AppDelegate和ViewController尽可能快。我注意到当我将所有回调减少到只是一个print语句时,来自HealthKit的回调立即出现并且更加一致。所以说Apple肯定会关注执行时间。因此,尽可能静态地声明事物并关注速度。
  4. 我已经转移到我使用Xamarin.iOS的原始项目,而不是swift,所以我没有跟上我最初发布的代码。但是这里是该代码的更新(和未经测试)版本,应考虑这些更改(速度改进除外):

    <div ng-repeat="obj in items" ng-init="show = false">
        <div ng-click="show = !show"></div>
        <div ng-repeat="sub in obj.children" ng-show="show">
            <p>Hello World!</p>
        </div>
    </div>