通知操作后台任务未按预期运行

Posted

技术标签:

【中文标题】通知操作后台任务未按预期运行【英文标题】:Notification action background task not running as expected 【发布时间】:2017-07-26 15:15:58 【问题描述】:

更新

在一些 cmets 之后,我将 resolveVisita 方法更改为在后台使用 URLSession,但看起来后台会话仅在应用程序处于前台时才会启动,这是在通知时运行的更新方法动作被点击:

static func resolverVisita(idMensagem: String, resposta: String, liberar: Bool, view: UIViewController?) 

    //let configuration = URLSessionConfiguration.background(withIdentifier: "url_session_resolve_visita")
    //var backgroundSession = URLSession(configuration: configuration)

    // Set up the URL request
    let todoEndpoint: String = URL_RESPONDE_VISITA

    guard let url = URL(string: todoEndpoint) else 
        print("Error: cannot create URL")
        return
    

    var urlRequest = URLRequest(url: url)
    urlRequest.httpMethod = "POST"

    let postString = "userUUID=\(SessionManager.getUsrUUID())&devUUID=\(devUUID)&msgID=\(idMensagem)&tarefa=\(liberar ? "L" : "B")&resposta=\(resposta)"

    urlRequest.httpBody = postString.data(using: .utf8)

    let config = URLSessionConfiguration.background(withIdentifier: "\(Bundle.main.bundleIdentifier!).responde_visita")

    let session = URLSession(configuration: config)

    /*
    let task = URLSession.shared.dataTask(with: urlRequest)  data, response, error in
        guard let data = data, error == nil else                                                  // check for fundamental networking error
            print("error=\(error)")
            return
        

        if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200            // check for http errors
            print("statusCode should be 200, but is \(httpStatus.statusCode)")
            print("response = \(response)")
        

        let responseString = String(data: data, encoding: .utf8)
        print("responseString = \(responseString)")
    */

    let task = session.dataTask(with: urlRequest)
    task.resume()


注释掉的代码有时似乎可以工作,但看起来它不是正确的做法。

更新结束

所以我有一个接收通知的应用程序,它有 2 个操作,根据用户响应,应用程序应该向服务器发送响应,最好是在后台。

我面临一个问题,即每当点击某个操作时,有时在打开应用程序之前不会调用 userNotificationCenter 方法,有时它会运行,但 Alamofire 服务器调用不会得到处理,然后如果我打开应用程序,控制台中会显示有关 Alamofire 调用的一些错误,但是如果我点击该操作然后足够快地打开应用程序,它会按预期工作。

我已经在 Xcode 的应用功能下启用了后台获取远程通知

这就是我为通知创建操作的方式:

 let liberar = UNTextInputNotificationAction(identifier:"liberar", title:"Liberar",options:[.authenticationRequired],
                                                     textInputButtonTitle: "Liberar",
                                                     textInputPlaceholder: "Resposta")

    let bloquear = UNTextInputNotificationAction(identifier: "bloquear", title: "Bloquear", options: [.destructive],
                                                     textInputButtonTitle: "Bloquear",
                                                     textInputPlaceholder: "Resposta")

这是我的UNUserNotificationCenterDelegate 协议实现:

extension AppDelegate : UNUserNotificationCenterDelegate 

// Receive displayed notifications for ios 10 devices.
func userNotificationCenter(_ center: UNUserNotificationCenter,
                            willPresent notification: UNNotification,
                            withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) 
    let userInfo = notification.request.content.userInfo

    // With swizzling disabled you must let Messaging know about the message, for Analytics
    // Messaging.messaging().appDidReceiveMessage(userInfo)

    // Print message ID.
    if let messageID = userInfo[gcmMessageIDKey] 
        print("Message ID: \(messageID)")
    

    // Print full message.
    print(userInfo)

    // Change this to your preferred presentation option
    completionHandler([.alert, .badge, .sound])


func userNotificationCenter(_ center: UNUserNotificationCenter,
                            didReceive response: UNNotificationResponse,
                            withCompletionHandler completionHandler: @escaping () -> Void) 

    let userInfo = response.notification.request.content.userInfo
    let acao = response.actionIdentifier

    //let request = response.notification.request

    // Print message ID.
    if let messageID = userInfo[gcmMessageIDKey] 
        print("Message ID: \(messageID)")
    

    print(acao)

    if acao == "liberar" 
        // Print full message.


        let textResponse = response as! UNTextInputNotificationResponse

        print("Liberando Visita")
        AppConfig.resolverVisita(idMensagem: (userInfo["msgid"] as? String)!, resposta: textResponse.userText, liberar: true, view: nil)
    

    else if acao == "bloquear" 
        let textResponse = response as! UNTextInputNotificationResponse

        print("Bloqueando Visita")
        AppConfig.resolverVisita(idMensagem: (userInfo["msgid"] as? String)!, resposta: textResponse.userText, liberar: false, view: nil)

        //let newContent = request.content.mutableCopy() as! UNMutableNotificationContent
        //print(textResponse.userText)

    

    completionHandler()

