iOS 远程推送

优质
小牛编辑
136浏览
2023-12-01

iOS 远程推送

之前曾经简要整理过iOS远程推送各个回调的一些内容 传送门

感觉脑子不够用了, 很多内容当时整理的不够详细清晰, 记忆也逐渐模糊

考虑很早的一些回调方法明显已经用不上了, 之前的部分文章也失去了回头再看的意义, 所以本篇文章主要以iOS 10.0 +为主, 重新梳理一下, 便于今后翻阅

若这篇文章有描述错误的地方, 还请及时指出


基础业务回调方法总结

一、 iOS 10 + 注册

UNUserNotificationCenter *userNotificationCenter = [UNUserNotificationCenter currentNotificationCenter];
[userNotificationCenter requestAuthorizationWithOptions: UNAuthorizationOptionBadge | UNAuthorizationOptionAlert | UNAuthorizationOptionSound
                                      completionHandler: ^(BOOL granted, NSError * _Nullable error) {
    if (granted) {
        GYDebugLog(@"UNUserNotificationCenter 授权成功");
        dispatch_sync(dispatch_get_main_queue(), ^{
            [[UIApplication sharedApplication] registerForRemoteNotifications];
            userNotificationCenter.delegate = self;
        });
    } else {
        GYWarnLog(@"UNUserNotificationCenter 授权失败");
    }
}];

二、获取DeviceToken

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    // 将来需要将此Token上传给后台服务器
    if (!deviceToken) return;
    NSString *deviceTokenString = @"";
    if ([UIDevice currentDevice].systemVersion.floatValue >= 13.0) {
        const unsigned *tokenBytes = [deviceTokenData bytes];
        NSString *hexToken = [NSString stringWithFormat:@"%08x%08x%08x%08x%08x%08x%08x%08x",
                              ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]),
                              ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]),
                              ntohl(tokenBytes[6]), ntohl(tokenBytes[7])];
        deviceTokenString = hexToken;
        NSLog(@" iOS 13 后 获取到 DeviceToken: %@",deviceTokenString);
    }
    else {
        deviceTokenString = [NSString stringWithFormat:@"%@",deviceTokenData];
        NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:@"\\<\\>"];
        deviceTokenString = [deviceTokenString stringByTrimmingCharactersInSet:set];
        deviceTokenString = [deviceTokenString stringByReplacingOccurrencesOfString:@" " withString:@""];
        NSLog(@" iOS 13 以前 获取到 DeviceToken: %@",deviceTokenString);
    }
}

三、iOS 10 + 使用到的回调方法

  • App处于前台时触发, iOS 10+会出现通知横幅, 而在以前的框架中, 前台运行时, 不会出现通知的横幅. userNotificationCenter:willPresentNotification:withCompletionHandler:
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
  NSDictionary *userInfo = notification.request.content.userInfo;
  // 可以设置当收到通知后, 有哪些效果呈现 (声音/提醒/数字角标)
  completionHandler(UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert);
}
  • App处于后台时触发, 收到推送消息人工干预时触发, 如点击消息栏/横幅 消息userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler {
    NSDictionary *userInfo = response.notification.request.content.userInfo;
    completionHandler();
}
  • 静默推送 或者 AppKill掉点击消息栏/横幅时, 会触发
    application:didReceiveRemoteNotification:fetchCompletionHandler:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
    //静默推送 显示蓝色Label
    completionHandler(UIBackgroundFetchResultNewData);
}
  • AppKill掉, 点击消息栏/横幅时, 会触发 application:didFinishLaunchingWithOptions:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    NSDictionary *remoteUserInfo = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
    if (remoteUserInfo) {
        // 获取到了推送消息
    }
    return YES;
}

注意点

  • 获取DeviceToken, iOS 13起解析的方式发生了改变

  • 通过requestAuthorizationWithOptions:授权, 不设置UNUserNotificationCenterdelegate, 这样还会走之前iOS 9的回调方法

  • userNotificationCenter: willPresentNotification: withCompletionHandler:
    可以通过设置回调参数,根据回调参数内是否传入UNNotificationPresentationOptionAlert,如果有,则在前台收到推送时,也会有弹框提醒,在系统通知栏内可见推送消息,如果不传入,则不会有弹框提醒,系统通知栏不可见 completionHandler( UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionSound );


结合业务 推送触发的回调方法

在重新整理这份资料前, 也查阅了部分网络文章, 部分和我整理的可能不一致

