使用WCSession的sendMessage无法唤醒后台/关闭状态的iPhone App
Apple Watch唤醒后台iPhone应用失败问题排查与解决
问题描述
我正在开发一款Apple Watch应用,核心需求是唤醒iPhone端配套应用,让它通过WKWebView加载网页、截取快照后回传至手表。目前双端前台时功能完全正常,但iPhone后台运行时效果不稳定,完全关闭后直接无法运行。
我尝试用WCSession的sendMessage唤醒后台iPhone应用,查资料说这个功能是支持的,但一直没实现。不确定是因为iPhone应用没回复手表的初始消息(要等WKWebView加载完成才能回传文件,没法在replyHandler里即时回复),还是漏了plist配置?
当前代码流程:
- Apple Watch用户点击按钮,触发ExtensionDelegate中已激活的WCSession调用
sendMessage - iPhone端通过AppDelegate中激活的WCSession接收消息
- 在
didReceiveMessage方法里,iPhone把URL传入WKWebView并开始加载 - WKWebView的
didFinish触发后,截取网页快照,通过transferFile回传手表 - 手表接收快照后传递到对应ViewController
相关代码
Watch端ExtensionDelegate发送消息
@objc func transmit(_ notification: Notification) { // The paired iPhone has to be connected via Bluetooth. if let session = session, session.isReachable { session.sendMessage(["SWTransmission": notification.userInfo as Any], replyHandler: { replyData in // handle reply from iPhone app here print(replyData) }, errorHandler: { error in // catch any errors here print(error) }) } else { // when the iPhone is not connected via Bluetooth } }
iPhone端激活WCSession
fileprivate let session: WCSession? = WCSession.isSupported() ? WCSession.default : nil func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. session?.delegate = self session?.activate() webView.navigationDelegate = self webView.scrollView.contentInsetAdjustmentBehavior = .never return true }
iPhone端接收消息并加载网页
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) { DispatchQueue.main.async { [self] in let dictionary = message["SWTransmission"] as! [String: Any] let link = URL(string: dictionary["URL"] as! String)! let request = URLRequest(url: link) webView.frame = CGRect(x: 0, y: 0, width: Int(((dictionary["width"] as! Double) * 1.5)), height: dictionary["height"] as! Int) webView.load(request) } }
iPhone端截取快照并回传
func getDocumentsDirectory() -> URL { let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) return paths[0] } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { webView.takeSnapshot(with: nil) { [self] (image, error) in let filename = getDocumentsDirectory().appendingPathComponent("webImage.jpg") if let data = image!.jpegData(compressionQuality: 0.8) { try? data.write(to: filename) } self.session?.transferFile(filename, metadata: nil) } }
Watch端接收快照
func session(_ session: WCSession, didReceive file: WCSessionFile) { DispatchQueue.main.async { [self] in do { NotificationCenter.default.post(name: NSNotification.Name("openSite"), object: nil, userInfo: ["imageURL": file.fileURL] as [String: Any]) } catch { print(error) } } }
问题根源与修复方案
1. 核心问题:未调用replyHandler导致唤醒失败
WCSession的sendMessage要求接收方必须在didReceiveMessage中调用replyHandler,哪怕只是回复空字典。如果不调用,系统会认为消息处理失败,后台唤醒的iPhone应用会被立刻挂起,后续WKWebView的加载和快照流程根本无法完成。
修复代码:
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) { // 先回复手表,确保系统不挂起应用 replyHandler(["status": "received"]) DispatchQueue.main.async { [self] in guard let dictionary = message["SWTransmission"] as? [String: Any], let urlString = dictionary["URL"] as? String, let link = URL(string: urlString), let width = dictionary["width"] as? Double, let height = dictionary["height"] as? Int else { return } let request = URLRequest(url: link) webView.frame = CGRect(x: 0, y: 0, width: Int(width * 1.5), height: height) webView.load(request) } }
2. 添加后台任务延长活跃时间
后台状态下系统会限制应用活跃时长,需要启动后台任务来保证WKWebView加载和快照完成:
var backgroundTask: UIBackgroundTaskIdentifier = .invalid func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) { replyHandler(["status": "received"]) // 启动后台任务 backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "WebSnapshotTask") { // 任务超时,结束后台任务 UIApplication.shared.endBackgroundTask(self.backgroundTask) self.backgroundTask = .invalid } DispatchQueue.main.async { [self] in // 原有加载WKWebView的代码 } } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { webView.takeSnapshot(with: nil) { [self] (image, error) in // 原有保存快照和transferFile的代码 // 结束后台任务 if backgroundTask != .invalid { UIApplication.shared.endBackgroundTask(backgroundTask) backgroundTask = .invalid } } }
3. 配置Info.plist
添加以下键值对,确保后台加载权限:
- 键:
UIBackgroundModes,类型:Array,添加字符串项:fetch - 键:
WKSupportsIndirectLoading,类型:Boolean,值:YES
4. 优化WCSession激活逻辑
确保iPhone端在应用激活时重新激活会话,避免会话失效:
func applicationDidBecomeActive(_ application: UIApplication) { session?.activate() }
额外注意事项
- 避免原代码中的强制解包(
as!),用可选绑定处理异常,防止应用崩溃 - 测试后台唤醒时,确保iPhone处于锁屏/后台状态,且与Watch保持蓝牙连接
transferFile为异步传输,手表端需正确处理文件URL的权限问题
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

