62

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

 5 years ago
source link: http://www.blogjava.net/jb2011/archive/2018/07/16/433298.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

本文作者:陈裕发, 腾讯系统测试工程师,由腾讯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如下:

rUFVZji.jpg!web

3.2 离线/远程push

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

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

JzMFfaQ.jpg!web

一种特殊的远程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:

AZzYvyv.jpg!web

jQN732y.png!web

由于本地push原理和作用相对于在线push和离线push都更为简单明了,下文主要介绍在线push和离线push。

4、本地push实现

4.1 iOS10以前本地push弹出方式

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

1)立即展示( iOS10以前)

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

UZ7VzyA.png!web

2)延迟展示( iOS10以前)

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

yYJbYzy.png!web

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

euAJZfu.png!web

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

3EZVjyN.jpg!web

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

IFzYj2i.jpg!web

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

3qeu2yf.jpg!web

4.3 处理本地push ( iOS10以前)

1)App没有启动情况下处理本地push

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

j63iEvr.jpg!web

2)App运行在后台及前台

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

UIApplication )application didReceiveLocalNotification:UILocalNotification *)notification来处理的。

vIFZ7br.jpg!web

4.4 iOS10以后本地push弹出方式

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

创建方法:

b6fuaea.png!web

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

fQrA32J.jpg!web

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

1)UNCalendarNotificationTrigger 传送本地通知的日期和时间;
2)UNTimeIntervalNotificationTrigger 传递本地通知之前必须过期的时间;
3)UNLocationNotificationTrigger 用户必须达到的地理位置才能提供本地通知;
4)UNPushNotificationTrigger 表示通知是从Apple推送通知服务发送的对象。

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

iAzyYrb.png!web

推送本地push的代码为:

UBVjmu3.png!web

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

5.1 在线push流程

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

1)判断app是否在线:

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

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

不需要经过苹果APNs;

需要自己实现长链接;

代码在app内部实现。

5.2 离线(远程)push流程

Afaa63E.png!web

主要流程为:

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等,如

MN7B3au.png!web

(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的详细流程:

eAZvMrU.jpg!web

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有两个函数:

Yfqm2eq.png!web

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

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

QJ7nMvf.png!web

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

j6fIBfN.png!web

则会调用:

nyQvYzV.png!web

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

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

iOS10对push的处理主要增加了两个方法:

ZR36VrJ.png!web

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

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

VVF7JvE.jpg!web

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更加多样化,可以有主标题,副标题,甚至还有附件。

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

2u2qEzQ.jpg!web

3D touch点入详情以后:

UbAvYjR.jpg!web

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

6.2 push携带附件

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

EJZjMfn.jpg!web

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

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

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

6.3 携带action的通知

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

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

FFbUZvA.jpg!web

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

rEjQZfm.png!web

然后在delegate的类中实现:

eyYfya6.jpg!web

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

6.4 改变push内容

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

1)改变本地push内容:

本地push,只要request的id一样,那么就可以更新推送。

更新的例子:

eQ7JFbU.png!web

此外,还有删除所有推送等,都在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有什么不同,为什么需要两个?它们分别实现什么功能呢?

rA7z22N.png!web

【1)notification service extension】

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

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

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

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

payload(举例)如下:

ua2Qze3.png!web

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

依然以腾讯新闻为例子:

jeaeiyR.jpg!web

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

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

7、iOS Push的测试要点罗列

NfiQzq7.jpg!web

另外注意一点: 测试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了。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK