Вопрос или проблема
Я пишу родной код iOS в проекте Flutter. Приложение WatchOS работает в активном режиме и может активировать приложение iOS, но не может вызвать didReceiveMessage в приложении iOS.
Приложения iOS и Watch могут общаться друг с другом без каких-либо проблем, когда находятся в активном состоянии. Однако, когда мое приложение закрыто, даже если оно пробуждается, оно не может общаться. Есть идеи? Должен ли я использовать фоновую задачу на стороне приложения iOS?
ios
import UIKit
import Flutter
import WatchConnectivity
import UserNotifications
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
var session: WCSession?
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
if(WCSession.isSupported()) {
session = WCSession.default;
session!.delegate = self;
session!.activate();
}
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
func showLocalNotification(with message: String) {
let content = UNMutableNotificationContent()
content.title = "Новое сообщение"
content.body = message
content.sound = .default
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { (error) in
if let error = error {
print("Ошибка уведомления: \(error.localizedDescription)")
}
}
NSLog("Уведомление отправлено")
}
}
extension AppDelegate: WCSessionDelegate{
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
}
func sessionReachabilityDidChange(_ session: WCSession) {
print("Доступность часов: \(session.isReachable)")
}
func sessionDidBecomeInactive(_ session: WCSession) {
}
func sessionDidDeactivate(_ session: WCSession) {
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {
showLocalNotification(with: "aaaa")
guard let receivedMessage = message["msg"] as? String else {
NSLog("Получено сообщение, но не удалось разобрать 'msg'")
return
}
DispatchQueue.main.async {
if receivedMessage == "OPEN" {
session.sendMessage(["msg": "OPENED"], replyHandler: nil, errorHandler: nil)
}
}
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any] ) {
showLocalNotification(with: "aaaa")
guard let receivedMessage = message["msg"] as? String else {
NSLog("Получено сообщение, но не удалось разобрать 'msg'")
return
}
DispatchQueue.main.async {
if receivedMessage == "OPEN" {
session.sendMessage(["msg": "OPENED"], replyHandler: nil, errorHandler: nil)
}
}
}
}
watch
import Foundation
import WatchConnectivity
import WatchKit
class WatchViewModel: NSObject, ObservableObject, WCSessionDelegate, WKExtensionDelegate {
var session: WCSession
@Published var incomingMessage = ""
// Инициализация сессии
init(session: WCSession = .default) {
self.session = session
super.init()
self.session.delegate = self
session.activate()
}
// Метод WCSessionDelegate: вызывается при завершении активации сессии
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
//
}
// Метод WCSessionDelegate: получение сообщений
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
DispatchQueue.main.async {
if let msg = message["msg"] as? String {
self.incomingMessage = msg
print("Получено сообщение: \(msg)")
}
}
}
// Отправка сообщения на iPhone
func sendMessage(_ message: String) {
if session.isReachable && session.activationState == .activated {
session.sendMessage(["msg": message], replyHandler: nil, errorHandler: nil)
print("Отправлено сообщение: \(message)")
}
}
// MARK: - Методы WKExtensionDelegate
func applicationDidFinishLaunching() {
session = WCSession.default;
}
func applicationDidBecomeActive() {
session = WCSession.default;
}
func applicationWillResignActive() {
session = WCSession.default;
}
func handle(_ backgroundTasks: Set) {
// Отправлено, когда система требует запустить приложение в фоновом режиме для обработки задач. Задачи поступают в множестве, поэтому перебирайте и обрабатывайте каждую.
for task in backgroundTasks {
// Используйте оператор switch для проверки типа задачи
switch task {
case let backgroundTask as WKApplicationRefreshBackgroundTask:
// Обязательно завершите фоновую задачу, как только закончите.
backgroundTask.setTaskCompletedWithSnapshot(false)
case let snapshotTask as WKSnapshotRefreshBackgroundTask:
// Задачи снимка имеют уникальный вызов завершения, убедитесь, что вы установили дату истечения
snapshotTask.setTaskCompleted(restoredDefaultState: true, estimatedSnapshotExpiration: Date.distantFuture, userInfo: nil)
case let connectivityTask as WKWatchConnectivityRefreshBackgroundTask:
// Обязательно завершите задачу подключения, как только закончите.
connectivityTask.setTaskCompletedWithSnapshot(false)
case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
// Обязательно завершите задачу URL-сессии, как только закончите.
urlSessionTask.setTaskCompletedWithSnapshot(false)
case let relevantShortcutTask as WKRelevantShortcutRefreshBackgroundTask:
// Обязательно завершите задачу с актуальными ярлыками, как только закончите.
relevantShortcutTask.setTaskCompletedWithSnapshot(false)
case let intentDidRunTask as WKIntentDidRunRefreshBackgroundTask:
// Обязательно завершите задачу с выполнением намерения, как только закончите.
intentDidRunTask.setTaskCompletedWithSnapshot(false)
default:
// убедитесь, что завершаете необработанные типы задач
task.setTaskCompletedWithSnapshot(false)
}
}
}
}
Ответ или решение
Почему метод didReceiveMessage
не вызывается в фоновом режиме iOS приложения
Ваша проблема заключается в том, что метод didReceiveMessage
не вызывается в iOS приложении, когда оно запущено в фоновом режиме, несмотря на то что ваше приложение на Apple Watch успешно передает сообщения. Это поведение типично и объясняется особенностями работы WatchConnectivity в iOS.
Основные факторы, влияющие на didReceiveMessage
в фоновом режиме:
-
Ограничения фона: В iOS, когда приложение находится в фоновом режиме или закрыто, оно не может принимать сообщения через WCSession в обычном режиме, как это происходит в активном состоянии. Сообщения в этих состояниях могут быть потеряны, если iOS приложение не было предварительно активировано соответствующим образом.
-
Активация с помощью фоновых задач: Для того чтобы ваше iOS приложение могло реагировать на сообщения из Watch приложения в фоновом режиме, вы должны использовать фоновые задачи. В вашем случае, если приложение на Apple Watch отправляет сообщение, когда iOS приложение не запущено, вам необходимо создать механизм, который позволит вашему приложению быть активированным, когда к нему поступают сообщения.
-
Настройки в
Info.plist
: Убедитесь, что ваше iOS приложение настроено для обработки фоновых задач. В вашемInfo.plist
файл должны быть указаны соответствующие ключи, такие какUIBackgroundModes
с типамиfetch
иremote-notification
. -
Тип сообщений: Очень важно понимать разницу между различными типами сообщений в WatchConnectivity. Например, метод
didReceiveMessage
предназначен для передачи кратких сообщений между устройствами, в то время как для более сложных операций стоит использоватьsendMessage
с ответами, а в случае необходимости может работать методdidReceiveMessageData
.
Рекомендации для решения проблемы
-
Обработка фоновых задач: Изучите возможность использования фоновых задач для обработки входящих сообщений. В iOS, чтобы увеличить шансы на получение сообщений, вы можете использовать метод
beginBackgroundTask(withName:expirationHandler:)
. -
Создание локальных уведомлений: Как вы уже делаете, отправляйте локальные уведомления, когда обрабатываете сообщение. Это будет хорошей стратегией для привлечения внимания пользователю, когда сообщение было получено.
-
Проверка возможности связи: В
sessionReachabilityDidChange
вы можете проверять доступность связи и пытаться инициировать повторную отправку сообщение в случае, если приложение не отвечает. -
Обработка сообщения в фоновом режиме:
- Добавьте код для обработки фонового режима в методе
handle
вашего делегатаWKExtensionDelegate
на Apple Watch. - Используйте
WKWatchConnectivityRefreshBackgroundTask
, чтобы проверить наличие входящих сообщений и выполнить необходимые операции.
- Добавьте код для обработки фонового режима в методе
Пример кода:
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
guard session.isReachable else {
showLocalNotification(with: "Watch not reachable")
return
}
// Обработка сообщения
processIncomingMessage(message)
// Уведомление для пользователя
showLocalNotification(with: "Received message from Watch")
}
Заключение
Чтобы обеспечить надежную передачу сообщений от вашего Watch приложения к iOS приложению даже когда оно находится в фоновом состоянии, важно реализовать фоновую обработку таких сообщений. Также, оставайтесь в курсе обновлений Apple и документации по WatchConnectivity, чтобы следить за возможными изменениями в поведении API и его возможностях.