【问题标题】:How to get push notification when app is not running/app is terminated当应用程序未运行/应用程序终止时如何获取推送通知
【发布时间】:2016-07-11 18:34:50
【问题描述】:

我试过了 来自 Google 开发人员的“在 iOS 上设置 GCM 客户端应用程序”。 我的应用程序有一个 android 版本,服务器成功向 Android 发送推送通知。在ios中,我可以将消息检索到didRecieveRemoteNotification函数。打印时如下所示,

aps: {
    alert =     {
        body = tyyy;
        title = "2 is going out at 03/24/2016 15:02:48";
    };
    badge = 2;
    sound = default;
}

当应用程序处于前台和后台时,它会收到此消息。当应用程序处于后台时,系统托盘中不显示任何内容。

当应用程序终止并且服务器正在发送推送通知时,我什么也没有收到,也没有显示任何活动。

我的代码如下。

AppDelegate.swift

import UIKit

 @UIApplicationMain



class AppDelegate: UIResponder, UIApplicationDelegate, GGLInstanceIDDelegate,  GCMReceiverDelegate {

var window: UIWindow?

var connectedToGCM = false
var subscribedToTopic = false
var gcmSenderID: String?
var registrationToken = "AIzaSy-.....-11bSP6v72UvyKY"
var registrationOptions = [String: AnyObject]()

let registrationKey = "onRegistrationCompleted"
let messageKey = "onMessageReceived"
let subscriptionTopic = "/topics/global"


func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {

    NSUserDefaults.standardUserDefaults().setBool(true, forKey: "APP_RUNNING")


    // Override point for customization after application launch.

    // [START_EXCLUDE]
    // Configure the Google context: parses the GoogleService-Info.plist, and initializes
    // the services that have entries in the file
    var configureError:NSError?
    GGLContext.sharedInstance().configureWithError(&configureError)
    assert(configureError == nil, "Error configuring Google services: \(configureError)")
    gcmSenderID = GGLContext.sharedInstance().configuration.gcmSenderID
    // [END_EXCLUDE]
    // Register for remote notifications
    if #available(iOS 8.0, *) {
        let settings: UIUserNotificationSettings =
        UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: nil)
        application.registerUserNotificationSettings(settings)
        application.registerForRemoteNotifications()
    } else {
        // Fallback
        let types: UIRemoteNotificationType = [.Alert, .Badge, .Sound]
        application.registerForRemoteNotificationTypes(types)
    }

    // [END register_for_remote_notifications]
    // [START start_gcm_service]
    let gcmConfig = GCMConfig.defaultConfig()
    gcmConfig.receiverDelegate = self
    GCMService.sharedInstance().startWithConfig(gcmConfig)
    // [END start_gcm_service]

    application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound],categories: nil))

    if let options = launchOptions {
        if let notification = options[UIApplicationLaunchOptionsLocalNotificationKey] as? UILocalNotification {
            if let userInfo = notification.userInfo {

                // do something neat here
            }
        }
    }

    return true
}

func subscribeToTopic() {
    // If the app has a registration token and is connected to GCM, proceed to subscribe to the
    // topic
    if(registrationToken != "" && connectedToGCM) {
        GCMPubSub.sharedInstance().subscribeWithToken(self.registrationToken, topic: subscriptionTopic,
            options: nil, handler: {(error:NSError?) -> Void in
                if let error = error {
                    // Treat the "already subscribed" error more gently
                    if error.code == 3001 {
                        print("Already subscribed to \(self.subscriptionTopic)")
                    } else {
                        print("Subscription failed: \(error.localizedDescription)");
                    }
                } else {
                    self.subscribedToTopic = true;
                    NSLog("Subscribed to \(self.subscriptionTopic)");
                }
        })
    }
}

func application( application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken
    deviceToken: NSData ) {

        // [END receive_apns_token]
        // [START get_gcm_reg_token]
        // Create a config and set a delegate that implements the GGLInstaceIDDelegate protocol.
        let instanceIDConfig = GGLInstanceIDConfig.defaultConfig()
        instanceIDConfig.delegate = self
        // Start the GGLInstanceID shared instance with that config and request a registration
        // token to enable reception of notifications
        GGLInstanceID.sharedInstance().startWithConfig(instanceIDConfig)
        registrationOptions = [kGGLInstanceIDRegisterAPNSOption:deviceToken,
            kGGLInstanceIDAPNSServerTypeSandboxOption:true]
        GGLInstanceID.sharedInstance().tokenWithAuthorizedEntity(gcmSenderID,
            scope: kGGLInstanceIDScopeGCM, options: registrationOptions, handler: registrationHandler)
        // [END get_gcm_reg_token]


}

// [START receive_apns_token_error]
func application( application: UIApplication, didFailToRegisterForRemoteNotificationsWithError
    error: NSError ) {
        print("Registration for remote notification failed with error: \(error.localizedDescription)")
        // [END receive_apns_token_error]
        let userInfo = ["error": error.localizedDescription]
        NSNotificationCenter.defaultCenter().postNotificationName(
            registrationKey, object: nil, userInfo: userInfo)
}


