WKWatchConnectivityRefreshBackgroundTask 永远不会在后台触发,但是 WKSnapshotRefreshBackgroundTask

WKWatchConnectivityRefreshBackgroundTask is never triggered in background, but WKSnapshotRefreshBackgroundTask

我想使用 session.updateApplicationContext(applicationContext).

从 iPhone 在后台更新我的手表应用程序状态

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

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

Apple’s docs 说“当您从配对的 iPhone 接收到后台数据时,系统会在后台启动您的应用程序,实例化一个 WKWatchConnectivityRefreshBackgroundTask 对象,然后将任务对象传递给扩展委托的 handleBackgroundTasks: 方法。“。

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

编辑:

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

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 语句的行中设置了一个 breakponint 并执行了应用程序。
当手表模拟器上的主页按钮被按下时,断点到达 WKSnapshotRefreshBackgroundTask。这没关系(见上文)。
但是,如果在 iPhone 模拟器上选择了不同的线路,watchOS 不会按预期安排 WKWatchConnectivityRefreshBackgroundTask。毕竟这个demo工程应该demo的正是这一点。
也许有人可以尝试演示项目并确认这个问题。

怎么了?

所有遇到同样问题的人:
我将问题提交给 Apple 开发人员技术支持,他们确认 (# 652471299) watchOS 3 中的问题,并建议提交错误报告,我所做的 (# 29284559)。
所以,只能等苹果修复bug了。

更新:

他们回答了我的错误报告,仅仅 2 天后:

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

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

感谢您的快速回答。但是,无论如何还是有问题: 函数

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 或后台,select iPhone 上的不同代码。 应用不会崩溃,这证明没有WKWatchConnectivityRefreshBackgroundTask被安排。
请在没有 Xcode 控件的情况下进行此测试。只需在 iPhone 和观看设备上 运行 即可。

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

也许我错了,有人可以告诉我如何正确地做。

更新我的回答


结论先行

目前 WKWatchConnectivityRefreshBackgroundTask 仅当 watchOS 扩展的 WCSession 处于 notActivated 状态且扩展未 运行 处于前台时才会在 watchOS 模拟器上被调用(在后台或终止)。

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

WCSession 核心

对于WCSession,当为activated时,可以传递userInfo,对方激活时,可以获取userInfo。对方不需要在前台被激活,它可以在高优先级后台。

测试结果

这是我的测试结果。

如何制作WCSessionnotActivated?

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

-------------下面是旧的post,不想看的可以忽略。------------ --------

理论

请先看图再解释

由于watchOS的历史,有WCSessionDelegate接收函数(watchOS 2.0开始)和WKExtensionDelegate.handle(_:)函数(watchOS 3.0开始)。

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

WKExtensionDelegate.handle(_:) 确实在后台工作。但是,WKExtensionDelegate.handle(_:) 是可选的,但建议使用 well-prepared 如果您使用 Xcode。

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

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

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


如何证明?

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

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

在你的 watchOS 应用程序中,在 interface.storyboard 中添加一个标签,并将其作为 @IBOutlet var label: WKInterfaceLabel! 拖到 viewController,并实现 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(_:)运行s,我们将total加1。如果func session(WCSession, didReceiveUserInfo: [String : Any] = [:])运行s,我们将total加4 .

调试

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

  1. 运行 项目。
  2. 当 watchOS 应用程序显示时,手动打开 iOS 应用程序。
  3. 单击了 iOS 应用中的按钮。可以看到 watchOS 中的 label 变化了 4.
  4. 在Xcode中,点击product->stop(或cmd+.)。 watchOS 应用程序将消失。
  5. 点击一次或多次 iOS 应用程序的按钮。然后手动打开 watchOS 应用程序。这次您会看到 label 的变化乘以 1 乘以您的点击次数。
  6. 当 watchOS 应用程序处于前台时,步长将再次变为 4。