Apple Watch Companion App:sendMessage 不适用于 quit iOS App

Apple Watch Companion App: sendMessage doesn't work with quit iOS App

我目前正在使用 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 应用程序,它就会起作用。

我的 AppDelegate.swift iOS 应用

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 w/o。

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

IIRC isReachable 通常从 watch 到对方 iPhone app,你应该考虑在会话激活时主动发送唤醒。