理解iOS消息推送一文就够:史上最全iOS Push技术详解

摘要:本文作者:陈裕发, 腾讯系统测试工程师,由腾讯WeTest整理发表。1、引言开发iOS系统中的Push推送,通常有以下3种情况:1)在线Push:比方QQ、微信等IM界面处于前端时,聊天消息和指令都会通过IM自建的网络长连接通道推送过来,这种Push在本文中暂且称为“在线Push”;2)本地Push

本文作者:陈裕发, 腾讯系统测试工程师,由腾讯WeTest整理发表。

1、引言

开发iOS系统中的Push推送,通常有以下3种情况:

1)在线Push:比方QQ、微信等IM界面处于前端时,聊天消息和指令都会通过IM自建的网络长连接通道推送过来,这种Push在本文中暂且称为“在线Push”;

2)本地Push:这种就是最常见的iOS系统通知(作使用相当于传统PC端的提醒窗口,在iOS10以后一律整合到UserNotifications.framework框架了),不涉及任何网络数据,仅仅是让APP拥有一个统一系统通知方式而已,比方:闹钟的定时提示等;

3)离线/远程Push:这就是iOS程序员最熟习的APNs这一套东西了,它使得APP处于后端或者者被kill的情况下仍能收到网络通知,最常见的应场景就是IM聊天工具了。

本文将对iOS Push的在线push、本地push及离线(远程)push进行了详细梳理,详情相关逻辑、测试时要注意的要点以及相关工具的用。小小的Push背后蕴藏着大大的逻辑,我们一起来学习吧!

消息推送/im开发学习交流:

- 即时通讯开发交流3群:185926912[推荐]

- 手机端IM开发入门文章:《新手入门一篇就够:从零开发手机端IM》

(本文同步发布于:http://www.52im.net/thread-1762-1-1.html)

2、相关文章

《手机端实时消息推送技术浅析》

《iOS的推送服务APNs详解:设计思路、技术原理及缺陷等》

《信鸽团队原创:一起走过 iOS10 上消息推送(APNS)的坑》

《扫盲贴:浅谈iOS和Android后端实时消息推送的原理和区别》

3、iOS的Push种类

3.1 在线push

在线push:当使用户在线(APP在前端)时,收到的状态栏的消息提示,称为在线push。这个功能与苹果系统无关,是我们自己的APP开发的一种功能,该push与设置中能否打开“通知”无关。

这里以iOS Qzone为例,当APP在前端时,自己发的说说被点赞了,收到的在线push如下:

3.2 离线/远程push

离线push:当APP在离线(kill掉进程、切到后端、锁屏)时,收到的消息提示,称为离线push。离线push是需要经过苹果的APNs服务器才可以推送到某台设施的某个APP上的,这是和本地push的本质区别。push与设置中能否打开“通知”有关。

这里最简单的以大家常使用的手机QQ为例,当APP在后端、锁屏或者者被kiil了进程时,收到了消息:

一种特殊的远程push:静默push

严格来说,静默push属于远程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)状态下运行一段代码,可使用于从服务器获取内容升级。

3.3 本地push

本地push:本地推送和远程推送的功能是一样的,都是要提示使用户去做某些事情。但是和远程推送不同的就是本地推送是不需要设施联网的,而远程推送是必须要设施联网的,由于只有联网状态下,才能和苹果的APNs服务器建立长连接,从而推送消息。本地推送是由App自己设定的,并且发送给安装此App的这台设施,属于一对一的对应关系。比较典型的应使用是闹钟相似的场景。该push与设置中能否打开“通知”有关。

最容易看到本地push的场景,可以直接在手机设置一个计时器,计时器时间到了就会弹出本地push:

因为本地push原理和作使用相对于在线push和离线push都更为简单明了,下文主要详情在线push和离线push。

4、本地push实现

4.1 iOS10以前本地push弹出方式

实验过iOS10以前的本地push方法在iOS10+的系统也能用,不过可能有些参数不生效。

1)立即展现( iOS10以前)

本地push略微简单,有两种方式可以调使用,一种是presentLocalNotificationNow方法,立即展现本地push:

2)推迟展现( iOS10以前)

