使用 URLSession 和后台获取以及使用 firebase 的远程通知
Using URLSession and background fetch together with remote notifications using firebase
我正在尝试在此处实现一个基本功能,当我的应用程序进入后台或挂起时将被调用。
实际上,我们的目标是每天发送大约 5 个,因此 Apple 不应限制我们的使用。
我整理了以下使用 firebase 和 userNotifications 的内容,现在,它在我的应用程序委托中。
import Firebase
import FirebaseMessaging
import UserNotifications
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var backgroundSessionCompletionHandler: (() -> Void)?
lazy var downloadsSession: Foundation.URLSession = {
let configuration = URLSessionConfiguration.background(withIdentifier: "bgSessionConfiguration")
configuration.timeoutIntervalForRequest = 30.0
let session = Foundation.URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
return session
}()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FIRApp.configure()
if #available(iOS 10.0, *) {
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: {_, _ in })
// For iOS 10 display notification (sent via APNS)
UNUserNotificationCenter.current().delegate = self
// For iOS 10 data message (sent via FCM)
FIRMessaging.messaging().remoteMessageDelegate = self
} else {
let settings: UIUserNotificationSettings =
UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
application.registerUserNotificationSettings(settings)
}
application.registerForRemoteNotifications()
let token = FIRInstanceID.instanceID().token()!
print("token is \(token) < ")
return true
}
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void){
print("in handleEventsForBackgroundURLSession")
_ = self.downloadsSession
self.backgroundSessionCompletionHandler = completionHandler
}
//MARK: SyncFunc
func startDownload() {
NSLog("in startDownload func")
let todoEndpoint: String = "https://jsonplaceholder.typicode.com/todos/1"
guard let url = URL(string: todoEndpoint) else {
print("Error: cannot create URL")
return
}
// make the request
let task = downloadsSession.downloadTask(with: url)
task.resume()
NSLog(" ")
NSLog(" ")
}
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession){
DispatchQueue.main.async(execute: {
self.backgroundSessionCompletionHandler?()
self.backgroundSessionCompletionHandler = nil
})
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
NSLog("in didReceiveRemoteNotification")
NSLog("%@", userInfo)
startDownload()
DispatchQueue.main.async {
completionHandler(UIBackgroundFetchResult.newData)
}
}
}
@available(iOS 10, *)
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
// Print message ID.
//print("Message ID: \(userInfo["gcm.message_id"]!)")
// Print full message.
print("%@", userInfo)
startDownload()
DispatchQueue.main.async {
completionHandler(UNNotificationPresentationOptions.alert)
}
}
*/
}
extension AppDelegate : FIRMessagingDelegate {
// Receive data message on iOS 10 devices.
func applicationReceivedRemoteMessage(_ remoteMessage: FIRMessagingRemoteMessage) {
print("%@", remoteMessage.appData)
}
}
extension AppDelegate: URLSessionDownloadDelegate {
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL){
NSLog("finished downloading")
}
}
结果如下:
当应用程序在前台时:
我得到日志"in startDownload func"
我得到日志"finished downloading"。
当应用程序处于后台时:
我得到日志"in startDownload func"
我没有得到日志 "finished downloading"。
消音器不工作,即当应用程序在后台运行时,我仍在托盘中收到通知。
我正在使用 Postman 发送请求并尝试了以下负载,这导致控制台错误 'FIRMessaging receiving notification in invalid state 2'
:
{
"to" : "Server_Key",
"content_available" : true,
"notification": {
"body": "Firebase Cloud Message29- BG CA1"
}
}
我设置了后台获取和远程通知的功能。该应用程序使用 swift 3 编写并使用最新的 Firebase
编辑:根据评论更新了 AppDelegate 以包含函数
几点观察:
当您的应用程序被 handleEventsForBackgroundURLSessionIdentifier
重新启动时,您不仅要保存完成处理程序,而且实际上还必须启动会话。您似乎在做前者,而不是后者。
此外,您必须实施 urlSessionDidFinishEvents(forBackgroundURLSession:)
并调用(并丢弃您对的引用)保存的完成处理程序。
您好像在做数据任务。但如果要后台运行,则必须是下载或上传任务。[您已编辑问题使其成为下载任务。]
在 userNotificationCenter(_:willPresent:completionHandler:)
中,您永远不会调用传递给此方法的完成处理程序。因此,当 30 秒(或任何时间)到期时,由于您没有调用它,您的应用程序将立即终止并且所有后台请求都将被取消。
因此,willPresent
应该在完成启动请求后立即调用其完成处理程序。不要将此完成处理程序(您已完成处理通知)与稍后提供给 urlSessionDidFinishEvents
的单独完成处理程序(您已完成处理后台 URLSession
事件)混淆。
您保存的后台会话完成处理程序不正确。我建议:
var backgroundSessionCompletionHandler: (() -> Void)?
保存时是:
backgroundSessionCompletionHandler = completionHandler // note, no ()
当你在 urlSessionDidFinishEvents
中调用它时,它是:
DispatchQueue.main.async {
self.backgroundSessionCompletionHandler?()
self.backgroundSessionCompletionHandler = nil
}
我正在尝试在此处实现一个基本功能,当我的应用程序进入后台或挂起时将被调用。
实际上,我们的目标是每天发送大约 5 个,因此 Apple 不应限制我们的使用。
我整理了以下使用 firebase 和 userNotifications 的内容,现在,它在我的应用程序委托中。
import Firebase
import FirebaseMessaging
import UserNotifications
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var backgroundSessionCompletionHandler: (() -> Void)?
lazy var downloadsSession: Foundation.URLSession = {
let configuration = URLSessionConfiguration.background(withIdentifier: "bgSessionConfiguration")
configuration.timeoutIntervalForRequest = 30.0
let session = Foundation.URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
return session
}()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FIRApp.configure()
if #available(iOS 10.0, *) {
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: {_, _ in })
// For iOS 10 display notification (sent via APNS)
UNUserNotificationCenter.current().delegate = self
// For iOS 10 data message (sent via FCM)
FIRMessaging.messaging().remoteMessageDelegate = self
} else {
let settings: UIUserNotificationSettings =
UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
application.registerUserNotificationSettings(settings)
}
application.registerForRemoteNotifications()
let token = FIRInstanceID.instanceID().token()!
print("token is \(token) < ")
return true
}
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void){
print("in handleEventsForBackgroundURLSession")
_ = self.downloadsSession
self.backgroundSessionCompletionHandler = completionHandler
}
//MARK: SyncFunc
func startDownload() {
NSLog("in startDownload func")
let todoEndpoint: String = "https://jsonplaceholder.typicode.com/todos/1"
guard let url = URL(string: todoEndpoint) else {
print("Error: cannot create URL")
return
}
// make the request
let task = downloadsSession.downloadTask(with: url)
task.resume()
NSLog(" ")
NSLog(" ")
}
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession){
DispatchQueue.main.async(execute: {
self.backgroundSessionCompletionHandler?()
self.backgroundSessionCompletionHandler = nil
})
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
NSLog("in didReceiveRemoteNotification")
NSLog("%@", userInfo)
startDownload()
DispatchQueue.main.async {
completionHandler(UIBackgroundFetchResult.newData)
}
}
}
@available(iOS 10, *)
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
// Print message ID.
//print("Message ID: \(userInfo["gcm.message_id"]!)")
// Print full message.
print("%@", userInfo)
startDownload()
DispatchQueue.main.async {
completionHandler(UNNotificationPresentationOptions.alert)
}
}
*/
}
extension AppDelegate : FIRMessagingDelegate {
// Receive data message on iOS 10 devices.
func applicationReceivedRemoteMessage(_ remoteMessage: FIRMessagingRemoteMessage) {
print("%@", remoteMessage.appData)
}
}
extension AppDelegate: URLSessionDownloadDelegate {
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL){
NSLog("finished downloading")
}
}
结果如下:
当应用程序在前台时:
我得到日志"in startDownload func"
我得到日志"finished downloading"。
当应用程序处于后台时:
我得到日志"in startDownload func"
我没有得到日志 "finished downloading"。
消音器不工作,即当应用程序在后台运行时,我仍在托盘中收到通知。
我正在使用 Postman 发送请求并尝试了以下负载,这导致控制台错误 'FIRMessaging receiving notification in invalid state 2'
:
{
"to" : "Server_Key",
"content_available" : true,
"notification": {
"body": "Firebase Cloud Message29- BG CA1"
}
}
我设置了后台获取和远程通知的功能。该应用程序使用 swift 3 编写并使用最新的 Firebase
编辑:根据评论更新了 AppDelegate 以包含函数
几点观察:
当您的应用程序被
handleEventsForBackgroundURLSessionIdentifier
重新启动时,您不仅要保存完成处理程序,而且实际上还必须启动会话。您似乎在做前者,而不是后者。此外,您必须实施
urlSessionDidFinishEvents(forBackgroundURLSession:)
并调用(并丢弃您对的引用)保存的完成处理程序。您好像在做数据任务。但如果要后台运行,则必须是下载或上传任务。[您已编辑问题使其成为下载任务。]在
userNotificationCenter(_:willPresent:completionHandler:)
中,您永远不会调用传递给此方法的完成处理程序。因此,当 30 秒(或任何时间)到期时,由于您没有调用它,您的应用程序将立即终止并且所有后台请求都将被取消。因此,
willPresent
应该在完成启动请求后立即调用其完成处理程序。不要将此完成处理程序(您已完成处理通知)与稍后提供给urlSessionDidFinishEvents
的单独完成处理程序(您已完成处理后台URLSession
事件)混淆。您保存的后台会话完成处理程序不正确。我建议:
var backgroundSessionCompletionHandler: (() -> Void)?
保存时是:
backgroundSessionCompletionHandler = completionHandler // note, no ()
当你在
urlSessionDidFinishEvents
中调用它时,它是:DispatchQueue.main.async { self.backgroundSessionCompletionHandler?() self.backgroundSessionCompletionHandler = nil }