APNS具体的流程大概就是:app注册APNS推送功能,app就会通过iOS系统向APNS服务器要devicetoken,然后将devicetoken传给app的推送服务器,推送服务器带着P12文件和device token,以及要推送的消息,发送给苹果服务器。随后就是上述步骤啦。需要注意的是:xcode必须配置Profile,才能接受消息。而推送服务端则必须要,携带推送证书的P12文件,与APNS推送服务器传输消息。
关于device token,还要多少两句,在开发和生产环境中的同一个iPhone和app对应的device token是不一样的,而且它也会改变。如果改变了,我们怎么办。这个人说的很好,这里转载一下。
正是因为device有可能改变,所以建议在app start时(即在didFinishLaunchingWithOptions 里)调用registerForRemoteNotificationTypes来获取device token以检查device token是否改变,如果改变了就应该把新token传给push provider。(官方描述:An application should register every time it launches and give its provider the current token)device token应该存储在NSUserDefaults来达到新旧比较的目的
那么旧device token在push provider对应的record怎么办?
方案1:把旧device token send to provider and request delete record
方案2:使用apns feedback service。
方案2可能更好些,因为总是需要使用apns feedback service来处理用户在device里删除app的情况。
调用registerForRemoteNotificationTypes方法后,成功注册后,APNS就会返回一个device token,然后回调delegate methoddidRegisterForRemoteNotificationsWithDeviceToken, 如果注册失败,则回调delegate method didFailToRegisterForRemoteNotificationsWithError。
注意:
* 在第一次调用registerForRemoteNotificationTypes方法时没有联网,则既不会调用didRegisterForRemoteNotificationsWithDeviceToken,也不会调用didFailToRegisterForRemoteNotificationsWithError
*在第一次调用registerForRemoteNotificationTypes注册成功后,之后即使没有联网,再调用registerForRemoteNotificationTypes时都会以最上一次的device token作为参数回调didRegisterForRemoteNotificationsWithDeviceToken方法。
* (官方描述) If your application has previously registered, calling registerForRemoteNotificationTypes:
results in the operating system passing the device token to the delegate immediately without incurring additional overhead.
看完这位说的,那么我的问题来了:什么是apns feedback service。然后就看到下面这位说的重要规则。记录下来。
前段时间,仔细研究了APNS的文档,把一些关键的地方记录了下来,弄懂这些对于理解APNS的规则,至关重要。
1. If APNs attempts to deliver a notification but the device is offline, the notification is stored for a limited period of time, and delivered to the device when it becomes available.
假如用户手机不在线,可能没有信号或者关机吧,APNs会存储转发,等用户在线时再发送
2.Only one recent notification for a particular application is stored. If multiple notifications are sent while the device is offline, each new notification causes the prior notification to be discarded. This behavior of keeping only the newest notification is referred to as coalescing notifications.
如果用户不在线,通知会合并,只会保留最新的一条。假如你给用户发了两条通知,但用户反馈说,只收到了一条,那么很可能是用户当时不在线,APNs的合并策略生效,只保留了最近一条
3.If the device remains offline for a long time, any notifications that were being stored for it are discarded
4.The maximum size allowed for a notification payload is 256 bytes; Apple Push Notification Service refuses any notification that exceeds this limit.
这个很重要,payload,就是最后生成的那段Json,不得超过256字节。如果超过了,建议去掉一些不需要的参数,把alert,就是提示信息的字数减少
5.don’t repeatedly open and close connections. APNs treats rapid connection and disconnection as a denial-of-service attack.
6.If you send a notification that is accepted by APNs, nothing is returned.
发送成功的木有返回,只有发送失败的才会返回
7.If you send a notification that is malformed or otherwise unintelligible, APNs returns an error-response packet and closes the connection. Any notifications that you sent after the malformed notification using the same connection are discarded, and must be resent.
这条非常重要,如果有error-response,那么这条之后的通知都需要重发。有很多开源的库,在发苹果通知时都没有检测error-response,如果你不小心用了,那么用户很可能反馈“怎么没有通知啊”
8.The notification identifier in the error response indicates the last notification that was successfully sent(实际情况不是,实际上返回的是出错的那条通知的ID). Any notifications you sent after it have been discarded and must be resent.When you receive this status code, stop using this connection and open a new connection.
这是对上一条的补充,如果出错了,需要关闭当前的连接,并且重新连接再发。error-response中返回的通知ID,可以帮助我们找出哪条出错了,这样就能知道哪些需要重发了
9.When a push notification cannot be delivered because the intended app does not exist on the device, the feedback service adds that device’s token to its list.
APNS的feedback service会返回那些已经卸载的设备的token--device_token。存储这些token,下次就不用再给他们发了,可以节省点资源。需要注意的是:feedback的接口读取一次,APNS就会清空它的列表,下次再读取时,返回的就是这两次读取之间这段时间新产生的device_token。
接下来是另外一位的更新版
苹果APNs’ device token特性和过期更新
发表于 2014 年 3 月 24 日
由schwimmer
APNs全名是Apple Push Notification Service。用iPhone的应该都习惯了,每次安装完一个新应用启动后,几乎都会弹出个警告框,“XXX应用”想要给您发送推送通知。这个警告框的权限申请就是为了APNs推送,用户授权后,应用提供商就可以通过APNs给用户推送消息。
APNs的工作机制简单来说可以分为两步,第一步是注册推送服务从APNs获取device token来告知应用提供商服务端,第二步是应用提供商服务端通过APNs给设备推送消息,device token是作为设备的唯一标示。
上图就是device token生成的一个过程。我们以第一次安装启动360儿童卫士应用为例,首先应用会弹出个警告框,请求用户允许发送推送通知,用户允许后–>儿童卫士会向系统注册推送服务,系统接到注册请求后就会自动连接APNs服务器请求获取设备令牌(即device token)–>APNs服务器生成包含device id的device token并下发给设备–>儿童卫士接受到device token,保存在本地同时发送给儿童卫士服务器,到此第一步就完成了。
上图就是推送消息的示图了,设备通过device token和APNs服务器保持连接状态。还以360儿童卫士为例,当孩子到家了,儿童卫士服务器就需要发到达提醒给家长。这时儿童卫士服务器就会通过device token作为目的设备标示来推送加密的到达提醒消息给APNs,APNs解密后再根据device token推送给指定设备。这样,一次推送就完成了。
了解了APNs工作机制,很明显能够看到device token在其中起了至关重要的串联指向作用。如果device token错误或缺失,推送就无法送达目标设备了。所以测试也罢开发也好,都很有必要了解一下device token的一些特性:
1.每个device token都是唯一的,只会对应一台设备。
2.device token与设备系统相关(注意不是和设备绑定的!详解见后文),同一设备系统上不同应用获取的token是同一个。
3.应用卸载重新安装,获取到的device token不会变化,而且不会再弹出推送权限申请的弹窗,会自动继承前一次安装的设置信息。这个特性容易引发一些安全问题,用户卸载重新安装一个应用后,还没有登录应用,就可能接到上次登录帐号的推送消息了。我使用iPhone QQ和Skype都碰到过这种情况。客户端没有办法处理这个问题,因为被卸载时客户端是没法做出反应来通知服务器的。苹果有一个feedback的机制可以解决这个问题,苹果为每个应用程序维护了一个不断更新的推送失败的设备列表。服务端可以去定期检查并更新推送设备列表,这样能解决大部分问题,也能减少不必要的报文开销。
4.第三点客户端不能处理,但退出登录通知服务器就是客户端的工作了。用户退出登录客户端时,客户端应该告知服务器,停止对这个设备继续推送用户退出登录帐号的消息了。这点应该不算device token的特性了,是一个标准处理方法。
相信很多人都有这样一个疑问,作为一个设备推送的唯一标示,device token是否会变化或者过期呢?苹果在这点上有些含糊其辞,只是在官方文档上建议开发者在每次启动应用时应该都向APNs获取device token并上传给服务器。从这句话来看,device token是会变化的,不然不用每次启动都去获取。因为苹果官方没有给出明确的device token变化的情况,所以以下列举的都是一些前人总结的经验,主要援引了stackoverflow上关于这个问题一个回答,回答者称是和苹果的一个工程师交流及自己实验得出的结果。
1.升级系统device token有可能变化,确认的是升级到iOS5会变化,猜测是升级大的系统版本后device token会变化。
2.抹掉所有内容和设置,reset设备后,device token会变化。
3.恢复一个非本机的备份后,device token会变化。
4.device token会过期,这个众说纷纭,有说是半年的,有说一年,有说两年的,不过会过期应该是确凿的。
5.备份或者恢复本机的备份,device token不会变化。
所以保险起见,按照苹果的每次启动应用时检查device token并发送到服务器是比较稳妥的做法。
接下来是一位php coder的一些注意点
注意事项:
1.建议和feedback服务器建立长连接,连接过于频繁有可能被当做攻击(简简单单的做一些测试时没有关系的);
2.获取的token是在上次你给你的应用发推送失败时加feedback服务的,里面会返回失败的具体时间
3.返回的数据由三部分组成,请看下面的图
结构中包含三个部分,第一部分是一个上次发推送失败的时间戳,第二个部分是device_token的长度,第三部分就是失效的device_token
现在开始处理推送的消息。首先来说一下这些方法:
1.func applicationWillResignActive(application: UIApplication){} 当App既将进入后台、锁屏、有电话进来时会触发此事件
2.func applicationDidEnterBackground(application: UIApplication) {} 当App进入后台时触发此事件
3.func applicationWillEnterForeground(application: UIApplication) {} 当App从后台即将回到前台时触发此事件
4.func applicationDidBecomeActive(application: UIApplication) {}当App变成活动状态时触发此事件
5.func applicationWillTerminate(application: UIApplication) {} 当App退出时触发此方法,一般用于保存某些特定的数据
- (void)application:(UIApplication *)applicationdidRegisterForRemoteNotificationsWithDeviceToken:(NSData *)pToken
- (void)application:(UIApplication *)applicationdidFailToRegisterForRemoteNotificationsWithError:(NSError *)error
为了让device端可以接收到推送消息,需要将设备的token传送到苹果的服务器,这个token就相当于设备的识别码,每一台苹果设备都有唯一的token,苹果的服务器就是通过这个token找到对应的设备,并传送相应地消息。这两个函数就是在传送token成功或者失败后调用的,用户在对应的函数里面做一些相应地处理。 一般都是在成功接受到token的地方处理,将token传给自己应用的推送服务器。
如果app处于前台或者后天的时候,收到推送的时候,进入方法:
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
都是程序在运行过程中(无论当前程序处于前台还是后台)接收到推送消息的处理函数。根据苹果的官方文档,建议大家使用
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
因为前者在程序处于后台的时候是无法接收到推送信息的(经实测-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo其实可以接收到,不知道是怎么回事,希望大虾解疑)。另外就是-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler 还有一个作用。根据苹果给出的文档,系统给出30s的时间对推送的消息进行处理,此后就会运行CompletionHandler程序块。
在处理这类推送消息(即程序被启动后接收到推送消息)的时候,通常会遇到这样的问题,就是当前的推送消息是当前程序正在前台运行时接收到的还是说是程序在后台运行,用户点击系统消息通知栏对应项进入程序时而接收到的?这个其实很简单,用下面的代码就可以解决:
?
1
2
3
4
5
6
7
8
9
10
11
12
|
void
application:(UIApplication*)application didReceiveRemoteNotification:NSDictionary)userInfo fetchCompletionHandler:((^)UIBackgroundFetchResult)completionHandler{
if
(application.applicationState == UIApplicationStateActive) {
NSLog(@
"active"
);
}
else
if
(application.applicationState == UIApplicationStateInactive)
{
NSLog(@
"inactive"
);
}
}
|
关于userInfo的结构,参照苹果的官方结构:
|
|
?
1
|
"alert"
:
"You got your emails."
,
|
|
|
?
1
|
"sound"
:
"bingbong.aiff"
|
|
|
|
|
|
即key aps对应了有一个字典,里面是该次推送消息的具体信息。具体跟我们注册的推送类型有关。另外剩下的一些key就是用户自定义的了。
如果是app没有启动的状态,
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
上述方法中lanchOptions就不会为空,有下面代码可以获取到推送的数据,数据跟前台或者后台的结构一样。
//判断是否有远程消息通知触发应用程序启动
if (launchOptions){
NSDictionary *pushInfo =[launchOptionsobjectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
_userInfo = pushInfo;
}
还要说的是,当程序完全挂起的时候,你与自身的服务器也失去连接,那么你每次启动程序的时候,应该去服务端获取推送的消息,需要展示或者存储。
接下来来说说本地通知,UILocalNotifation,这个链接写的很好:
http://blog.csdn.net/bihailantian1988/article/details/7383197。以后可以翻看
还有这个人的也写得很好;http://www.cnblogs.com/foxting/p/4618045.html
要注意的是,本地通知最多只能推送64个,多了只会被系统忽略。
这个链接的小功能实现,也能帮助理解本地通知:http://www.mamicode.com/info-detail-895953.html
还是要自己去实现功能才能理解比较好。