欢迎大家前往腾讯云 + 社区,获取更多腾讯海量技术实践干货哦~
本文由 WeTest 质量开放平台团队发表于云 + 社区专栏
作者:陈裕发,腾讯系统测试工程师
商业转载请联系腾讯 WeTest 获得授权,非商业转载请注明出处。
原文链接:http://wetest.qq.com/lab/view/380.html
WeTest 导读
本文主要对 iOS Push 的在线 push、本地 push 及离线(远程)push 进行梳理,介绍了相关逻辑,测试时要注意的要点以及相关工具。小小的 Push 背后蕴藏着大大的逻辑!
Push 种类
一、在线 push
在线 push:当用户在线(APP 在前台)时,收到的状态栏的消息提醒,称为在线 push。这个功能与苹果系统无关,是我们自己的 APP 开发的一种功能,该 push 与设置中是否打开“通知”无关。
这里以 iOS Qzone 为例,当 APP 在前台时,自己发的说说被点赞了,收到的在线 push 如下:
1.png
Qzone 在线 push
二、离线(远程)push
离线 push:当 APP 在离线(kill 掉进程、切到后台、锁屏)时,收到的消息提醒,称为离线 push。离线 push 是需要经过苹果的 APNs 服务器才可以推送到某台设备的某个 APP 上的,这是和本地 push 的本质区别。push 与设置中是否打开“通知”有关。
这里最简单的以大家常用的手机 QQ 为例,当 APP 在后台、锁屏或者被 kiil 了进程时,收到了消息:
2.png
离线 push
1、静默 push
静默 push 用的场景不较少,这里只做简要介绍。
首先我们看看离线(远程)push 与静默 push 的区别:
普通离线(远程)push:收到推送后(有文字有声音),点开通知,进入 APP 后,才执行 – (void)application:(UIApplication didReceiveRemoteNotification:(NSDictionary fetchCompletionHandler:(void result))handler )application )userInfo (^)(UIBackgroundFetchResult
静默 push:收到推送(没有文字没有声音),不用点开通知,不用打开 APP,就能执行 (void)application:(UIApplication)application)userInfo didReceiveRemoteNotification:(NSDictionary fetchCompletionHandler:(void (^)(UIBackgroundFetchResultresult))handler,用户完全感觉不到。
所以静默 push 又被我们称做 Background Remote Notification(后台远程推送)。静默推送是在 iOS7 之后推出的一种推送方式。它与其他推送的区别在于允许应用收到通知后在后台(background)状态下运行一段代码,可用于从服务器获取内容更新。
三、本地 push
本地 push:本地推送和远程推送的功能是一样的,都是要提醒用户去做某些事情。但是和远程推送不同的就是本地推送是不需要设备联网的,而远程推送是必需要设备联网的,因为只有联网状态下,才能和苹果的 APNs 服务器建立长连接,从而推送消息。本地推送是由 App 自己设定的,并且发送给安装此 App 的这台设备,属于一对一的对应关系。比较典型的应用是闹钟类似的场景。该 push 与设置中是否打开“通知”有关。
最容易看到本地 push 的场景,可以直接在手机设置一个计时器,计时器时间到了就会弹出本地 push:
3.png
本地 push
4.png
由于本地 push 原理和作用相对于在线 push 和离线 push 都更为简单明了,下文主要介绍在线 push 和离线 push。
本地 push 实现
一、iOS10 以前本地 push 弹出方式
试验过 iOS10 以前的本地 push 方法在 iOS10+ 的系统也能使用,不过可能有些参数不生效。
1、立即展示(iOS10 以前)
本地 push 稍微简单,有两种方式可以调用,一种是 presentLocalNotificationNow 方法,立即展示本地 push:
5.png
2、延迟展示(iOS10 以前)
另一种是用 scheduleLocalNotification 方法按计划来弹本地推送:
6.png
如果使用这种方法,需要对推送的时间进行设置,举个例子,设为 5 秒后:
7.png
二、设置本地 push 内容(iOS10 以前)
8.png
其中 alertBody 是消息内容锁屏与不锁屏时效果如下:
9.png
本地 push 效果
applicationIconBadgeNumber 是消息数量,我们可以看到这里设置为 66:
10.png
消息数
三、处理本地 push(iOS10 以前)
1、App 没有启动情况下处理本地 push
这种情况下,当点击通知时,会启动 App,而在 App 中,开发人员可以通过实现 AppDelegate 中的方法:- (BOOL)application:(UIApplication)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions,然后从 lauchOptions 中获取 App 启动的原因,若是因为本地通知,则可以 App 启动时对 App 做对应的操作,比方说跳转到某个画面等等。
11.png
2、App 运行在后台及前台
上面的 2 种情况的处理基本一致,不同点只有当运行再后台的时候,会有弹窗提示用户另外一个 App 有通知,对于本地通知单的处理都是通过 AppDelegate 的方法:- (void)application:(UIApplication)application didReceiveLocalNotification:(UILocalNotification *)notification 来处理的。
12.png
四、iOS10 以后本地 push 弹出方式
iOS10 以后,本地通知可以由使用 UNUserNotificationCenter 来管理。
创建方法:
13.png
接下来需要需创建一个包含待通知内容的 UNMutableNotificationContent 对象:
14.png
在 iOS 上可以通过以下几种触发器来触发本地 push:
● UNCalendarNotificationTrigger 传送本地通知的日期和时间。
● UNTimeIntervalNotificationTrigger 传递本地通知之前必须过期的时间。
● UNLocationNotificationTrigger 用户必须达到的地理位置才能提供本地通知。
● UNPushNotificationTrigger 表示通知是从 Apple 推送通知服务发送的对象。
假如以时间间隔(TimeInterval)来触发,则设置触发器代码为:
15.png
推送本地 push 的代码为:
16.png
在线、离线(远程)push 流程
一、在线 push 流程
在线 push 相对简单,因为是内部实现,具体流程如上面所示。
1、判断 app 是否在线
此处可以根据 APP 自身的后台策略如上一次与后台交互的时间等方法来判断 APP 是否在线或者离线。认为在线,会发送在线 push,否则,发送离线 push。
2、在线 push 特点
● 在线 push 有以下几个特点:
● 不需要经过苹果 APNs。
● 需要自己实现长链接。
● 代码在 app 内部实现。
二、离线(远程)push 流程
17.png
离线 push 流程
主要流程为:
● 服务器端将消息先发送到苹果的 APNs
● 由苹果的 APNs 将消息推送到客户的设备端
● 由 iOS 系统将接收到的消息传递给相应的 App。
简而言之离线 push 是苹果系统的行为,与 app 状态无关,能够直接推送到指定手机的指定 app。
在进一步了解离线 push 前,我们有必要先了解几个名词。
1、离线 push 名词解释
—APNs
APNs:Apple Push Notification service(苹果推送通知服务)。
APNs 主要用于以下场景:当用户主动杀掉 APP,或者 APP 进入后台超过约定时长时,APP 会被 kill,这样保障了前台 APP 的流畅性,也延长了手机的使用时长,获得了较好的用户体验,但是这也意味着,服务器无法主动和用户交互(如推送实时消息等),所以苹果推出了 APNs,允许设备和服务器分别与苹果的推送通知服务器保持长连接状态。
关于 APNs 的更新有以下几点:
● iOS 8 以后,APNs 推送的字节是 2k,iOS8 以前是 256 字节
● iOS 9 以后 APNs 支持 HTTP/ 2 协议栈,优化长连接,具有标准的 HTTP 返回和管道复用技术
● iOS 10 以后,推送的字节是 4k,APNs 可根据推送消息的唯一标示符查询某条消息是否被用户阅读,可更新某一推送消息,而不用发重读的多条消息
关于 APNs 更全面的介绍可以看官方文档:
https://developer.apple.com/l…
—payload
什么是 payload?对于每一条发送给 APNs 的推送消息,都包含一个 payload,通常是组成了一个 JSON 的 Dictionary,这其中必不可少的是 aps 属性,它对应的 value 也是一个 Dictionary,包含一些但不限于以下内容:标题、副标题、内容、附件、category 等,如
18.png
—device token
什么是 device token?我们看一下官方的简介:
device token: APNs uses device tokens to identify each unique app and device combination. It also uses them to authenticate the routing of remote notifications sent to a device.(device token 是 APNs 用于区分识别每个 iOS 设备和设备上不同 app 的一个标识符,还可以用于 APNs 通过它将推送消息路由到指定设备上)
即:device token 里包含了 device id 和 bundle id 的信息,但是 device id 和 bundle id 不会确定唯一的 device token。
但是,这里有个坑,查资料得知,iOS8 及之前的 iOS 系统,对于同一部手机,如果卸载后重装 APP 的话,device token 是不会变的,在 token 变了以后,老的 token,就被认为是无效了,苹果不会对这部分无效的 token 推送。但是,对 iOS9 及以后的 iOS 系统,对于同一部手机,卸载后重装 APP 的 device token 是会发生变化的,而且老的 token 不会无效,还可以正常推送,这应该是苹果的一个 bug,但是苹果也没有修复这个问题,所以这个需要开发者自己来解决,否则容易出现一个 app 收到多个 push 的问题。官方的说法是:
To protect user privacy, do not use device tokens to identify user devices. Device tokens change when the user updates the operating system and when a device’s data and settings are erased. As a result, apps should always request the current device token at launch time.(即此举为了保护用户隐私,device token 会在更新系统、擦除设置重置后变化,在一定时间后会过期)
2、离线 push 详细流程
知道了以上概念后我们重新来看一下离线(远程)push 的详细流程:
19.png
离线 push 详细流程
1) 首先是应用程序注册消息推送。
2) iOS 跟 APNS Server 要 deviceToken。应用程序接受 deviceToken。
3) 应用程序将 deviceToken 发送给 PUSH 服务端程序。
4) 服务端程序向 APNS 服务发送消息。
5) APNS 服务将消息发送给 iPhone 应用程序。
值得注意的是,当由于用户反复卸载重装程序(虽然概率很小)等原因导致多个 device Token 指向同一台设备的同一个 app,又把多个 device Token 发给 APNs 时,用户就会收到多条 push。苹果 APNs 是不会对多个 device Token 是否指向同一台设备的同一个 app 做校验的,所以需要后台来做去重等处理保证用户不会收到多条 push。
三、对离线(远程)push 的响应
1、iOS 7 以上对离线(远程)push 时的响应
iOS 7 以上关于接受离线 push 有两个函数
20.png
那么这两个函数有什么区别呢?其实这两个方法都是用来处理离线 push 的。
差别就是,如果 app 在前台是收到离线(远程)push,那么就会调用
21.png
相对的,如果在后台或者杀进程情况下,点击收到的离线 push,那么就会调用,如果没有实现
22.png
则会调用
23.png
若实现了前者,就只调用前者。
2、iOS 10 以上对离线(远程)push 的响应
iOS10 对 push 的处理主要增加了两个方法
24.png
其中前者是对 APP 在前台时收到 push 时的处理,后者是点击 push 进入 APP 执行的函数。
用得比较多的是后者,我们可以举个例子,点击 push 进入 APP 后如何获取 push 的消息、角标、标题等内容:
25.png
iOS 10 关于 push 的一些新特性
iOS10 新增的 UserNotifications 框架,主要有了这样几方面的更新:
● 用 UserNotifications 框架替换了原先与通知相关的接口,通知文字可分为 title、subtitle 和 body 三部分,通知可携带附件
● 系统在展示通知之前,可以唤起 app 附带的 service extension,并且允许它改动通知的内容
● 用户在对通知右滑查看、下拉或者 3d touch 的时候,通知会展开,展开后页面的布局可以由 app 附带的 content extension 来决定
一、push 的多样性
iOS10 以前的 push 只有文字,甚至没有标题。
iOS10 以后的 push 更加多样化,可以有主标题,副标题,甚至还有附件,这里以我司的腾讯新闻为例(有标题,内容,和附件):
26.png
腾讯新闻 push
3D touch 点入详情以后:
27.png
腾讯新闻 push 详情
这里我们惊奇的发现,除了可以携带图片这样的附件、push 还能展开详情以外,进入详情以后,下面还多了“打开”、“收藏”、“不感兴趣”这些选项,这里就涉及到以下 iOS10 的新特性。
二、push 携带附件
因为 payload 有大小限制,所以如果 remote notification 想要携带附件,那么 payload 上只能带上如附件下载地址之类的信息,等通知到达客户端后由 service extension 下载附件到本地,然后在初始化 UNNotificationAttachment 对象时传入附件在本地的 URL。
28.png
初始化 UNNotificationAttachment 对象时,可以传入 option 参数。这里的 option 参数可以强制指定附件的类型,可以选择是否展示缩略图,以及缩略图截取自附件的哪一帧、哪一部分。
目前 iOS10 通知只将几种格式的图片、音频和视频作为附件,附件的大小也有一定限制,具体可以看官方文档中的限制说明。
关于附件的更加详细的说明,可以参考官方文档:
https://developer.apple.com/d…
三、携带 action 的通知
上面提到的“打开”、“收藏”、“不感兴趣”这些选项其实就是 push 携带的 action,其实从 iOS8 开始,通知已经可以携带 action 了。而在 iOS10 中,通知的 action 被放在了更明显的位置,与 action 相关的接口也有了很大变化。
决定一个通知应该有哪些 action 呢?在 payload 中,这是由 category 字段决定的。如果我们希望一个通知能携带若干个 action,我们就需要将若干个 action 和一个 category 绑定起来。通知到达前端后,系统会根据 category 的名字来决定要给这个通知展示哪些 action:
29.png
怎么得知用户选了哪个 action 并做出相应操作呢?这需要给 UNUserNotificationCenter 指定一个 delegate:
30.png
然后在 delegate 的类中实现
31.png
方法:通过 response.notification.request.content.categoryIdentifier 和 response.actionIdentifier 就可以得知用户选择的 action 了。
四、改变 push 内容
这里主要讲应用的比较多的离线(远程)push 的改变 push 方法
1、改变本地 push 内容
本地 push,只要 request 的 id 一样,那么就可以更新推送:
更新的例子:
31.png
32.png
此外,还有删除所有推送等,都在 UNUserNotificationCenter.h 中实现。
2、改变离线(远程)push 内容
目前远程 push 只支持更新 push 内容,更新需要通过新的字段 apps-collapse-id 来作为唯一标示。方法是在 HTTP/2 请求头中使用相同的 apns-collapse-id,这样收到同样的 apns-collapse-id 的 push 时,push 内容便会更新。
使用场景:比较容易理解的一个场景就是球赛比分,比如现在是 1:0,如果变成 1:1 的话,只需要刷新原来的新闻,这样用户就不会因为同一场比赛收到多条 push。
五、两个 extension
有两个与 push 相关的 extension,可能我们会好奇这两个 extension 有什么不同,为什么需要两个?它们分别实现什么功能呢?
33.png
push 相关 extension
1、notification service extension
给 app 添加 notification service extension 后,系统会在收到通知后唤醒它,并允许它修改通知的内容,之后再展示这个通知。
service extension 只对 remote notification 起作用,local notification 是无法唤起它的。
如果想要让系统唤起 service extension 的话,payload 必须符合这样几个条件:
1) 必须增加 mutable-content 字段并为 1,这表示允许客户端修改这个通知:
payload(举例) 如下:
34.png
2) 这个通知必须展示一个 alert,如果只是一个修改 badge 的通知的话,是不会唤起 service extension 的
3) 静默推送是不能唤起 service extension 的,所以 payload 中不能有”content-available”: 1 字段
所以,通过这个 notification service extension,你可以在接收到推送之后、展示推送之前处理一些事情,比如说更新一下推送内容,或者在后台做一些其他事情。
2、notification content extension
另一项 notification content extension 用于完全自定义推送展开后的视图。上面腾讯新闻的展开后的视图就是通过这个 notification content extension 实现的。
依然以腾讯新闻为例子:
35.png
展开界面
这里 Notification Content Extension 大展拳脚的地方,在这里可以自定义绘制不同的内容,将希望展现给用户的额外信息可以加载这里。
下半部分的 notification action 的实现就是在上面提到的“携带 action 的通知”。
测试要点
36.png
Q&A
Q:离线 push,支持角标(badge)在本地角标数值上 + 1 这样的操作吗?
A:不支持。如果是自己实现 push 服务的话,需要自己的后台将角标值 badge 发送个 APNs 服务器,有些 APP 使用第三方 push SDK 除外。
Q:如果重复收到离线 push,可能是什么情况?
A:
1)iOS9 之后卸载重装后生成新的 deviceToken,后台对多个 deviceToken 都发送了 push
2)后台对注销了的账号也发送了 push。
总而言之一般是后台的逻辑出现了问题,而不是 APNs 服务器出现问题。
Q:直接卸载 APP,还能收到离线 push 吗?
A:不会收到。直接卸载 APP,虽然后台不知道 APP 被卸载了,仍然会对之前的账号发送 push,但是由于手机上没有对应 APP,所以并不会收到 push。
Q:为什么有时候全新安装 APP 就立马有红点角标?
A:这是因为卸载该 APP 时有红点角标。每个 APP 的角标都是存在 iOS 手机系统里的,开发无法修改,所以此时卸载前有角标,重新安装也会有角标。但是,APP 卸载之后超过一天的时间再重装,那么角标就会被系统清空,届时也不会有新安装的 APP 就有角标的情况存在。
相关工具
Knuff 离线 push 工具下载链接:https://github.com/KnuffApp/K…
使用方法也比较简单
37.png
比如我的 payload 输入如下:
38.png
得到的应该是有“Knuff 测试”文字,和角标数变为 999,我们可以看下结果,与预料是一致的:
39.png
预期结果
有了这个工具也更加方便了我们的 iOS push 的调试。
参考资料 iOS 推送之远程推送(iOS Notification Of Remote Notification):https://www.jianshu.com/p/4b9… 相关阅读系统负载能力浅析搭建公众号自动回复功能【每日课程推荐】机器学习实战!快速入门在线广告业务及 CTR 相应知识