这里是 resolveVisita 方法,它基本上只是收集一些数据并使用 Alamofire 通过 POST 请求发送。

static func resolverVisita(idMensagem: String, resposta: String, liberar: Bool, view: UIViewController?) 

    if view != nil 
        showLoading(mensagem: NSLocalizedString("Processando...", comment: ""), view: view!)
    
    //Parameters to be sent
    let parameters: Parameters=[
        "userUUID":SessionManager.getUsrUUID(),
        "devUUID":devUUID,
        "msgID": idMensagem,
        "resposta": resposta,
        "tarefa": liberar ? "L" : "B"
        ];

    //Sending http post request
    Alamofire.request(URL_RESPONDE_VISITA, method: .post, parameters: parameters).responseJSON
        response in
        //printing response
        print(response)

        //getting the json value from the server
        if let result = response.result.value 

            //converting it as NSDictionary
            let jsonData = result as! NSDictionary

            var mensagem : String = "Solicitacao resolvida com sucesso"

            //displaying the message in label
            if((jsonData["error"] as! Bool))

                mensagem = jsonData["error_msg"] as! String

                if view == nil 
                    Notificacoes.notificaErroSolicitavao(msgId: idMensagem, errMsg: mensagem)
                

            

            if view != nil 
                view?.dismiss(animated: false)
                    showAlert(mensagem: NSLocalizedString(mensagem, comment: ""), view: view!, okMsg: NSLocalizedString("Voltar", comment: ""), segue: "voltarParaLogin")
                
            
            else
                print(mensagem)
            

        
    


如果我只是将.foreground 选项添加到使应用程序在被选中后打开的操作中,问题就解决了,但是我真的认为没有必要为此任务打开应用程序。

【问题讨论】:

我建议启动后台 URLSession 请求(这对于 Alamofire 来说确实很笨拙),或者至少在异步 responseJSON 完成处理程序之后才调用通知完成处理程序叫做。后一种方法的典型解决方案是为您的方法本身提供一个完成处理程序,您将在 responseJSON 闭包结束时调用。然后将对用户通知完成处理程序的调用移动到该闭包中。 @Rob 哦,我现在明白了,当调用完成处理程序时,后台任务被杀死,所以我必须找到一种方法来调用 alamofire 响应的完成句柄。对吗? 是的,就是这样。而且您通常只有几秒钟(30?),这就是为什么如果不太可能很快收到回复,您会使用后台URLSession @Rob 我实际上不需要响应是公平的,因为服务器会为响应生成另一个通知,所以我现在会尝试,如果我遇到问题我可能会检查URLSession 嘿,@Rob 先生,所以我更改了我的 resolveVisita 方法以使用 URLSession 背景而不是 alamofire,我创建了会话,请求执行了 task.resume,但它没有从通知中运行!它仅在应用程序打开时才有效。我是否必须设置一些特殊权限才能从通知或锁定屏幕启动后台会话? 【参考方案1】:

所以一段时间后,我决定以全新的心态回到这个问题,我发现我做错了什么,我试图在锁定屏幕的背景中创建 URLSession 的实例,并创建一个“相同会话”的新实例,为了解决这个问题,我所要做的就是创建一个 URLSession 的静态实例,然后在需要时使用它。

我是这样创建的。

 static let resolveVisitaSession = URLSession(configuration: URLSessionConfiguration.background(withIdentifier: "br.com.freaccess.resolveVisitaRequest"))

这是在选择 UNNotificationAction 时调用的方法,我在那里使用我的 URLSession。

static func resolverVisita(idMensagem: String, resposta: String, liberar: Bool) 
    print("Entered resolveVisita method")

    // Set up the URL request
    let todoEndpoint: String = URL_RESPONDE_VISITA

    guard let url = URL(string: todoEndpoint) else 
        print("Error: cannot create URL")
        return
    

    var urlRequest = URLRequest(url: url)
    urlRequest.httpMethod = "POST"

    let postString = "userUUID=\(SessionManager.getUsrUUID())&devUUID=\(devUUID)&msgID=\(idMensagem)&tarefa=\(liberar ? "L" : "B")&resposta=\(resposta)&skipSocket=true"

    urlRequest.httpBody = postString.data(using: .utf8)

    resolveVisitaSession.dataTask(with: urlRequest).resume()

【讨论】:

以上是关于通知操作后台任务未按预期运行的主要内容,如果未能解决你的问题,请参考以下文章

WP 8.1 中的后台任务运行不止一次

带有进度通知的长时间运行的后台任务

推送通知后台任务崩溃 Windows Phone 8.1 模拟器/设备

如果前台应用程序终止,如何通知后台任务?

后台任务永远运行?

iPhone:重复后台任务