iOS推送流程(APNS)

iOS推送流程(APNS)

一、APNS(Apple Push Notification Service)

苹果推送通知服务(APNs)是推送通知的网关,iPhone ipad 对于应用程序在后台运行有诸多限制,考虑到手机电池电量,应用不允许在后台进行过多的操作。因此,当用户切换到其他程序后,原先的程序无法保持运行状态。对于那些需要保持持续连接状态的应用程序(比如社区网络应用),将不能收到实时的信息。推送是解决轮询所造成的流量消耗和电量消耗的一个比较好的解决方案

为解决这一限制,苹果推出了APNs(苹果推送通知服务 Apple Push Notification services)。APNs 允许设备与苹果的推送通知服务器保持常连接状态。当你想发送一个推送通知给某个用户的iPhone上的应用程序时,你可以使用 APNs 发送一个推送消息给目标设备上已安装的某个应用程序。

苹果的推送服务APNs基本原理简单来说就是苹果利用自己专门的推送服务器(APNs)接收来自我们自己应用服务器的需要被推送的信息,然后推送到指定的iOS设备上,然后由设备通知到我们的应用程序,设备以通知或者声音的形式通知用户有新的消息。推送的前提是装有我们应用的设备需要向APNs服务器注册,注册成功后APNs服务器会返给我们一个device_token,拿到这个token后我们将这个token发给我们自己的应用服务器,当有需要被推送的消息时,我们的应用服务器会将消息按指定的格式打包,然后结合设备的device_token一并发给APNs服务器,由于我们的应用和APNs维持一个基于TCP的长连接,APNs将新消息推送到我们设备上,然后在屏幕上显示出新消息来。
iOS推送流程(APNS)_第1张图片
我们的设备和APNS服务器之间的通讯是基于SSL协议的TCP流通讯,二者之间维持一个长连接。
iOS推送流程(APNS)_第2张图片
上图显示的这个消息体就是我们的服务器(Provider)发送给APNS服务器的消息结构,APNS验证这个结构正确并提取其中的信息后,再将消息推送到指定的设备。

//Payload内容
{
    "aps":{
        "alert":"一些文字",
        "badge":"1",
        "sound":"default",
    }
}

这其实就是个JSON结构体,alert标签的内容就是会显示在用户手机上的推送信息,badge显示的数量(注意是整型)是会在应用Icon右上角显示的数量,提示有多少条未读消息等,sound就是当推送信息送达是手机播放的声音,传defalut就标明使用系统默认声音,如果传比如“beep.wav”就会播放在我们应用工程目录下名称为beep.wav的音频文件,比如当手机锁屏时QQ在后台收到新消息时的滴滴声。

有这么一种情况,当我们将应用从设备卸载后,推送的消息改如何处理呢。我们知道,当我们将应用从设备卸载后,我们是收不到Provider给我们推送的消息的,但是,如何让APNS和Provider都知道不去向这台卸载了应用的设备推送消息呢?针对这个问题,苹果也已经帮我们解决了,那就是Feedback service。他是APNS的一部分,APNS会持续的更新Feedback service的列表,当我们的Provider将信息发给APNS推送到我们的设备时,如果这时设备无法将消息推送到指定的应用,就会向APNS服务器报告一个反馈信息,而这个信息就记录在feedback service中。按照这种方式,Provider应该定时的去检测Feedback service的列表,然后删除在自己数据库中记录的存在于反馈列表中的device_token,从而不再向这些设备发送推送信息。连接Feedback service的过程同样使用Socket的方式,连接上后,直接接收由APNS传输给我们的反馈列表,传输完成后断开连接,然后我们根据这个最新的反馈列表在更新我们自己的数据库,删除那些不再需要推送信息的设备的device_token。从Feedback service读取的数据结构如下:
iOS推送流程(APNS)_第3张图片
结构中包含三个部分,第一部分是一个时间戳,记录的是设备失效后的时间信息,第二个部分是device_token的长度,第三部分就是失效的device_token,我们所要获取的就是第三部分,跟我们的数据库进行对比后,删除对应的device_token,下次不再向这些设备发送推送信息。

二、配置推送准备工作

首先要有一台苹果的设备,模拟器是不支持推送的,所以你需要一台iphone,ipod touch或者ipad。

准备CSR文件,生成带有Push Notifications功能的AppID,给该AppID的Push Notifications配置CSR,下载证书配置钥匙串,导出p12文件。
其中

证书: 内容是公钥或私钥,由其他机构对其签名组成的数据包。
Entitlements: 包含了 App 权限开关列表。
CertificateSigningRequest: 本地公钥。
p12: 本地私钥,可以导入到其他电脑。
Provisioning Profile: 包含了 证书 / Entitlements 等数据,并由苹果后台私钥签名的数据包。
  • CSR文件:
    首先我们要通过证书助手生成一个Certificate Signing Request(也就是CSR)的请求文件。
    iOS推送流程(APNS)_第4张图片
    iOS推送流程(APNS)_第5张图片
    继续之后选择保存位置,点击保存
    这时该位置上会有一个CertificateSigningRequest.certSigningRequest的请求文件,也就是我们说的CSR文件。
  • 生成带有Push Notifications功能的AppID
    iOS推送流程(APNS)_第6张图片
    在此处点+添加,按需求填写信息即可。
    iOS推送流程(APNS)_第7张图片
    iOS推送流程(APNS)_第8张图片
    这里的bundleID是识别APP的唯一ID,一个APP对应一个APP ID 也就是一个bundleID,一般采用域名反写的方式命名,输入部分的英文建议如此。
    当然也可以一个APP ID对应多的应用,为多个APP设定同一个bundleID,使用下面的Wildcard App ID设定,但是会少一些功能,例如游戏中心,内购,数据保护,iCloud。
    iOS推送流程(APNS)_第9张图片
    这里要把Push Notifications勾选上,应用才能带有推送能力。
    全部设定好之后继续,保存,生成了一个新的APP ID
    PS:当然一开始没有选上也没有关系,在我们生成的APP ID后,选择编辑,也会有勾选框可以选择增加能力。
  • 给该AppID的Push Notifications配置CSR:
    iOS推送流程(APNS)_第10张图片
    这部分是为这个APP ID配置push notifications,一个SSL Certificate可以让你手机上的notification server连接到Apple Push Notification Service(APNS)
    可以看到配置部分有两个:
    Development SSL Certificate:开发推送证书配置,开发环境可以收到推送。
    Production SSL Certificate:生辰推送证书配置,线上环境可以收到推送。
    点击Create Certificate,看完了About Creating a Certificate Signing Request (CSR)页,到达配置页
    iOS推送流程(APNS)_第11张图片
    选取之前生成的CSR文件:CertificateSigningRequest.certSigningRequest点击continue即可配置完成。
  • 下载推送证书,加到自己电脑上的钥匙串里:
    iOS推送流程(APNS)_第12张图片
    点击Download,下载开发和生产的推送证书。
    这里写图片描述
    双击两个证书,会自动添加在钥匙串里去,
    iOS推送流程(APNS)_第13张图片
    找到自己的APP(后面的名字为当时设定的bundleID),右键导出p12文件,填上密码,保存。
    iOS推送流程(APNS)_第14张图片
    iOS推送流程(APNS)_第15张图片
    生成的这个p12文件,通常是给推送平台配置(自己的推送平台,或第三方推送平台,友盟等),以便他们能够发送推送通知到APNS。

这样,全部配置方面的事情就做完了。

三、App里处理推送

iOS处理生命周期和推送都在AppDelegate里面,AppDelegate继承自UIResponder,遵守UIApplicationDelegate协议,其中跟推送相关的方法都在UIApplicationDelegate协议中。包含本地推送和远程推送。

由于iOS不断更新,推送相关的配置方法也不断变化,iOS8,iOS 10分别更新了一些方法,废弃了一系列方法。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
     [self registPushNotification:application];
     //程序未启动时,收到远程推送,保存下来,在应用进入前台后可以处理。
     _launchNotification = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
}

- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
    [application registerForRemoteNotifications];
}

