陈斌彬的技术博客

Stay foolish,stay hungry

iOS 推送简介

在移动应用中,推送已经成为不可缺少的重要功能。本文档主要介绍有关 iOS 推送的基础知识,并从几个典型问题出发,分析如何解决在实现推送中出现的一些问题。

1 本地通知和远程通知简介

在 iOS 设备上(模拟器无法使用推送),系统收到通知后这样处理:

  • 在屏幕上弹出一些选项,或者在屏幕顶部显示横幅(banner)如下图左
  • App 的角标数值发生变化,具体表现为 App icon 右上角的小红点及数字,如邮件中的红点
  • 伴随推送消息的提示声音

当应用处于前台运行时,系统是不会在屏幕上显示通知,但是仍会调用相应的 API。

只有真机可以使用推送功能。

用户可以设置每一个 App 的通知权限,如下图最后一个:

img

用户可以选择关闭某个应用的推送功能。还可以设置通知是否在通知中心显示、通知到达时是否发出声音、通知能否改变 App 角标以及锁屏时是否显示该 App 的通知,还可以设置通知到达时的提醒样式。当然,这些都可以分别对每一个应用进行单独设置。

1.1 本地通知

本地通知是一种基于时间的提醒方式。

本地通知最多向系统注册 64 个,当超过这个数量后,最早注册的本地通知会被丢弃。

本地通知在 iOS 设备上的显示与远程推送通知一样。

1.2 远程通知

iOS App 运行在后台时,无法主动进行网络连接。

远程通知可以用来提醒用户。发送远程通知时,服务器首先需要使用推送证书与 APNs(Apple Push Notification service)建立安全连接,然后将消息传递给它。当 APNs 收到消息后,会通过与手机之间的长连接下发到对应的手机上,然后 iOS 弹出这条消息来提醒用户。

用户看通过点击或滑动通知来运行 App。可通过程序中相应的方法可以获取通知信息,然后做相应的逻辑处理。

如果不是通过通知启动 App,那么无法在程序中获取通知信息。例如通过点击应用图标启动 App。

推送通知都包含 Payload:一个 Apple 已经定义好的属性列表,操作系统根据它决定使用哪一种方式来提醒用户。还可以在 Payload 中加入一些自定义数据。

通知并不能一定到达。如果在用户无法收到推送通知时(关机或网络不可用),这种情况下 APNs 收到了多条发往这台设备的通知,那么只会保留最后一条,最早的会被丢弃。

APNs 首先通过移动蜂窝网络发送通知,只有当移动蜂窝网络不可用时才使用 Wi-Fi。

2 通知的两种推送环境

在使用 iOS 远程推送功能时,有两种不同的环境。开发环境(Development)以及生产环境(Production)。

App 当前使用的推送环境与 Xcode - Build Settings - Code Signing - Provisioning Profile 文件的模式一致。

2.1 证书与证书校验

与 APNs 之间是加密的连接,因此需要使用证书来加密连接。每个的推送环境有自己单独的推送证书,即开发证书和生产证书。

在将证书最终转为 pem 格式后,可通过与 APNs 连接来测试证书是否有效。

开发环境:

openssl s_client -connect gateway.sandbox.push.apple.com:2195 -cert MyApnsDev.pem

生产环境:

openssl s_client -connect gateway.push.apple.com:2195 -cert MyApnsPro.pem

当输入完命令回车后,终端首先会输出很多相关信息。

当连接建立失败时,会直接 close 掉。

当连接建立成功时,终端会停止输出,并等待你输入,你可以随便输入一些字符后摁回车,然后连接才会关闭。

以上命令在 Mac 下没有问题,在其他操作系统下需要指定服务器当前的 CA 根证书,具体可以从网站上下载:

entrust_2048_ca.cer 下载

下载完成之后,在以上命令后面加上 -CAfile entrust2048ca.cer 即可

2.2 DeviceToken

通知需推送到具体某一台设备,而 DeviceToken 就是这台设备的标识符。在向 APNs 发送推送通知时,需要使用 DeviceToken 来指定这条通知将要到达的设备 。

每一台设备,不同的推送环境下分别有一个 DeviceToken。即 DeviceToken 也分开发环境和生产环境。

应用在向 APNs 注册推送通知时,会根据当前 Xcode 工程中 target 对应的 Provisioning Profile 决定请求对应环境的 DeviceToken。即系统返回的 DeviceToken 其环境与 Provisioning Profile 的环境对应。