另一种是使用scheduleLocalNotification方法按计划来弹本地推送:

假如用这种方法,需要对推送的时间进行设置,举个例子,设为5秒后:

4.2 设置本地push内容( iOS10以前)

其中alertBody是消息内容锁屏与不锁屏时效果如下:

applicationIconBadgeNumber是消息数量,我们可以看到这里设置为66:

4.3 解决本地push ( iOS10以前)

1)App没有启动情况下解决本地push

这种情况下,当点击通知时,会启动App,而在App中,开发人员可以通过实现AppDelegate中的方法:- (BOOL)application:UIApplication)application didFinishLaunchingWithOptions:NSDictionary *)launchOptions,而后从lauchOptions中获取App启动的起因,若是由于本地通知,则可以App启动时对App做对应的操作,比如说跳转到某个画面等等。

2)App运行在后端及前端

上面的2种情况的解决基本一致, 不同点只有当运行再后端的时候,会有弹窗提醒使用户另外一个App有通知,对于本地通知单的解决都是通过AppDelegate的方法:- (void)application

UIApplication )application didReceiveLocalNotification:UILocalNotification *)notification来解决的。

4.4 iOS10以后本地push弹出方式

iOS10以后,本地通知可以由用 UNUserNotificationCenter来管理。

创立方法:

接下来需要需创立一个包含待通知内容的 UNMutableNotificationContent 对象:

在iOS上可以通过以下几种触发器来触发本地push:

1)UNCalendarNotificationTrigger 传送本地通知的日期和时间;

2)UNTimeIntervalNotificationTrigger 传递本地通知之前必需过期的时间;

3)UNLocationNotificationTrigger 使用户必需达到的地理位置才能提供本地通知;

4)UNPushNotificationTrigger 表示通知是从Apple推送通知服务发送的对象。

如果以时间间隔(TimeInterval)来触发,则设置触发器代码为:

推送本地push的代码为:

5、在线、离线(远程)push流程

5.1 在线push流程

在线push相对简单,由于是内部实现,具体流程如上面所示。

1)判断app能否在线:

此处可以根据APP自身的后端策略如上一次与后端交互的时间等方法来判断APP能否在线或者者离线。认为在线,会发送在线push,否则,发送离线push。

2)在线push有以下几个特点:

不需要经过苹果APNs;

需要自己实现长链接;

代码在app内部实现。

5.2 离线(远程)push流程

主要流程为:

1)服务器端将消息先发送到苹果的APNs;

2)由苹果的APNs将消息推送到用户的设施端;

3)由iOS系统将接收到的消息传递给相应的App。

简而言之离线push是苹果系统的行为,与app状态无关,能够直接推送到指定手机的指定app。

在进一步理解离线push前,我们有必要先理解几个名词。

【离线push名词解释】:

(1)名词解释之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更全面的详情可以看官方文档:点此进入。

(2)名词解释之payload

什么是payload?对于每一条发送给APNs的推送消息,都包含一个payload,通常是组成了一个JSON的Dictionary,这其中必不可少的是aps属性,它对应的value也是一个Dictionary,包含少量但不限于以下内容:标题、副标题、内容、附件、category等,如

(3)名词解释之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会在升级系统、擦除设置重置后变化,在肯定时间后会过期)

【离线push详细流程】

知道了以上概念后我们重新来看一下离线(远程)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。

5.3 对离线(远程)push的响应

1)iOS 7以上对离线(远程)push时的响应

iOS 7以上关于接受离线push有两个函数:

那么这两个函数有什么区别呢?其实这两个方法都是使用来解决离线push的。

差别就是,假如app在前端是收到离线(远程)push,那么就会调使用:

相对的,假如在后端或者者杀进程情况下,点击收到的离线push,那么就会调使用,假如没有实现:

则会调使用:

若实现了前者,就只调使用前者。

2)iOS 10以上对离线(远程)push的响应

iOS10对push的解决主要添加了两个方法:

其中前者是对APP在前端时收到push时的解决,后者是点击push进入APP执行的函数。

使用得比较多的是后者,我们可以举个例子,点击push进入APP后如何获取push的消息、角标、标题等内容:

6、iOS 10关于push的少量新特性