- (void)registPushNotification:(UIApplication *)application
{
    if ([application respondsToSelector:@selector(registerForRemoteNotifications)]) { // ios8+的远程通知注册
        UIUserNotificationType types = UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert;
        UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
        [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
        [application registerForRemoteNotifications];
    }
    else { // ios8以下
        SUPPRESS_DEPRECATED_WARNING([application registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)]);
    }
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{
    [self processLocalNotification:userInfo];

}

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{

}

上面的方法,可以让推送达到正常使用的水平。

  1. 调用流程:
    • 应用启动
    • 注册配置
    • 注册推送
    • 注册deviceToken
    • 收到推送进行处理
  2. 推送处理流程
    处理推送必须是通过点击通知进入,收到通知,点应用进入不会触发通知处理的方法。

    • 程序未启动,收到通知,系统展示通知栏,点击进入:
      此时程序启动,会响应- (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary )launchOptions 方法,在该方法中,可以通过[launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];拿到未启动时收到的推送信息,之后怎么处理,就按需求来了。
    • 程序已启动,在后台,收到通知,系统展示通知栏,点击进入:
      此时程序会响应- (void)application:(UIApplication )application didReceiveRemoteNotification:(NSDictionary )userInfo方法,userInfo就是需要信息内容。
    • 程序已启动,在前台,收到通知,系统不展示通知栏:
      此时程序会响应- (void)application:(UIApplication )application didReceiveRemoteNotification:(NSDictionary )userInfo方法,userInfo就是需要信息内容。

    可以看到程序在启动的情况下,不管在前台还是后台,都会进入到- (void)application:(UIApplication )application didReceiveRemoteNotification:(NSDictionary )userInfo方法中,区别只是系统会不会进行通知栏弹窗,如果想要前台弹窗,就需要自己去做效果了。
    另外可以通过application.applicationState != UIApplicationStateActive来判断当前app前后台的状态,进而分别处理。

  3. iOS 10推荐的推送处理方式:
    优点:

    • 通知处理代码可以从AppDelegate中剥离。
    • 通知的注册,设置,处理更加结构化,更易于模块化开发。
    • UserNotification支持自定义通知音效和启动图。
    • UserNotification支持向通知内容中添加媒体附件,例如音频,视频。
    • UserNotification支持开发者定义多套通知模板。
    • UserNotification支持完全自定义的通知界面。
    • UserNotification支持自定义通知中的用户交互按钮。
    • 通知的触发更加容易管理。

    引入

    #import 

使用UNUserNotificationCenterDelegate协议里的方法,
iOS推送流程(APNS)_第16张图片

具体代码中:

- (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary )launchOptions{
    UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
        // 必须写代理,不然无法监听通知的接收与点击
        center.delegate = self;
        [center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert | UNAuthorizationOptionBadge | UNAuthorizationOptionSound) completionHandler:^(BOOL granted, NSError * _Nullable error) {
            if (granted) {
                // 点击允许
                NSLog(@"注册成功");
                [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
                    NSLog(@"%@", settings);
                }];
            } else {
                // 点击不允许
                NSLog(@"注册失败");
            }
        }];
}


// iOS 10收到通知
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler{
    NSDictionary * userInfo = notification.request.content.userInfo;
    UNNotificationRequest *request = notification.request; // 收到推送的请求
    UNNotificationContent *content = request.content; // 收到推送的消息内容
    NSNumber *badge = content.badge;  // 推送消息的角标
    NSString *body = content.body;    // 推送消息体
    UNNotificationSound *sound = content.sound;  // 推送消息的声音
    NSString *subtitle = content.subtitle;  // 推送消息的副标题
    NSString *title = content.title;  // 推送消息的标题

    if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
        NSLog(@"iOS10 前台收到远程通知:%@", [self logDic:userInfo]);

    }
    else {
        // 判断为本地通知
        NSLog(@"iOS10 前台收到本地通知:{\\\\nbody:%@,\\\\ntitle:%@,\\\\nsubtitle:%@,\\\\nbadge:%@,\\\\nsound:%@,\\\\nuserInfo:%@\\\\n}",body,title,subtitle,badge,sound,userInfo);
    }
    completionHandler(UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionSound|UNNotificationPresentationOptionAlert); // 需要执行这个方法,选择是否提醒用户,有Badge、Sound、Alert三种类型可以设置,Alert可以设定前台展示通知栏。
}

你可能感兴趣的:(objective-c)