Функция DidReceiveMessage не срабатывает в фоновом режиме приложения iOS

Вопрос или проблема

Я пишу родной код 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 в фоновом режиме:

  1. Ограничения фона: В iOS, когда приложение находится в фоновом режиме или закрыто, оно не может принимать сообщения через WCSession в обычном режиме, как это происходит в активном состоянии. Сообщения в этих состояниях могут быть потеряны, если iOS приложение не было предварительно активировано соответствующим образом.

  2. Активация с помощью фоновых задач: Для того чтобы ваше iOS приложение могло реагировать на сообщения из Watch приложения в фоновом режиме, вы должны использовать фоновые задачи. В вашем случае, если приложение на Apple Watch отправляет сообщение, когда iOS приложение не запущено, вам необходимо создать механизм, который позволит вашему приложению быть активированным, когда к нему поступают сообщения.

  3. Настройки в Info.plist: Убедитесь, что ваше iOS приложение настроено для обработки фоновых задач. В вашем Info.plist файл должны быть указаны соответствующие ключи, такие как UIBackgroundModes с типами fetch и remote-notification.

  4. Тип сообщений: Очень важно понимать разницу между различными типами сообщений в WatchConnectivity. Например, метод didReceiveMessage предназначен для передачи кратких сообщений между устройствами, в то время как для более сложных операций стоит использовать sendMessage с ответами, а в случае необходимости может работать метод didReceiveMessageData.

Рекомендации для решения проблемы

  1. Обработка фоновых задач: Изучите возможность использования фоновых задач для обработки входящих сообщений. В iOS, чтобы увеличить шансы на получение сообщений, вы можете использовать метод beginBackgroundTask(withName:expirationHandler:).

  2. Создание локальных уведомлений: Как вы уже делаете, отправляйте локальные уведомления, когда обрабатываете сообщение. Это будет хорошей стратегией для привлечения внимания пользователю, когда сообщение было получено.

  3. Проверка возможности связи: В sessionReachabilityDidChange вы можете проверять доступность связи и пытаться инициировать повторную отправку сообщение в случае, если приложение не отвечает.

  4. Обработка сообщения в фоновом режиме:

    • Добавьте код для обработки фонового режима в методе 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 и его возможностях.

Оцените материал
Добавить комментарий

Капча загружается...