我的结论来源于真机实测, 通过在回调方法中写入日志, 包含触发某一回调的时间、方法名称, 此刻App状态, 以及执行回调时是否包含推送内容, 如果存在疑问欢迎一起探讨, 如果有误, 请及时指出

  • 普通推送 (非静默推送, 非后台模式)

    标准的推送格式, 结合业务设置alertsoundbadge, 并且不包含 content-available

    • AppKill掉, 或从未启动App

      • 除了系统的横幅等默认行为外, 代码层面什么都不会触发

      • 收到推送消息点击App图标启动

        application:didFinishLaunchingWithOptions:获取不到消息, 无法处理业务

      • 收到推送消息点击 横幅或消息栏中的消息 启动App

        触发回调方法:application:didFinishLaunchingWithOptions: -> application:didReceiveRemoteNotification:fetchCompletionHandler:

        1. application:didFinishLaunchingWithOptions:中可以通过UIApplicationLaunchOptionsRemoteNotificationKey获取到消息
        2. 随后在application:didReceiveRemoteNotification:fetchCompletionHandler: 回调也会被执行

        这两个回调都会被调用, 那么在业务上要区分一下, 避免业务重复执行

        对于App未运行时苹果给出的官方描述:
        if the app is not running when a remote notification arrives, the method launches the app and provides the appropriate information in the launch options dictionary. The app does not call this method to handle that remote notification. Instead, your implementation of the application:willFinishLaunchingWithOptions: or application:didFinishLaunchingWithOptions: method needs to get the remote notification payload data and respond appropriately.

    • App 后台挂起

      • 点击消息栏/ 横幅
        触发回调: userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler: 此时App当前状态: UIApplicationStateInactive
    • App后台运行时

      • App处于后台活动状态时(即还未被挂起), 当收到后台推送时不会触发任何回调, 没有执行代码的能力

      • 收到推送消息点击App图标启动

        获取不到消息, 如果要做到准确一致, 需要结合服务端接口处理

      • 收到推送消息点击 横幅或消息栏中的消息 启动App

        触发回调方法:userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:

    • App前台运行时

      • 在前台时会自动触发回调方法: userNotificationCenter:willPresentNotification:withCompletionHandler:

        默认会有横幅展示, 如果不需要可以通过回调控制, 若关闭了横幅, 那么通知消息栏也会不展示这条推送

      • 若开启了Alert, 在执行回调的同时, 若点击了横幅或去消息栏再点击推送消息, 还会触发回调方法:userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:

  • 后台推送

    iOS 7.0开始允许应用收到通知后直接在后台(background)状态下运行一段代码,而无需用户点击,可用于从服务器获取内容更新。

    1. 首先确保Background Mode中除勾选Remote notifications
    2. 后台推送时增加"content-available": 1, 需要注意的是要与alertbadge等字段等同级, 比如放在alert中就无效了
      触发关键回调方法: application:didReceiveRemoteNotification:fetchCompletionHandler:

    3. App前台运行时

      • 与普通正常推送无任何区别, 触发回调方法: userNotificationCenter:willPresentNotification:withCompletionHandler:
        此刻App状态为UIApplicationStateActive

      • 前台收到推送后, 执行完会回调方法, 若此刻点击横幅或消息栏, 还会触发 userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:

    4. App后台运行时

      • 会直接触发回调方法: application:didReceiveRemoteNotification:fetchCompletionHandler:
        此刻App状态为UIApplicationStateBackground

      • 点击App图标回到前台时, 不会再额外触发回调方法

      • 点击消息栏/横幅使APP回到前台时还会触发回调方法: userNotificationCenter:didReceiveNotificationResponse:
        此刻App状态为 UIApplicationStateInactive

    5. App后台挂起

      • App后台挂起收到推送消息什么都不操作会先后触发:

        application:didReceiveRemoteNotification:fetchCompletionHandler:
        此刻App状态为: UIApplicationStateBackground
        userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:
        此刻App状态为: UIApplicationStateInactive

      • 点击App图标回到前台时, 会触发回调方法: application:didReceiveRemoteNotification:fetchCompletionHandler:
        此刻App状态为: UIApplicationStateBackground

      • 点击消息栏/横幅使APP回到前台时会触发回调方法:

        application:didFinishLaunchingWithOptions:
        此刻App的状态为: UIApplicationStateBackground
        application:didReceiveRemoteNotification:fetchCompletionHandler:
        此刻App的状态为: UIApplicationStateBackground
        userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:
        此刻App的状态为: UIApplicationStateInactive

        • 这样在收到推送时, 即便应用在后台挂起, 用户未点击横幅或消息栏, 也是能够执行代码的
          需要注意的是, 当用户点击横幅/消息栏中的消息 或 点击App图标回到前台时, 会重复触发回调方法
          注意根据applicationState区分场景
    6. AppKill

      • 除了系统的横幅等默认行为外, 代码层面什么都不会触发
        但是获取不到任何推送消息

      • 点击App图标启动App
        触发回调方法: applicationDidFinishLaunchingWithOptions:
        此刻App状态为: UIApplicationStateInactive
        但是获取不到任何推送消息

      • 点击横幅或消息栏消息启动App, 触发回调方法:
        applicationDidFinishLaunchingWithOptions:
        此刻App状态为: UIApplicationStateInactive
        并且启动选项中可以获取到消息内容
        application:didReceiveRemoteNotification:fetchCompletionHandler:
        此刻App状态为: UIApplicationStateInactive

  • 静默推送

    关键回调方法:application:didReceiveRemoteNotification:fetchCompletionHandler:

    1. 在后台推送配置基础上, 在aps中除了 "content-available": 1外, 不允许设置alertsoundbadge等字段, 也就代表着不会有任何声音和界面的体现

    2. AppKill掉或者App从未启动, 那么不会触发任何回调, 静默推送失去作用

    3. App处于前台时 触发回调方法: application:didReceiveRemoteNotification:fetchCompletionHandler:
      此刻App状态: UIApplicationStateActive

    4. App处于后台时 触发回调方法: application:didReceiveRemoteNotification:fetchCompletionHandler:
      此刻App状态: UIApplicationStateBackground

    5. App 处于挂起/ 被Kill掉 不触发任何回调, 启动时在application:didFinishLaunchingWithOptions:获取不到推送消息