iOS推送机制详解

简介

APNs 是 Apple Push Notification service 的简称 苹果推送通知服务
为什么会有 APNs ?
由于移动设备内存、CPU、电量的局限性,iOS 不允许 APP 的进程常驻后台(事实上可以申请后台运行一段时间,最长约10分钟)。
当用户主动杀掉APP或者APP进入后台超过限定时长时,就意味着该APP进程的结束。这很大程度保障了前台APP的流畅性,也延长了手机的使用时长。这也是苹果用户体验好的原因之一。但是这也意味着,服务器无法主动和用户交互(如推送实时消息等)。为了解决这个限制,苹果推出了APNs,允许设备和服务器分别与苹果的推送通知服务器保持长连接状态。
假如我们的苹果设备(PC电脑、笔记本、iPad、iPhone等)安装了一个名叫XXX的应用。那么我们具体是怎样接收到推送通知的?流程如下图:


image.png

安全结构

APNs 采用双层信任机制:连接信任 和 device token(设备令牌) 信任

连接信任:服务器(Provider)与 APNs 之间 | APNs 与设备之间

服务器与 APNs :有两种方式建立连接信任

1.基于令牌(JWT: Json Web Tokens)的连接信任

Provider只需要提供一对公私钥(私钥Provider自己保存,公钥苹果保存),并使用其中的私钥生成并加密JWT Token,每次向APNs请求推送的时候带上这个Token即可。
image.png
  • Provider通过TLS向APNs发起请求。
  • APNs返回一个证书给Provider。
  • Provier验证这个证书。通过后,发送push数据并带上JWT token。
  • APNs验证token,并返回请求的结果。
TLS:为了保证数据传输安全,在HTTP层与TCP层之间插入的一个安全校验层

基于令牌的连接方式需要注意:应该定时更新token,token的有效期为1小时。

  • 对于基于JWT的方式,能每次请求都创建新的token,尽量在一小时内使用同一个token。
  • 不能频繁的建立、关闭连接,否则APNs会把这当做是黑客攻击,拒绝访问。应该尽量将连接保活,直到你认为这个连接接下来会长时间不使用为止。
  • 当需要发送大量的推送数据的时候,可以同时创建多个连接,以改善性能。
  • 当吊销证书或者token时,应该关闭所有相关的连接。

2.基于证书的连接信任

这种方式是指Provider可以采用一个唯一的证书以及一个加密的私钥来与APNs交互,其中证书是由苹果产生的(通过苹果账号登录到developer account配置创建)。

image.png

  • Provider通过TLS向APNs请求连接。
  • APNs向Provider返回一个APNs证书。
  • Provider需要把你之前从开发者帐户中生成的证书发给 APNs
  • APNs 验证服务器提供的证书是否正确,如果正确,则确定服务器与 APNs 的信任连接。此时服务器可以向 APNs 发送推送请求了。
APNs 与每个设备之间的连接是自动建立的,这个过程中,不需要你的 app 做什么。

每个设备都有一个加密的证书和私有密钥,在设备激活的时候生成,并保存在设备的 keychain 中,在设备激活的时候,APNs 根据设备的证书和密钥验证并授权与设备之间的连接。
image.png
  • 当设备启动与 APNs 的 TLS 连接时,信任协商开始(图中顶部的箭头)。
  • APNs 将 APNs证书 返回给设备。
  • 操作系统验证此证书后,它会向 APNs 发送设备证书(图“设备证书”中的箭头)。
  • 最后,APNs 验证设备证书并建立信任(图中底部的箭头)。

设备token的生成与分发

什么是deviceToken ?
deviceToken是一个APP装在一个设备上的唯一标识符。一个APP在不同的设备上deviceToken不一样;一个APP删除之后再次安装deviceToken也不一样。

在项目代码AppDelegate里面有一个回调方法,当APNs注册成功通过该回调方法可以获取到返回的deviceToken

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

上面说过当有消息需要被推送时,我们自己的服务器会将消息按指定的格式结合设备的deviceToken一并打包 ,APNs拿到这个包之后验证这个包结构是否正确并提取其中的信息后,再将消息推送到指定的设备。