// [START ack_message_reception]
func application( application: UIApplication,
    didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
        print("Notification received: \(userInfo)")
        // This works only if the app started the GCM service
        GCMService.sharedInstance().appDidReceiveMessage(userInfo);
        // Handle the received message
        // [START_EXCLUDE]
        NSNotificationCenter.defaultCenter().postNotificationName("reloadTableEvent", object: nil)
        NSNotificationCenter.defaultCenter().postNotificationName(messageKey, object: nil,
            userInfo: userInfo)
        // [END_EXCLUDE]
}

func application( application: UIApplication,
    didReceiveRemoteNotification userInfo: [NSObject : AnyObject],
    fetchCompletionHandler handler: (UIBackgroundFetchResult) -> Void) {
        print("Notification received: \(userInfo)")
        // This works only if the app started the GCM service
        GCMService.sharedInstance().appDidReceiveMessage(userInfo);
        // Handle the received message
        // Invoke the completion handler passing the appropriate UIBackgroundFetchResult value
        // [START_EXCLUDE]
        NSNotificationCenter.defaultCenter().postNotificationName(messageKey, object: nil,
            userInfo: userInfo)
        handler(UIBackgroundFetchResult.NoData);
        // [END_EXCLUDE]
}
// [END ack_message_reception]

func registrationHandler(registrationToken: String!, error: NSError!) {
    if (registrationToken != nil) {
        self.registrationToken = registrationToken
        print("Registration Token: \(registrationToken)")
        NSUserDefaults.standardUserDefaults().setValue(registrationToken, forKey: "registrationToken")
        self.subscribeToTopic()
        let userInfo = ["registrationToken": registrationToken]
        NSNotificationCenter.defaultCenter().postNotificationName(
            self.registrationKey, object: nil, userInfo: userInfo)
    } else {
        print("Registration to GCM failed with error: \(error.localizedDescription)")
        let userInfo = ["error": error.localizedDescription]
        NSNotificationCenter.defaultCenter().postNotificationName(
            self.registrationKey, object: nil, userInfo: userInfo)
    }
}

// [START on_token_refresh]
func onTokenRefresh() {
    // A rotation of the registration tokens is happening, so the app needs to request a new token.
    print("The GCM registration token needs to be changed.")
    GGLInstanceID.sharedInstance().tokenWithAuthorizedEntity(gcmSenderID,
        scope: kGGLInstanceIDScopeGCM, options: registrationOptions, handler: registrationHandler)
}
// [END on_token_refresh]

// [START upstream_callbacks]
func willSendDataMessageWithID(messageID: String!, error: NSError!) {
    if (error != nil) {
        // Failed to send the message.
    } else {
        // Will send message, you can save the messageID to track the message
    }
}

func didSendDataMessageWithID(messageID: String!) {
    // Did successfully send message identified by messageID
}
// [END upstream_callbacks]

func didDeleteMessagesOnServer() {
    // Some messages sent to this device were deleted on the GCM server before reception, likely
    // because the TTL expired. The client should notify the app server of this, so that the app
    // server can resend those messages.
}

func applicationWillResignActive(application: UIApplication) {
    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
    // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}

func applicationDidEnterBackground(application: UIApplication) {

    NSUserDefaults.standardUserDefaults().setBool(false, forKey: "APP_RUNNING")

    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.

    GCMService.sharedInstance().disconnect()
    // [START_EXCLUDE]
    self.connectedToGCM = false
    // [END_EXCLUDE]
}

func applicationWillEnterForeground(application: UIApplication) {

    NSUserDefaults.standardUserDefaults().setBool(true, forKey: "APP_RUNNING")

    // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}

func applicationDidBecomeActive(application: UIApplication) {

    NSUserDefaults.standardUserDefaults().setBool(true, forKey: "APP_RUNNING")

    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.

    // Connect to the GCM server to receive non-APNS notifications
    GCMService.sharedInstance().connectWithHandler({(error:NSError?) -> Void in
        if let error = error {
            print("Could not connect to GCM: \(error.localizedDescription)")
        } else {
            self.connectedToGCM = true
            print("Connected to GCM")
            // [START_EXCLUDE]
            self.subscribeToTopic()
            // [END_EXCLUDE]
        }
    })
}

func applicationWillTerminate(application: UIApplication) {

    NSUserDefaults.standardUserDefaults().setBool(false, forKey: "APP_RUNNING")

    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}

func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
    if let userInfo = notification.userInfo {
        NSNotificationCenter.defaultCenter().postNotificationName(
            "LoadEventViewController", object: nil, userInfo: userInfo)
    }
}




}

ViewController,弹出本地通知

