4

我想从 iPhone 在后台更新我的手表应用程序状态,使用session.updateApplicationContext(applicationContext).

在手表上的应用程序处于活动状态时发送应用程序联系人确实可以正常工作。
当我激活手表上的主页按钮时,手表应用程序会进入后台,handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>)被调用并WKSnapshotRefreshBackgroundTask提供 a。

所以我不明白为什么 aWKSnapshotRefreshBackgroundTask被正确触发,而不是 a WKWatchConnectivityRefreshBackgroundTask

Apple 的文档说“<em>当你从配对的 iPhone 接收到后台数据时,系统会在后台启动你的应用程序,实例化一个WKWatchConnectivityRefreshBackgroundTask对象,并将任务对象传递给你的扩展委托的 handleBackgroundTasks: 方法。”。

但这不会发生,无论是在设备上还是在模拟器上。有什么问题?

编辑:

为了检查可能出了什么问题,我下载了 Apple 的演示项目“QuickSwitch”,可以在此处下载。这是应该处理后台任务的代码:

func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
    for backgroundTask in backgroundTasks {
        if let wcBackgroundTask = backgroundTask as? WKWatchConnectivityRefreshBackgroundTask {
            // store a reference to the task objects as we might have to wait to complete them
            self.wcBackgroundTasks.append(wcBackgroundTask)
        } else {
            // immediately complete all other task types as we have not added support for them
            backgroundTask.setTaskCompleted()
        }
    }
    completeAllTasksIfReady()
}

在那里,同样的事情发生了
我确实在 if 语句的行中设置了一个断点并执行了应用程序。
当手表模拟器上的主页按钮被按下时,断点到达WKSnapshotRefreshBackgroundTask。这没关系(见上文)。
但是,如果在 iPhone 模拟器上选择了不同的行,watchOS 不会WKWatchConnectivityRefreshBackgroundTask像预期的那样安排 a 。毕竟,这个演示项目应该正是演示了这一点。
也许有人可以尝试演示项目并确认这个问题。

怎么了?

4

2 回答 2

13

更新我的答案


先说结论

当前WKWatchConnectivityRefreshBackgroundTask仅在 watchOS 扩展WCSession程序处于notActivated状态且扩展程序未在前台运行(在后台或终止)时在 watchOS 模拟器上确定调用。

在真实设备中,它不会在我的测试中被调用。但苹果文档说它可能。因此,在 Apple 更改其文档之前,您不应该依赖它不会被调用。

WCSession 核心

WCSession 核心

对于WCSession,当为 时activated,可以传输用户信息,当对方处于活动状态时,可以获取用户信息。对方不需要在前台激活,它可以在高优先级的后台。

测试结果

这是我的测试结果。

在模拟器和设备中

如何制作WCSession notActivated

  1. 使用 Xcode 终止您的 watchOS 扩展。Xcode 将向您的 WKExtension 发送终止信号。
  2. 或者不要WCSession.activate()在 watchOS Extension 端的代码中运行。默认情况WCSession下。notActivated

-------------以下是旧帖,不想看的可以放心忽略。--------

理论

在此处输入图像描述

请先看图,我再解释。

由于watchOS的历史,既有WCSessionDelegate接收功能(从watchOS 2.0开始),也有WKExtensionDelegate.handle(_:)功能(从watchOS 3.0开始)。

尽管它们都声称是后台处理,但前者仅在您的应用程序处于前台时才立即起作用。如果您的应用程序不在前台(在后台或被终止),数据将排队,并在您的应用程序再次进入前台时立即执行。

WKExtensionDelegate.handle(_:)真的在后台工作。但是,WKExtensionDelegate.handle(_:)如果您使用 Xcode,建议使用它并做好充分准备,但它是可选的。

如果您不WKExtensionDelegate.handle(_:)通过评论来实施。您的应用程序以 watchOS 2.0 方式运行。

如果您实施WKExtensionDelegate.handle(_:)但您WCSession的 watchOS 应用程序中没有。结果很棘手。当您 watchOS 应用程序处于前台时,您将不会获得任何数据,因为您没有WCSession. 当您的应用程序处于后台时,它会在数据到来时被唤醒,但您无法获取数据,因为您没有会话。

如果您同时实现了它们,在大多数情况下,数据将根据您的 watchOS 应用程序的状态进行处理,并且永远不会排队。


如何证明?

创建一个新的 watchOS 项目。在iOS部分,添加一个按钮,每次点击按钮,发送一个userInfo到watchOS

session.transferUserInfo(["send test":""])

在你的 watchOS 应用中,在 中添加一个标签interface.storyboard,并将其拖到viewControlleras 中@IBOutlet var label: WKInterfaceLabel!,并同时实现WKExtensionDelegate.handle(_:)func session(WCSession, didReceiveUserInfo: [String : Any] = [:]) appDelegate

var total = 0

func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
    // Sent when the system needs to launch the application in the background to process tasks. Tasks arrive in a set, so loop through and process each one.
    for task in backgroundTasks {
        // Use a switch statement to check the task type
        switch task {
        case let backgroundTask as WKApplicationRefreshBackgroundTask:
            // Be sure to complete the background task once you’re done.
            backgroundTask.setTaskCompleted()
        case let snapshotTask as WKSnapshotRefreshBackgroundTask:
            // Snapshot tasks have a unique completion call, make sure to set your expiration date
            snapshotTask.setTaskCompleted(restoredDefaultState: true, estimatedSnapshotExpiration: Date.distantFuture, userInfo: nil)
        case let connectivityTask as WKWatchConnectivityRefreshBackgroundTask:
            // Be sure to complete the connectivity task once you’re done.
            total += 1
            DispatchQueue.main.async {
                if let viewController = WKExtension.shared().rootInterfaceController as? InterfaceController {
                    viewController.label.setText(String(self.total))
                }
            }

            connectivityTask.setTaskCompleted()
        case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
            // Be sure to complete the URL session task once you’re done.
            urlSessionTask.setTaskCompleted()
        default:
            // make sure to complete unhandled task types
            task.setTaskCompleted()
        }
    }
}

public func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
    total += 4
    DispatchQueue.main.async {
        if let viewController = WKExtension.shared().rootInterfaceController as? InterfaceController {
            viewController.label.setText(String(self.total))
        }
    }
}

如果WKExtensionDelegate.handle(_:)运行,我们加total1。如果func session(WCSession, didReceiveUserInfo: [String : Any] = [:])运行,我们加total4。

调试

在 Xcode 中,选择product->schemeasWatchKit app这样我们就可以在 Xcode 中终止 watchOS 应用程序。

  1. 运行项目。
  2. 当 watchOS 应用显示时,手动打开 iOS 应用。
  3. 单击iOS应用程序中的按钮。您可以看到labelwatchOS 中的 4 变化。
  4. 在 Xcode 中,单击product->stop(或 cmd+.)。watchOS 应用程序将消失。
  5. 在 iOS 应用程序的按钮上单击一次或多次。然后手动打开 watchOS 应用。这次您将看到label更改乘以 1 乘以您的点击次数。
  6. 当 watchOS 应用程序处于前台时,该步骤将再次为 4。
于 2017-01-28T22:50:33.343 回答
1

致所有有同样问题的人:
我将问题提交给 Apple 开发人员技术支持,他们确认 (#652471299) watchOS 3 中的问题,并建议提交错误报告,我做了什么 (#29284559)。
因此,必须等待Apple修复错误。

更新:

他们仅在 2 天后回复了我的错误报告:

好吧,我们遇到很多这样的问题,通常是对时间的误解或应用程序没有暂停,因为它正在调试或不在 Dock 中,因此它不会获得任意任务。在这种情况下,阅读上面的描述我猜用户在测试时正在通过 xcode 进行调试。两种任务类型:Watch Connectivity 和 URLSession 仅作为“启动”事件到达。调试时,xcode 会保持应用程序运行,因此它永远不会得到这些任务。测试这一点的最佳方法是断开与 xcode 的连接并进行测试,确保您的应用程序也在停靠栏中——只有停靠的应用程序才会获得自主任务。
如果您在尝试后发现这不起作用,我们将需要一个 sysdiagnose 才能更进一步。

我认为这种说法是错误的。我的回复是:

感谢你及时的答复。但是,无论如何都有问题:函数

handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>)

应该处理所有后台任务,包括WKWatchConnectivityRefreshBackgroundTask.

要检查这是否不是这种情况很容易:只需在安排此类后台任务时让应用程序崩溃,即在 Apple 的 QuickSwitch 演示项目中插入一条assert始终为的语句false

func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
    for backgroundTask in backgroundTasks {
        if let wcBackgroundTask = backgroundTask as? WKWatchConnectivityRefreshBackgroundTask {
            assert(false) // If the app comes here, it will crash
            // store a reference to the task objects as we might have to wait to complete them
            self.wcBackgroundTasks.append(wcBackgroundTask)
        } else {
            // immediately complete all other task types as we have not added support for them
            backgroundTask.setTaskCompleted()
        }
    }
    completeAllTasksIfReady()
}

然后在前台、dock 或后台运行应用程序,并在 iPhone 上选择不同的代码。 该应用程序不会崩溃,这证明没有WKWatchConnectivityRefreshBackgroundTask计划。
请在没有 Xcode 控制的情况下进行此测试。只需在 iPhone 和手表设备上运行它。

现在,1周后,我没有得到任何回复。

也许我错了,有人可以给我一个提示如何正确地做。

于 2016-11-16T11:38:55.220 回答