【问题标题】:Apple Watch Companion App: sendMessage doesn't work with quit iOS AppApple Watch Companion App:sendMessage 不适用于退出 iOS 应用程序
【发布时间】:2021-12-02 03:35:34
【问题描述】:

我目前正在使用 Swift 和 Flutter 构建 Apple Watch Companion App。我在theamorn's Github project 的帮助下做到了这一点。一切都在模拟器中运行(iOS 15.0 和 WatchOS 8.0),即使 iOS 应用程序被强制退出。但是,在我的 AW Series 3 (WatchOS 8.0) 和 iPhone 11 (iOS 15.0) 上进行测试时,只要打开 iOS 应用程序,它就可以工作。

iOS 应用

AppDelegate.swift
import UIKit
import Flutter
import WatchConnectivity

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    var session: WCSession?
    let methodChannelName: String = "app.controller.watch"
    
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
      initFlutterChannel()
      if WCSession.isSupported() {
          session = WCSession.default;
          session!.delegate = self;
          session!.activate();
      }
      
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
    
    private func initFlutterChannel() {
          if let controller = window?.rootViewController as? FlutterViewController {
            let channel = FlutterMethodChannel(
              name: methodChannelName,
              binaryMessenger: controller.binaryMessenger)
            
            channel.setMethodCallHandler({ [weak self] (
              call: FlutterMethodCall,
              result: @escaping FlutterResult) -> Void in
              switch call.method {
                case "flutterToWatch":
                   guard let watchSession = self?.session, watchSession.isPaired,
                      watchSession.isReachable, let methodData = call.arguments as? [String: Any],
                      let method = methodData["method"], let data = methodData["data"] as? Any else {
                      result(false)
                   return
                   }
                
                   let watchData: [String: Any] = ["method": method, "data": data]
                   watchSession.sendMessage(watchData, replyHandler: nil, errorHandler: nil)
                   result(true)
                default:
                   result(FlutterMethodNotImplemented)
                }
             })
           }
        }
}

extension AppDelegate: WCSessionDelegate {
    
    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
        
    }
    
    func sessionReachabilityDidChange(_ session: WCSession) {
        print("Watch reachability: \(session.isReachable)")
        if (session.isReachable) {
            //invoke sendWakeupToFlutter via MethodChannel when reachability is true
            DispatchQueue.main.async {
                if let controller = self.window?.rootViewController as? FlutterViewController {
                    let channel = FlutterMethodChannel(
                        name: self.methodChannelName,
                        binaryMessenger: controller.binaryMessenger)
                    channel.invokeMethod("sendWakeupToFlutter", arguments: [])
                }
            }
        }
    }
    
    
    func sessionDidBecomeInactive(_ session: WCSession) {
        
    }
    
    func sessionDidDeactivate(_ session: WCSession) {
        
    }
    
    func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
        DispatchQueue.main.async {
            if let method = message["method"] as? String, let controller = self.window?.rootViewController as? FlutterViewController {
                let channel = FlutterMethodChannel(
                    name: self.methodChannelName,
                    binaryMessenger: controller.binaryMessenger)
                channel.invokeMethod(method, arguments: message)
            }
        }
    }
}

我的 WatchViewModel.swift 在我的Watch Extension

import Foundation
import WatchConnectivity

class WatchViewModel: NSObject, ObservableObject {
    var session: WCSession
    var deviceList: String = ""
    @Published var loading: Bool = false
    @Published var pubDeviceList: [Device]?
    
    // Add more cases if you have more receive method
    enum WatchReceiveMethod: String {
        case sendLoadingStateToNative
        case sendSSEDeviceListToNative
    }
    
    // Add more cases if you have more sending method
    enum WatchSendMethod: String {
        case sendWakeupToFlutter
        case sendCloseToFlutter
    }
    
    init(session: WCSession = .default) {
        self.session = session
        super.init()
        self.session.delegate = self
        session.activate()
    }
    
    func sendDataMessage(for method: WatchSendMethod, data: [String: Any] = [:]) {
        sendMessage(for: method.rawValue, data: data)
    }
    
}

extension WatchViewModel: WCSessionDelegate {
    
    
    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
        
    }
    
    func sessionReachabilityDidChange(_ session: WCSession) {
        print("iPhone reachability: \(session.isReachable)")
        if(session.isReachable) {
            //invoke sendWakeupToFlutter via sendMessage when reachability is true
            sendDataMessage(for: .sendWakeupToFlutter)
        }
    }
    
    // Receive message From AppDelegate.swift that send from iOS devices
    func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
        DispatchQueue.main.sync {
            guard let method = message["method"] as? String, let enumMethod = WatchReceiveMethod(rawValue: method) else {
                return
            }
            
            switch enumMethod {
                case .sendLoadingStateToNative:
                    self.loading = (message["data"] as? Bool) ?? false
                case .sendSSEDeviceListToNative:
                    self.deviceList = (message["data"] as? String) ?? ""

                    let data = self.deviceList.data(using: .utf8)!
                    do {
                        self.pubDeviceList = try JSONDecoder().decode([Device].self, from: data)
                    } catch let error {
                        print(error)
                    }
            }
        }
    }
    
    func sendMessage(for method: String, data: [String: Any] = [:]) {
        guard session.isReachable else {
            print("ios not reachable")
            return
        }
        print("ios is reachable")
        let messageData: [String: Any] = ["method": method, "data": data]
        let callDepth = 10
        session.sendMessage(messageData, replyHandler: nil, errorHandler: nil)
    }
}

有人知道如何解决这个问题吗?提前致谢!

编辑: 到目前为止我的手表扩展的变化:

func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
        if (activationState == WCSessionActivationState.activated) {
            sendDataMessage(for: .sendWakeupToFlutter)
        }
    }

注意:接受的答案并没有完全解决我的问题,但改善了情况。我最终使用 Flutter MethodChannels 制作了一个相当独立的 Watch App。

【问题讨论】:

    标签: ios swift flutter apple-watch


    【解决方案1】:

    它可能无法解决您的问题,但您应该小心使用 session.isReachable - 它的值仅对 activated 的会话有效,它几乎可以肯定是(激活会话是异步的,但我认为很快),但是 WatchOS 有许多 API,其中只有在满足某些条件时才能信任该值,并且您应该检查,否则当实际值应该是“不知道”时,您最终会得到类似 true 或 false 的值

    IIRC isReachable 从手表到对应的 iPhone 应用程序通常为 true,您应该考虑在会话激活时主动发送唤醒。

    【讨论】:

    • 您好,感谢您的宝贵时间!我已经执行了两次sendMessage,因为我也在我的主 SwiftUI-View 的onAppear 中执行了它(在sessionReachabilityDidChange 中再次执行它只是为了修复错误而绝望的尝试?) - 或者有另一种主动的方法唤醒 iOS App 除了发送sendMessage?
    • 您也可以通过更新其应用程序上下文来唤醒 iOS 伴侣,但这只是另一种消息传递方式。 activationDidCompleteWith… 是尝试唤醒手机的地方,onAppear 可能太早了,需要检查activationState 值是否为.activated
    • 您好,我已经对其进行了测试,现在它可以在大约 3/5 的情况下使用。有趣的是,当我的手机被锁定时,它永远不会起作用。我目前正在查看iOSDeviceNeedsUnlockAfterRebootForReachability - 你经历过某事吗?相似的?谢谢你的帮助!我真的很感激?代码更改可以在原帖中看到。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-03
    • 2015-06-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多