本文作者:陈裕发, 腾讯系统测试工程师,由腾讯wetest整理发表。
开发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群:[推荐]
- 移动端im开发入门文章:《》
(本文同步发布于:)
《》
《》
《》
《》
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.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.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的消息、角标、标题等内容:
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的通知”。
另外注意一点:测试push的时候,区分好appstore证书和开发证书。两者不能相互发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了。
knuff离线push工具下载链接:
使用方法也比较简单:
比如我的payload输入如下:
得到的应该是有“knuff测试”文字,和角标数变为999,我们可以看下结果,与预料是一致的:
有了这个工具也更加方便了我们的ios push的调试。
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
>>
(本文同步发布于:)
作者: (点击作者姓名进入github)
出处:
交流:欢迎加入即时通讯开发交流群
讨论:
jack jiang同时是和的作者,可前往下载交流。
本博文
欢迎转载,转载请注明出处(也可前往 找到我)。