func scheduleLocal(message: String) {
    let settings = UIApplication.sharedApplication().currentUserNotificationSettings()

    if settings!.types == .None {
        let ac = UIAlertController(title: "Can't schedule", message: "Either we don't have permission to schedule notifications, or we haven't asked yet.", preferredStyle: .Alert)
        ac.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
        presentViewController(ac, animated: true, completion: nil)
        return
    }

    // create a corresponding local notification
    let notification = UILocalNotification()
    notification.alertBody = message // text that will be displayed in the notification
    notification.alertAction = "open" // text that is displayed after "slide to..." on the lock screen - defaults to "slide to view"
    notification.fireDate = NSDate(timeIntervalSinceNow: 0) // todo item due date (when notification will be fired)
    notification.soundName = UILocalNotificationDefaultSoundName // play default sound
    notification.userInfo = ["UUID": 1, ] // assign a unique identifier to the notification so that we can retrieve it later
    notification.category = "TODO_CATEGORY"
    UIApplication.sharedApplication().scheduleLocalNotification(notification)
}

我有 2 个问题,

  1. 即使应用程序处于终止状态,是否也可以接收和显示推送通知?如果有,怎么做?
  2. 我在代码中做错了吗?

【问题讨论】:

  • 1) 可以吗 2) 你应该格式化你的代码以提高可读性
  • 你在apple developerper上配置好所有必要的东西了吗?你有证书吗?您需要同时配置 GCM 和 Apple developer。当我在 iOS 上使用 GCM 实现通知时,我无法实现后台通知。这是因为服务器发送给您的格式必须准确。我可以指出我的问题。 stackoverflow.com/q/35873147/1585121
  • 当您的应用程序终止或在后台运行时,操作系统会为您显示通知。
  • 你创建的推送通知是什么环境?沙盒还是生产?而且您的代码不处理来自 APN 服务器的消息
  • “您的代码不处理来自 APN 服务的消息”是什么意思我通过参考 Mayerz 链接在应用程序处于后台时使其工作。但是当应用程序运行和终止时仍然没有显示通知。编辑了清晰代码的问题

标签: ios swift apple-push-notifications terminate system-tray


【解决方案1】:

这是你的推送通知 JSON?

aps: { "content-available" = 1; }

如果是,那么您正在发送静默推送。静默推送意味着用户没有收到视觉通知,只是调用了您应用的应用委托回调。删除 content-available 标记并改为传递消息文本。

如果 App 在前台,iOS 不会显示推送通知,而只是调用委托。然后您可以显示警报视图或您喜欢的其他内容。

或者你可以显示这个:https://github.com/avielg/AGPushNote

关于“由用户终止”的状态,这里是 Apple 文档(用于静默推送):

Apple documentation

使用此方法为您的应用处理传入的远程通知。 与 application:didReceiveRemoteNotification: 方法不同,它是 仅当您的应用程序在前台运行时调用,系统 当您的应用在前台运行时调用此方法或 背景。此外,如果您启用了远程通知 后台模式,系统启动您的应用程序(或从 挂起状态),并在远程时将其置于后台状态 通知到达。但是系统不会自动 如果用户强制退出它,则启动您的应用程序。在这种情况下, 用户必须在系统之前重新启动您的应用程序或重新启动设备 尝试再次自动启动您的应用。

【讨论】:

  • 谢谢。完毕。尽管如此,当应用程序处于运行状态时,不会触发任何通知。终止状态时相同。当应用程序在后台/手机被锁定时,推送通知显示成功。有什么想法吗?
  • 我现在收到的是这个,aps: { alert = { body = tyyy; title = "2 将于 2016 年 3 月 24 日 15:02:48 发布"; };徽章 = 2; “内容可用”= 1;声音=默认;仍然,当应用程序处于运行状态或终止时,不会显示推送通知。只有当应用程序在后台时才会显示推送通知。我不明白为什么一种状态有效而另一种状态无效
  • 我不是问重新启动应用程序,我问的是为什么在终止状态和运行状态时系统托盘中不显示推送通知
  • 你看我的回答了吗?我已经回答了这个。在运行和终止状态下不显示推送通知。就是这样,iOS 的行为是这样的。这是正确的行为。
  • 但是像 gmail、facebook 这样的应用程序即使处于运行状态或终止状态也会发送推送通知。他们是怎么做到的?
【解决方案2】:

如果您还没有这样做,请在 Apple developerper 上注册并配置您的应用以接收推送通知。您必须为此付费(99 美元/年)。

GCM 在前台直接向您的应用发送消息,但在后台 GCM 依靠 APN(Apple 推送通知服务)到达您的设备。

因此,您的服务器发送给 GCM 的消息格式必须精确,this finally worked for me

【讨论】:

  • 前台或后台推送通知都需要 APN。没有服务器可以直接向苹果手机发送通知,只能通过苹果的 APNs
猜你喜欢
  • 1970-01-01
  • 2016-03-25
  • 1970-01-01
  • 2012-02-29
  • 1970-01-01
  • 2021-06-06
  • 2016-05-05
  • 2017-07-11
  • 1970-01-01
相关资源
最近更新 更多