本文翻译自官网Apple Push Notification Service,由于本人英语水平有限,翻译的不是很准确,读者请见谅,欢迎指出不足之处。
推送通知的核心功能是苹果推送通知服务(简称APNs),它是iOS和OS X设备传输信息的一个可靠的、高效的服务。每台设备将一个可信任和加密的IP和该服务建立连接,通过这个连接接收通知。当应用程序没有运行时,这时候接收到一条通知,设备会弹出提示。
软件服务器(provider)发起通知,通过一个安全可靠的通道与APNs创建连接,传入将用于客户端应用程序的数据。软件服务器通过该通道发送给APNs将要推送的目标设备,目标设备上的应用程序就会接收到一条新数据。
1. 工作机制
APNs远程传输从服务器到设备的数据,一条通知是由设备令牌(devicetoken)和有效负载(payload)两个主要部分组成的一条短信息。设备令牌类似一串手机号码,它包含安装了App并启用APNs的设备信息,通过该令牌可以定位到该设备。APNs使用设备令牌来授权远程通知。有效负载是一个JSON格式的字符串,指定了目标设备如何进行提示。
软件服务器将设备令牌和有效负载打包发送给APNs,APNs再推送给目标设备。
当provider向APNs授权时,会将应用程序的Bundle Identifier发送给APNs服务。
图1-1. 从一个服务器到一个应用程序的推送通知
图1-1是一个非常简单的provider和device直接推送的工作机制,在provider和device方面同时存在多点连接。图1-2是一个更真实的推送工作机制的描述
图1-2. 多个服务器和多台设备之间的推送通知
2. 服务质量
APNs包含一个默认的服务质量(QoS)组件,执行一个存储转发功能。
如果APNs尝试发送一个通知,但是设备处于离线状态,该通知会被存储一段时间,当设备处于在线状态的时候再发送该通知。
一个特定的App只会存储最近的一条通知,如果设备在离线的情况下provider发送多条通知,前面的通知会被丢弃,只保留最后一条。
如果设备长时间处于离线状态,被存储的任何通知都会被丢弃。
3. 安全架构
为了能在provider和device之间通信,APNs揭露某些入口点。但是为了确保安全,必须调整这些入口点。为此,APNs需要对provide和device两个分别验证。
连接验证(Connection trust)包含两个方面,一方面是APNs验证与一个授权发送通知的provider连接;另一方面是APNs验证与一个合法的设备的连接。
在APNs与入口点建立信任之后,必须确保通知传送到一个合法的终点。为此,必须确认消息传送的路线,只有预期的设备才能接受它。
在APNs中,为确保精准的消息传送,需要通过设备令牌进行令牌验证(token trust),设备令牌是设备第一次与APNs连接时,APNs对设备的不透明的标识符。设备与它的provider共享该devicetoken,此后,该令牌包含在provider发送的每一条通知中。
一个特定通知的合法路线是建立信任的基础。
注:设备令牌和设备UDID不是同一件事。
下面讨论连接验证、令牌验证的必要组件,以及建立信任的四个过程。
l APNs与device之间的连接验证
APNs通过TLS与一台连接的设备建立身份验证(注意,这个阶段的系统负责连接验证,你自己不需要做任何事情)。在这个过程,设备启动一个与APNs的TLS连接,APNs返回他的服务器证书,设备验证该证书,然后发送设备证书给APNs,APNs验证该设备证书。如图3-1:
图3-1. APNs与device之间的验证过程
l APNs与provider之间的连接验证
同APNs与device之间的连接验证一样。
图3-2. APNs与provider之间的验证过程
注意:provider连接只对一个特定的App有效,证书中指定了该App的BundleID。APNs同时维护一个撤销证书的列表,如果provider的证书在列表中,APNs可以撤销provider验证(即拒绝连接)。
l 令牌生成和传播
应用程序接收推送通知必须先注册,通常在应用程序安装后。系统收到一条注册请求,然后与APNs连接,传递该请求。APNs使用设备证书生成一个devicetoken,该devicetoken包含设备的标识符,返回设备加密后的devicetoken。如图3-3:
图3-3. 令牌生成和传播过程
应用程序中收到的device token是一个NSData的对象,App然后将devicetoken以二进制或十六进制的形式传递给provider。图3-4中展示了令牌生成和传播的顺序
图3-4. 令牌生成和传播顺序
l 令牌验证(通知)
在系统从APNs获取到device token之后,每一次与APNs的连接必须提供该devicetoken,APNs解密devicetoken并验证令牌是不是连接的设备生成的,然后验证令牌里的设备标识符是否和设备证书里的设备标识符匹配。
provider发送给APNs的每一条通知必须包含device token,APNs通过密钥解密该devicetoken,从而确保通知有效,然后使用devicetoken中的deviceID确定通知的目标设备。
图3-5. 令牌验证过程
l 验证组件
为了支持APNs的安全模式,provider和device必须拥有确信的证书,授权证书、或者令牌。
Provider:每一个provider需要有一个独特的证书和私钥验证与APNs的连接,这个证书由Apple公司提供。对于每一条通知,provider必须提供目标设备的devicetoken给APNs,provider可以选择性的是否验证APNs。
Device:系统使用包含密钥和证书的公共服务器证书与APNs建立TLS连接,获得设备激活过程中的设备证书和密钥,将其存储在钥匙串中。该系统还拥有其特定的devicetoken,并负责将此devicetoken传递给provider。
4. 通知的有效负载
每一条推送通知都包含一个有效负载(payload),该payload包含如何让系统弹出提示的信息,以及一些provider自定义的数据。一条通知的payload的最大尺寸为256个字节,APNs拒绝任何超出该限制的通知。
对于每一条通知,都是一个JSON类型的字典对象。这个字典必须通过“aps”key包含另外一个字典。这个aps字典包含下面一个或多个属性:
l 向用户显示的提示信息
l App 图标上显示的数字
l 弹出通知的声音
当通知到来的时候App没有运行,弹出提示信息,播放声音,App图标显示badge的值;如果App处于运行中,系统将通知作为一个NSDictionary对象传递给application的delegate。
Provider可以在保留字“aps”外自定义有效负载值,自定义的值必须使用JSON结构和基本类型:dictionary,array,string,number,Boolean。你不应该包括客户信息(或任何敏感数据)作为自定义的有效负载数据。相反,将其用于多种用途,如设置上下文(用户界面)或内部度量。
表4-1. aps字典的keys和values
Key | Value type | Comment |
alert | string or dictionary | 如果包含该属性,系统展示标准的提示,你可以指定一个字符串或者一个字典作为提示的信息,如果值是一个字符串,它将成为提示的文本信息,提示包括两个按钮:关闭和查看。点击查看,启动应用程序。如果值是一个字典,看表4-2对该字典的描述。 |
badge | number | 应用程序图标上显示的数字。 如果该属性的值没有设置,那不改变图标上的数字。设置该属性的值为0可以移除。 |
sound | string | 应用程序包里的声音文件名。如果设置的声音文件不存在,或者指定了default,则播放默认的声音。 |
content- available | number | 该值为1表明新内容有效。该属性用在支持杂志和后台下载的App中。 杂志App是保证在每24小时能收到至少以一条推送通知。 |
表4-2. alert字典的keys和values
Key | Value type | Comment |
body | string | 提示的文本信息 |
action- loc-key | string or null | 如果指定一个字符串,系统显示带有两个按钮的提示框,其行为如表4-1。该字符串作为一个key去获取本地字符串,然后取代“查看”作为右边按钮的标题。 |
loc- key | string | Localizable.strings文件中的一个key,该字符串可以用%@和%n$@ ,loc-args中指定的变量格式化。 |
loc- args | array of strings | 出现在loc-key的格式说明符。 |
launch- image | string | 程序包里的一张图片的文件名,可以包括文件扩展名或者不包括。该图片作为用户点击按钮或滑动滑块的启动图片。如果没有指定该属性,系统Info.plist文件中通过UILaunchImageFile key指定的图片,或者使用Default.png。 |
注:如果你想设备显示带关闭和查看按钮的提示框,指定alert的类型为string,不要指定alert的类型为字段,尽管字典中只有一个body属性。
5. 格式化本地字符串
你可以用两种方式显示本地提示信息,服务器发起的通知定位文本,为此,必须知道当前设备的语言环境;或者客户端应用程序在其包里支持的本地化文件中转换提示信息。provider在通知的有效负载aps字典中指定loc-key和loc-args属性的值,当device接收到通知(假设App不在运行中),使用aps字典属性根据当前语言环境查找本地化文件并格式化字符串,然后显示给用户。
第二种选项工作的更多细节。
应用程序可以按照其支持的每种语言国际化资源文件,比如图片、音频、文本,国际化的资源文件放在包里一个子目录下,目录名称由两部分组成:语言代码和“.lproj”扩展名(例如fr.plroj)。本地化字符串被放在Localizable.strings文件中,文件中的每一个实体都有一个key和string类型的值,该string可以使用格式说明符替换变量值。
注:当action-loc-key属性是一个字符串的时候同样适用,在Localizable.strings文件中设置这个key的值,就能修改提示框右边按钮的标题。
举例说明,provider指定了alert属性的值:
"alert" : { |
"loc-key" : "GAME_PLAY_REQUEST_FORMAT", |
"loc-args" : [ "Jenna", "Frank"] |
} |
当device收到通知,使用"GAME_PLAY_REQUEST_FORMAT"作为key在Localizable.strings文件中寻找对应的值。假设文件中存在该key的值:
"GAME_PLAY_REQUEST_FORMAT"= "%@ and %@ have invited you to play Monopoly";
设备显示“Jennaand Frank have invited you to play Monopoly”提示信息。
注:只有你完全需要的时候才应该使用字典类型的alert的loc-key和loc-args属性,许多App不需要这两个属性。
6. JSON格式的有效负载例子
注:为了可读性,下面的例子用空格和断行加以修饰。实际当中省略这些来减少有效负载的大小,提升网络性能。
例1:下面是一个简单的有效负载,aps字典中只带有一个提示信息,当然默认的还有两个按钮(关闭和查看)。该有效负载还带有一个自定义的数组。
{ |
"aps" : { "alert" : "Message received from Bob" }, |
"acme2" : [ "bang", "whiz" ] |
} |
例2:下面的例子自定义了alertView右边按钮的title,此时,“PLAY”是本地文件 Localizable.strings中的key,同时设置appicon的badge数量为5.
{ |
"aps" : { |
"alert" : { |
"body" : "Bob wants to play poker", |
"action-loc-key" : "PLAY" |
}, |
"badge" : 5, |
}, |
"acme1" : "bar", |
"acme2" : [ "bang", "whiz" ] |
} |
例3:下面的例子弹出一个带有关闭和查看的提示框,以及设置appicon的badge数量为9,并且当接收到通知的时候播放声音。
{ |
"aps" : { |
"alert" : "You got your emails.", |
"badge" : 9, |
"sound" : "bingbong.aiff" |
}, |
"acme1" : "bar", |
"acme2" : 42 |
} |
例4:下面的例子使用loc-key和loc-args 。
{ |
"aps" : { |
"alert" : { |
"loc-key" : "GAME_PLAY_REQUEST_FORMAT", |
"loc-args" : [ "Jenna", "Frank"] |
}, |
"sound" : "chime" |
}, |
"acme" : "foo" |
} |