iOS10新添加的UserNotifications框架,主要有了这样几方面的升级:

1)使用UserNotifications框架替换了原价与通知相关的接口,通知文字可分为title、subtitle和body三部分,通知可携带附件;

2)系统在展现通知之前,可以唤起app附带的service extension,并且允许它改动通知的内容;

3)使用户在对通知右滑查看、下拉或者者3d touch的时候,通知会开展,开展后页面的布局可以由app附带的content extension来决定。

6.1 push的多样性

iOS10以前的push只有文字,甚至没有标题。iOS10以后的push更加多样化,可以有主标题,副标题,甚至还有附件。

这里以我司的腾讯新闻为例(有标题,内容,和附件):

3D touch点入介绍以后:

这里我们惊奇的发现,除了可以携带图片这样的附件、push还能开展介绍以外,进入介绍以后,下面还多了“打开”、“收藏”、“不感兴趣”这些选项,这里就涉及到以下iOS10的新特性。

6.2 push携带附件

由于payload有大小限制,所以假如remote notification想要携带附件,那么payload上只能带上如附件下载地址之类的信息,等通知到达用户端后由service extension下载附件到本地,而后在初始化UNNotificationAttachment对象时传入附件在本地的URL。

初始化UNNotificationAttachment对象时,可以传入option参数。这里的option参数可以强制指定附件的类型,可以选择能否展现缩略图,以及缩略图截取自附件的哪一帧、哪一部分。

目前iOS10通知只将几种格式的图片、音频和视频作为附件,附件的大小也有肯定限制,具体可以看官方文档中的限制说明。

关于附件的更加详细的说明,可以参考官方文档:点此进入。

6.3 携带action的通知

上面提到的“打开”、“收藏”、“不感兴趣”这些选项其实就是push携带的action,其实从iOS8开始,通知已经可以携带action了。而在iOS10中,通知的action被放在了更显著的位置,与action相关的接口也有了很大变化。

决定一个通知应该有哪些action呢?在payload中,这是由category字段决定的。假如我们希望一个通知能携带若干个action,我们就需要将若干个action和一个category绑定起来。通知到达前台后,系统会根据category的名字来决定要给这个通知展现哪些action:

怎样得知使用户选了哪个action并做出相应操作呢?这需要给UNUserNotificationCenter指定一个delegate:

而后在delegate的类中实现:

方法:通过response.notification.request.content.categoryIdentifier和response.actionIdentifier即可以得知使用户选择的action了。

6.4 改变push内容

这里主要讲应使用的比较多的离线(远程)push的改变push方法。

1)改变本地push内容:

本地push,只需request的id一样,那么即可以升级推送。

升级的例子:

此外,还有删除所有推送等,都在UNUserNotificationCenter.h中实现。

2)改变离线(远程)push内容:

目前远程push只支持升级push内容,升级需要通过新的字段apps-collapse-id来作为唯一标示。方法是在HTTP/2 请求头中用相同的apns-collapse-id,这样收到同样的apns-collapse-id的push时,push内容便会升级。

用场景:比较容易了解的一个场景就是球赛比分,比方现在是1:0,假如变成1:1的话,只要要刷新原来的新闻,这样使用户就不会由于同一场比赛收到多条push。

6.5 两个extension

有两个与push相关的extension,可能我们会好奇这两个extension有什么不同,为什么需要两个?它们分别实现什么功能呢?

【1)notification service extension】

给app增加notification service extension后,系统会在收到通知后唤醒它,并允许它修改通知的内容,之后再展现这个通知。

service extension只对remote notification起作使用,local notification是无法唤起它的。

假如想要让系统唤起service extension的话,payload必需符合这样几个条件:

1)必需添加mutable-content字段并为1,这表示允许用户端修改这个通知:

payload(举例)如下:

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实现的。

仍然以腾讯新闻为例子:

这里Notification Content Extension大展拳脚的地方,在这里可以自己设置绘制不同的内容,将希望展示给使用户的额外信息可以加载这里。

下半部分的notification action的实现就是在上面提到的“携带action的通知”。

7、iOS Push的测试要点罗列