一般在使用 Xcode 开发时,Provisioning Profile 为 Development ,因此获取的 DeviceToken 也是开发模式。

在 App 需要打包为 ipa 文件或者需要上传到 AppStore 时,会将 Provisioning Profile 文件修改为 Distribution,这时获取到的 DeviceToken 是生产模式。

3 在应用程序中注册远程推送功能

App 必须要向 APNs 请求注册以实现推送功能,在请求成功后,APNs 会返回一个设备的标识符即 DeviceToken 给 App,服务器在推送通知的时候需要指定推送通知目的设备的 DeviceToken。在 iOS 8 以及之后,注册推送服务主要分为四个步骤:

  1. 使用 registerUserNotificationSettings:注册应用程序想要支持的推送类型
  2. 通过调用 registerForRemoteNotifications方法向 APNs 注册推送功能
  3. 请求成功时,系统会在应用程序委托方法中返回 DeviceToken,请求失败时,也会在对应的委托方法中给出请求失败的原因。
  4. 将 DeviceToken 上传到服务器,服务器在推送时使用。

上述第一个步骤注册的 API 是 iOS 8 新增的,因此在 iOS 7,前两个步骤需更改为 iOS 7 中的 API。

DeviceToken 有可能会更改,因此需要在程序每次启动时都去注册并且上传到你的服务器端。

注意:iOS 设备与 APNs 需要建立一条长连接,之后的推送注册以及推送获取都需要通过这条长连接。

当 iOS 设备无网络可用时,在向 APNs 请求注册后,既不会有注册成功的回调,也不会有注册失败的回调。如果发生这种情况,需要检查设备的网络连接。

开发环境和生产环境建立不同的长连接,且设备上要至少有一个开发环境的 App 并且注册推送,才会建立开发环境下的长连接。

当已经注册过推送服务后,以后系统会立刻返回给你 DeviceToken。还有一点是,返回 DeviceToken 的回调方法并不一定是只有在你注册或者再次注册时才被系统调用,当DeviceToken 改变时也会被系统直接调用。

4 在程序中处理通知

我们来看一下当系统传递本地或远程通知的时候都有哪些情况。

通知到达时,应用不在前台。

这种情况下,操作系统将呈现通知,弹出一个选项或者在屏幕上部显示 banner 提醒,给 App 角标设置对应的数字,可能播放提示音,当用户稍微往下滑动通知时显示更多的 action 按钮(如果有设置)。

通知到达时,应用在前台运行。

这种情况下,不会弹出 banner。系统会根据当前的通知是本地通知还是远程通知,调用不同的方法,并且在方法中传递通知的 Payload 数据。

在 iOS8 的通知中,用户点击通知上的自定义 action 按钮进入应用。

这种情况下,系统会调用 iOS8 中对 action 按钮新增的处理方法。

用户点击通知进入应用。

系统会根据当前的通知是本地通知还是远程通知,调用不同的方法,并且在方法中传递通知的 Payload 数据。

注意:只有当通过通知进入到 App 时,才可以获取到通知信息。

4.1 自定义通知提示音

你可以在 App 的 Bundle 中加入一段自定义提示音文件。然后当通知到达时可以指定播放这个文件。必须为以下几种数据格式:

  • Linear PCM
  • MA4(IMA/ADPCM)
  • μLaw
  • aLaw

你可以将它们打包为 aiff、wav 或caf文件。自定义的声音文件时间必须小于 30 秒,如果超过了这个时间,将被系统声音代替。

4.2 Payload

Payload 是通知的一部分,每一条推送通知都包含一个 Payload。它包含了系统提醒用户通知到达的方式,还可以添加自定义的数据。即通知主要传递的数据为 Payload。

Payload 本身为 JSON 格式的字符串,它内部必须要包含一个键为 aps 的字典。aps 中可以包含以下字段中的一个或多个:

  • alert:其内容可以为字符串或者字典,如果是字符串,那么将会在通知中显示这条内容
  • badge:其值为数字,表示当通知到达设备时,应用的角标变为多少。如果没有使用这个字段,那么应用的角标将不会改变。设置为 0 时,会清除应用的角标。
  • sound:指定通知展现时伴随的提醒音文件名。如果找不到指定的文件或者值为 default,那么默认的系统音将会被使用。如果为空,那么将没有声音。
  • content-available:此字段为 iOS 7 silent remote notification 使用。不使用此功能时无需包含此字段。