在app启动的时候,必须向iOS系统注册远程推送,成功后,苹果将会返回一个设备token给app,此时app就可以将这个token上报给自己的后台。

如果有必要产生一个新的token,APNs会使用设备证书生成一个token(其中包含了一个设备ID),并使用token key加密后返回给设备。同时设备会将这个token以NSData对象的形式返回给app,app获取到该token之后应该将其发送到自己后台,后台之后就可以通过这个token来发送推送数据。过程如下图:
image.png
  • 设备自动将用户的UDID 和应用的 AppID发送到APNs,APNs返回一个deviceToken给iOS设备
  • 设备将deviceToken发送到自己公司的后台服务器,用以保存;
  • 当后台需要推送消息给用户时,服务器将需要推送的消息和deviceToken传输给APNs;
  • APNs将消息推送给指定的deviceToken对应的手机;
  • 用户接收消息;

接下来就来看看这个包的结构:

image

这个包分五个部分

  • 第一个部分是命令标示符
  • 第二个部分是我们的deviceToken的长度
  • 第三个部分是我们的deviceToken字符串
  • 第四个部分是推送的消息体(Payload)的长度
  • 最后一个部分也就是真正的消息内容了,里面包含了推送消息的基本信息。(比如消息内容,应用icon右上角显示的数字角标以及推送消息到达时所播放的声音等)

当应用从设备卸载后,推送的消息改如何处理??

我们知道当应用从设备卸载后,是收不到推送消息的。但是,如何让APNs和Provider知道不再向这台卸载了应用的设备推送消息呢?

Feedback Service。它是APNS的一部分,APNs会持续的更新Feedback service的列表,当我们的Provider将信息发给APNs服务器,APNs推送信息到我们的设备时,如果这时设备无法将消息推送到指定的应用(应用已经删除),就会向APNs服务器发送一个反馈信息,而这个信息就记录在Feedback service中。按照这种方式,Provider应该定时的去检测Feedback service的列表,然后删除在自己数据库中记录的存在于反馈列表中的deviceToken,从而不再向这些设备发送推送信息。连接Feedback service的过程同样使用长连接的方式,连接上后,直接接收由APNs传输给我们的反馈列表,传输完成后断开连接,然后我们根据这个最新的反馈列表在更新我们自己的数据库,删除那些不再需要推送信息的设备的deviceToken。

从Feedback service读取的数据结构如下:

image

结构中包含三个部分

  • 第一部分是一个时间戳,记录的是设备失效后的时间信息
  • 第二个部分是deviceToken的长度
  • 第三部分就是失效的deviceToken,我们所要获取的就是第三部分,跟我们的数据库进行对比后,删除对应的deviceToken,下次不再向这些设备发送推送信息

deviceToken注意:不是设备唯一标识,是设备和应用组合的唯一标识。同一个手机不同的应用会生成不同的deviceToken,同一个手机同一个应用只生成一个deviceToken。在iOS7.0和iOS8.0的系统中,卸载重装应用,deviceToken不会发生变化,但是iOS9.0(包括)以后卸载重装应用,deviceToken会发生变化。

服务器的职责

服务器在跟 APNs 沟通连接的时候具有以下责任:

  • 通过 APNs 接收并管理关于你的app的全球唯一的设备验证码,及其它一些数据
  • 根据app功能的需要,决定通知推送的时间
  • 建立通知请求,并发送通知请求到 APNs, APNs 再将通知递送到相应的设备

推送内容:

远程推送通知,分为 普通推送 / 后台推送 / 静默推送 3 种类型

  • 普通推送
    • 就是我们在手机上平时见到的推送通知。
    • 包含声音、横幅、角标、自定义字段。
    • 处于前台,不会展示横幅,可通过 didReceiveRemoteNotification(iOS 7 before)didReceiveRemoteNotification:fetchCompletionHandler:(iOS 7 after)获取通知内容(前台展示横幅的方法看这里)。
    • 处于后台,会展示横幅,无法获取通知内容。
    • 处于退出,会展示横幅,无法获取通知内容。
    • 点击图标启动,无法获取通知内容。
    • 点击通知横幅启动,在 didFinishLaunchingWithOptions 获取通知内容。
{
 aps =     {
   alert = "显示内容";
   badge = 1;  // App 角标, 1)当badge>0时,角标会随badge而变化。2)当badge=0时,角标维持不变。3)当badge<0时,角标维持不变。
   sound = default;  // 推送声音,默认系统三全音,如需使用自己的声音,需要将声音文件拖拽&拷贝至 Xcode 工程目录任意位置,并在推送时指定其文件名
 };
 key1 = value1;  // 自定义字段,可设置多组,用于处理内部逻辑
 key2 = value2;
}
  • 后台推送
    • 各种显示效果跟普通推送完全一样。
    • 必须携带 "content-available" = 1;
    • 必须携带 alert、badge、sound 中 至少 1 个字段。
    • 仅 iOS 7 以后支持。
    • 必须在 Xcode 工程中 TARGETS – Capabilities – Background Modes – Remote notifications 开启该功能,具体可参照 iOS 7 Background Remote Notification
    • 处于前台,可通过didReceiveRemoteNotification(iOS 7 before)didReceiveRemoteNotification:fetchCompletionHandler:(iOS 7 after) 获取通知内容。
    • 处于后台,可通过 didReceiveRemoteNotification:fetchCompletion Handler: 获取通知内容 // 获取情况中与普通推送的唯一不同点,此时 iOS 系统允许开发者在 App 处于后台的情况下,执行一些代码,大概提供几分钟的时间,可以用来偷偷的刷新 UI、切换页面、下载更新包等等操作。
    • 处于退出,无法获取通知内容。
    • 点击图标启动,无法获取通知内容。
    • 点击推送横幅启动,在 didFinishLaunchingWithOptions 获取通知内容。
    {
      aps =     {
        alert = "显示内容";
        badge = 1;
        "content-available" = 1;  // 必带字段
        sound = default;
      };
      key1 = value1;
    }
    
  • 静默推送
    • 没有任何展示效果。
    • 必须携带 "content-available" = 1;,因此静默必然是后台的。
    • 必须不携带 alert、badge、sound。
    • 可携带自定义字段。
    • 处于前台,可通过didReceiveRemoteNotification(iOS 7 before)didReceiveRemoteNotification:fetchCompletionHandler:(iOS 7 after) 获取通知内容。
    • 处于后台,可通过 didReceiveRemoteNotification:fetchCompletion Handler: 获取通知内容 // 获取情况中与普通推送的唯一不同点,此时 iOS 系统允许开发者在 App 处于后台的情况下,执行一些代码,大概提供几分钟的时间,可以用来偷偷的刷新 UI、切换页面、下载更新包等等操作。
    • 处于退出,无法获取通知内容。
{
    aps =     {
    alert = "";
    "content-available" = 1;  // 必带字段
    };
    key1 = value1;
}

新旧协议:基于二进制的旧 APNs 协议、基于 HTTP/2 的新 APNs 协议:

新旧协议的对比:

旧协议 新协议
连接不稳定 长连接,提供检测是否可用的方法
发送给APNs成功没有相应 成功失败均有相应
最大支持256字节 最大4k
- Request 和 Response 支持JSON网络协议
不同的推送,需要配置不同的推送证书 不同推送类型,只需要一种推送证书 Universal Push Notification Client SSL 证书
image.png

image.png

APNs基于 HTTP/2 的新 APNs 协议服务推出时间:
image.png

新 APNs 协议仍然存在的问题:不能保证推送肯定到达手机。当 APNs 向你发送了多条推送,但是你的设备网络状况不好,在 APNs 那里下线了,这时 APNs 到你的手机的链路上有多条任务堆积,APNs 的处理方式是,只保留最后一条消息推送给你,然后告知你推送数。那么其他三条消息呢?会被APNs丢弃。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354