另外注意一点:测试Push的时候,区分好Appstore证书和开发证书。两者不能相互发Push。

8、有关iOS Push的常见疑问汇总

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 就有角标的情况存在。

Q:自己Server通过APNs发的每一条Push,用户端都会收到么?

答案能否定的,Push是不可靠的,push通知是fire-and-forget,比方手机关机,那么自然就收不到,尽管Apple会尝试几次。

Q:Push消息的大小是多少?

iOS8发的时间点起,无论那个iOS系统,push消息的body大小调整为2k,注意这里是iOS8的时间点,也就是2014年秋,就目前来说push的限制应该是2k不再是256了。

9、相关工具推荐

Knuff离线push工具下载链接: KnuffApp/Knuff/releases

用方法也比较简单:

比方我的payload输入如下:

得到的应该是有“Knuff测试”文字,和角标数变为999,我们可以看下结果,与意料是一致的:

有了这个工具也更加方便了我们的iOS push的调试。

附录:更多消息推送技术文章

《iOS的推送服务APNs详解:设计思路、技术原理及缺陷等》

《信鸽团队原创:一起走过 iOS10 上消息推送(APNS)的坑》

《Android端消息推送总结:实现原理、心跳保活、遇到的问题等》

《扫盲贴:认识MQTT通信协议》

《一个基于MQTT通信协议的完整Android推送Demo》

《IBM技术经理访谈:MQTT协议的制定历程、发展示状等》

《求教android消息推送:GCM、XMPP、MQTT三种方案的优劣》

《手机端实时消息推送技术浅析》

《扫盲贴:浅谈iOS和Android后端实时消息推送的原理和区别》

《绝对干货:基于Netty实现海量接入的推送服务技术要点》

《手机端IM实践:谷歌消息推送服务(GCM)研究(来自微信)》

《为何微信、QQ这样的IM工具不用GCM服务推送消息?》

《极光推送系统大规模高并发架构的技术实践分享》

《从HTTP到MQTT:一个基于位置服务的APP数据通信实践概述》

《魅族2500万长连接的实时消息推送架构的技术实践分享》

《专访魅族架构师:海量长连接的实时消息推送系统的心得体会》

《深入的聊聊Android消息推送这件小事》

《基于WebSocket实现Hybrid移动应使用的消息推送实践(含代码示例)》

《一个基于长连接的安全可扩展的订阅/推送服务实现思路》

《实践分享:如何构建一套高可使用的手机端消息推送系统?》

《Go语言构建千万级在线的高并发消息推送系统实践(来自360公司)》

《腾讯信鸽技术分享:百亿级实时消息推送的实战经验》

《百万在线的美拍直播弹幕系统的实时推送技术实践之路》

《京东京麦商家开放平台的消息推送架构演进之路》

《理解iOS消息推送一文就够:史上最全iOS Push技术详解》

>>?更多同类文章 ……

(本文同步发布于:http://www.52im.net/thread-1762-1-1.html)

  • 全部评论(0)
最新发布的资讯信息
【系统环境|服务器应用】在CentOS 7上如何安装Gogs 0.11.53(2020-02-10 10:14)
【系统环境|】淘码库,据消息称已被调查。淘码库源码网,已经无法访问!(2020-01-14 04:13)
【系统环境|服务器应用】Discuz隐藏后台admin.php网址修改路径(2019-12-16 16:48)
【系统环境|服务器应用】2020新网站如何让百度快速收录网站首页最新方法,亲测有用!免费(2019-12-16 16:46)
【系统环境|服务器应用】Discuz发布帖子时默认显示第一个主题分类的修改方法(2019-12-09 00:13)
【系统环境|软件环境】Android | App内存优化 之 内存泄漏 要点概述 以及 处理实战(2019-12-04 14:27)
【系统环境|软件环境】MySQL InnoDB 事务(2019-12-04 14:26)
【系统环境|软件环境】vue-router(单页面应用控制中心)常见用法(2019-12-04 14:26)
【系统环境|软件环境】Linux中的Kill命令(2019-12-04 14:26)
【系统环境|软件环境】Linux 入门时必学60个文件解决命令(2019-12-04 14:26)
手机二维码手机访问领取大礼包
返回顶部