1. 什么是推送通知
消息通知分本地通知和远程推送通知,是没有运行在前台的应用程序可以让它们的用户获得相关消息通知的方式。消息通知可能是一条消息,即将发生的日历事件,或远程服务器的新数据。当被操作系统显示时,本地通知和推送通知看起来一样。它们可以显示一个警告信息或在应用程序的图标上面显示一个徽标。它们也可以在警告窗或徽标显示时播放一段声音。推送通知是在 iOS 3.0 和 Mac OS X v7.0 之后引入的。本地通知是在 iOS 4.0 之后引入的。它们都不支持 Mac OS X,当用户被通知相应的应用程序有消息,事件,或其他数据时,他们可以启动该应用程序并查看详情。他们也可以选择忽略通知,此时应用程序没有被激活。
本地通知和推送通知为不同的需求而设计的。本地通知是本地 iPhone、iPad、或iPod touch 上面的应用发起的。相反推送通知(又称远程通知)是从其他设备上面到达的。它来自一个远程设备——应用程序的提供者——并在有新的消息需要查看或新的数据需要下载的时候被推送到本地设备上面的应用,常见的本地通知像iphone的日历,微信或者qq这些都是本地推送,比如还安装了优酷,qq视频这些软件,允许推送后,每天会给你发些新的视频消息,这些就是远程推送。
2. 什么是APNS?
苹果推送通知服务(APNs)是推送通知的网关,iPhone ipad 对于应用程序在后台运行有诸多限制,考虑到手机电池电量,应用不允许在后台进行过多的操作。因此,当用户切换到其他程序后,原先的程序无法保持运行状态。对于那些需要保持持续连接状态的应用程序(比如社区网络应用),将不能收到实时的信息。推送是解决轮询所造成的流量消耗和电量消耗的一个比较好的解决方案
为解决这一限制,苹果推出了APNs(苹果推送通知服务 Apple Push Notification services)。APNs 允许设备与苹果的推送通知服务器保持常连接状态。当你想发送一个推送通知给某个用户的iPhone上的应用程序时,你可以使用 APNs 发送一个推送消息给目标设备上已安装的某个应用程序。
苹果的推送服务APNs基本原理简单来说就是苹果利用自己专门的推送服务器(APNs)接收来自我们自己应用服务器的需要被推送的信息,然后推送到指定的iOS设备上,然后由设备通知到我们的应用程序,设备以通知或者声音的形式通知用户有新的消息。推送的前提是装有我们应用的设备需要向APNs服务器注册,注册成功后APNs服务器会返给我们一个device_token,拿到这个token后我们将这个token发给我们自己的应用服务器,当有需要被推送的消息时,我们的应用服务器会将消息按指定的格式打包,然后结合设备的device_token一并发给APNs服务器,由于我们的应用和APNs维持一个基于TCP的长连接,APNs将新消息推送到我们设备上,然后在屏幕上显示出新消息来。
3. 推送流程
3.1 获取设备device_token阶段
整个过程基本就这样,下面我们看一下设备注册APNs的流程图:
上图完成了如下步骤:
1.Device连接APNs服务器并携带设备序列号
2.连接成功,APNs经过打包和处理产生device_token并返回给注册的Device
3.Device携带获取的device_token向我们自己的应用服务器注册
4.完成需要被推送的Device在APNs服务器和我们自己的应用服务器注册
执行顺序如下所示:
这里要提到的一点是,我们的设备和APNS服务器之间的通讯是基于SSL协议的TCP流通讯,二者之间维持一个长连接,当从APNS服务器注册成功后,一定要将device_token发送给我们的应用服务器,因为在推送过程中,首相是由我们的应用服务器(上图中Provider)将需要推送的消息结合device_token按指定格式(后面会提到)打包然后发送给APNS服务器,然后由APNS服务器推送给我们的设备。
3.2 消息推送过程
好了,注册设备的过程完成了,接下来就是如何推送了:
推送的过程经过如下步骤:
1.首先,安装了具有推送功能的应用,我们的设备在有网络的情况下会连接苹果推送服务器,连接过程中,APNS会验证device_token,连接成功后维持一个长连接;
2.Provider(我们自己的服务器)收到需要被推送的消息并结合被推送设备的device_token一起打包发送给APNS服务器;
3.APNS服务器将推送信息推送给指定device_token的设备;
4.设备收到推送消息后通知我们的应用程序并显示和提示用户(声音、弹出框)
3.3 完整流程介绍
比较直观的流程参照下图:
- 应用启用推送通知功能,需要用户确认;
- 应用收到设备识别ID(device token),相当于接收推送通知的地址;
- 应用将设备识别ID发送到你开发的服务器;
- 当有推送通知的需要时,你就可以通过你开发的服务组件发送信息到苹果的服务器上;
- 苹果推送通知服务将信息推送到用户的设备上。
上图显示了我们的应用服务器将消息推送到我们的App的完整路径,其实真正完成推送的是APNS服务器,我们自己的应用服务器只是将需要推送的消息告诉苹果服务器,至于如何维护消息队列或如何保证消息能被推送到指定的设备上,这些都由苹果APNS给我们做完了
4. Push机制类型
四种:徽章、提示框、声音和横幅,具体表现形式如下图
Push机制的4个组件
Provider
APNS
iPhone设备
Client App
其中APNS(Apple Push Notification Service)是由苹果提供的消息推送服务中心,所有的消息都经由这里转发给相应的设备
5. 正式开工
5.1 准备工作
你得有台ios设备,iphone,ipad
5.1.2 为推送通知获取授权
为了给提供者这边开发和配置推送通知,你必须从开发者中心(即苹果官网 DevCenter)取得 SSL 授权证书。每个授权证书限制必须对应一个单独的应用,由应用的Bundle ID 标识。而且该授权证书也被限制用于以下两个环境之一,沙箱环境(用于开发和测试)和生产环境。这些环境都拥有它自己的 IP 地址,而且需要它们自己的授权证书。你必须同时获得这些环境的的配置文件(即 Provisioning profiles)。
5.1.3 提供者和APNs之间通过二进制接口通信
二进制接口是异步的,而且它使用 TCP 方式通过 sock 连接把二进制内容的推送通知发送给 APNs。沙箱和生产环境都有自己独立的接口,每个都有它自己的地址和端口。对于每个接口,你需要使用 TLS(或 SSL)和已经拿到的 SSL 授权证书来建立一条到 APNs 的安全通信通道。提供者把推送通知打包并通过该通道发送给 APNs。APNs 包含了一个反馈服务,它负责维护每个应用的传递通知失败的设备列表(即APNs 当前无法把推送通知传递到这些设备上面的对应的应用)。提供者应该周期性的连接到反馈服务来查看推送失败的设备以便它可以把之前失败的通知重复发送过
去。
开发状态服务器地址 gateway.sandbox.push.apple.com 2195
产品状态服务器地址 gateway.push.apple.com 2195
Development和Production两个版本对应的apns device token是不同的,前者是develop的mobileprovision下获取的。后者是production的mobileprovision获取的。
Development和Production两个版本可以共用一个App ID(不推荐。共用时每次调试前都要删除设备上的app,重新打包生成。而且公用appid会经常抓狂,早上行,下午就不行了。所以不推荐),但是不能共用一个mobileprovision,所以要单独生成Distribution的证书供production版本使用。
注:Distribution的版本是无法在设备上debug调试的!
Development和Production两个版本的code sign是不同的,前者是iPhone Developer,后者是iPhone Distribution。注意不能搞错。
无论是Development Push SSLCertificate还是Production Push SSL Certificate 都有过期时间的。Development Push SSL Certificate有效期大概四个月左右,而ProductionPush SSL Certificate的有效期是一年。需要注意在过期之前生成新的证书,以免影响使用。
5.2 证书生成
5.2.1 过程简介
先概述下大致过程,然后下面会截图给出详细的步骤
在Mac上生成 Apple推送通知SSL许可证:
1. 登录到 apple Developer Connection Portal 并点击 App IDs
2. 创建一个不使用通配符的 App ID 。通配符 ID 不能用于推送通知服务。例如,我们的iPhone程序ID像这样:54im.com.PushChat
3. 点击App ID旁的“Configure”,然后按下按钮生产 推送通知许可证。根据“向导”指导的步骤生成一个签名并上传,最后下载生成的许可证。
4. 通过双击.cer文件将你的 aps_developer_identity.cer 引入Keychain中。
5. 在Mac上启动 Keychain助手,然后在login keychain中选择 Certificates分类。你将看到一个可扩展选项“Apple Development Push Servicescom.54im.PushChat”
6. 扩展此选项然后右击“Apple Development Push Services” > Export “Apple Development Push Services:com.54im.PushChat”。保存为 PushChat_cert.p12 文件。
7. 扩展“Apple Development Push Services” 对“Private Key”做同样操作,保存为 PushChat_key.p12 文件。
8. 需要通过终端命令将这些文件转换为PEM格式:
openssl pkcs12 -clcerts -nokeys -out cert.pem -in PushChat_cert.p12
9. 转换得到key的pem:
openssl pkcs12 -nocerts -out key.pem -in PushChat_key.p12
10. 如果你想要移除密码,要么在导出/转换时不要设定或者执行:
openssl rsa -in key.pem -out key.unencrypted.pem
11. 最后,你需要将键和许可文件合成为apns-dev.pem文件,此文件在连接到APNS时需要使用:
cat apns-dev-cert.pem key.unencrypted.pem > ck.pem
5.2.2 配置详解
1. 创建APPID
首先登陆我们的Apple Developer后台为将要使用推送服务的App新建一个App ID,如下图,点击新建后输入基本信息
我把要改的地方截图下来了,高手勿笑哦,屌丝第一次用mac,也是第一次进苹果开发者后台。
APPID创建好后,我们点编辑刚刚生成好的APPID,生成下development证书,生产情况下用 Production证书
创建正式过程中,要求上传一张Certificate Signing Request 证书请求签名文件
2. 生成证书请求文件
这个请求文件在自己的mac上生成
输入证书信息
3. 生成PUSH证书
还记得刚刚苹果开发者那里要上传的证书不,将生成好的这个.certSigningRequest证书上传上去,
下载aps_development.cer这个证书到mac上,如果是发布版的推送证书,就为aps_production.cer。然后双击该证书,将推送证书安装到我们的Mac机器上,安装成功后会看到如下界面(如果是发布版,则证书的Development部分显示的是Production)
需要为certificate和它之下的private key各自export出一个.p12文件。(会出现设置密码过程)
4. 导出公钥
导出私钥
5. key转换
需要将上面的2个.p12文件转成.pem格式:
openssl pkcs12 -clcerts -nokeys -out cert.pem -in Push_Chat_cert.p12
openssl pkcs12 -nocerts -out key.pem -in Push_Chat_cert_key.p12
如果需要对key不进行加密
openssl rsa -in key.pem -out key.unencrypted.pem
然后就可以 合并两个.pem文件, 这个ck.pem就是服务端需要的证书了
cat cert.pem key.unencrypted.pem > apns-dev.pem
创建 Provisioning Profile
5.3 创建 provisioning profile
接下来,需要创建 provisioning profile 以便允许应用程序安装到真实设备上。
将该prifiles文件下载下来,名字是PushChat.mobileprovision,其实不用下载,xcode里面会根据你的项目id自动去拉对于的这个文件。
在制作测试demo前我们先了解下开源的一个应用 easyapns,或者你也可以写demo,主要就是收集DeviceToken
啥是Easy APNS
Easy APNS 是一个用来管理苹果推送通知的PHP脚本。如果你对苹果推送通知后端部分比较感兴趣,而恰巧你有熟悉PHP,那么Easy APNS是你工具箱中必须的工 具。Easy APNS完全开源,并且设置非常简单。通过使用免费的、开源的PHP脚本,Easy APNS为开发者提供了一种很直观的可以用来控制整个 推送通知后端部分的方式。
项目官网:http://www.easyapns.com/
github地址:https://github.com/manifestinteractive/easyapns
github里面有这几个目录
delegate 将这里面代码添加到新建项目的 AppDelegate.m中
php 将文件放在一个可以访问的web目录下(要有php+mysql环境)
sql 将该目录sql导入到数据库
三、IOS 代码编写
首先,在AppDelegate.m 中:
1,注册通知
1 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 2 // Override point for customization after application launch. 3 ViewController *mainCtrl=[[ViewController alloc] init]; 4 self.window.rootViewController=mainCtrl; 5 6 //注册通知 7 if ([UIDevice currentDevice].systemVersion.doubleValue<8.0) { 8 [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeBadge)]; 9 } 10 else { 11 [[UIApplication sharedApplication] registerForRemoteNotifications]; 12 [[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge|UIUserNotificationTypeSound|UIUserNotificationTypeAlert categories:nil]]; 13 } 14 15 //判断是否由远程消息通知触发应用程序启动 16 if (launchOptions) { 17 //获取应用程序消息通知标记数(即小红圈中的数字) 18 NSInteger badge = [UIApplication sharedApplication].applicationIconBadgeNumber; 19 if (badge>0) { 20 //如果应用程序消息通知标记数(即小红圈中的数字)大于0,清除标记。 21 badge--; 22 //清除标记。清除小红圈中数字,小红圈中数字为0,小红圈才会消除。 23 [UIApplication sharedApplication].applicationIconBadgeNumber = badge; 24 NSDictionary *pushInfo = [launchOptions objectForKey:@"UIApplicationLaunchOptionsRemoteNotificationKey"]; 25 26 //获取推送详情 27 NSString *pushString = [NSString stringWithFormat:@"%@",[pushInfo objectForKey:@"aps"]]; 28 UIAlertView *alert=[[UIAlertView alloc] initWithTitle:@"finish Loaunch" message:pushString delegate:nil cancelButtonTitle:@"cancel" otherButtonTitles:nil, nil nil]; 29 [alert show]; 30 } 31 } 32 33 return YES; 34 }
2,注册通知后,获取device token
1 - (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { 2 NSString *token = [NSString stringWithFormat:@"%@", deviceToken]; 3 NSLog(@"My token is:%@", token); 4 //这里应将device token发送到服务器端 5 } 6 7 - (void)application:(UIApplication *)app didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { 8 NSString *error_str = [NSString stringWithFormat: @"%@", error]; 9 NSLog(@"Failed to get token, error:%@", error_str); 10 }
3,接收推送通知
1 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo 2 { 3 [UIApplication sharedApplication].applicationIconBadgeNumber=0; 4 for (id key in userInfo) { 5 NSLog(@"key: %@, value: %@", key, [userInfo objectForKey:key]); 6 } 7 /* eg. 8 key: aps, value: { 9 alert = "U8fd9U662fU4e00U6761U6d4bU8bd5U4fe1U606f"; 10 badge = 1; 11 sound = default; 12 } 13 */ 14 UIAlertView *alert=[[UIAlertView alloc] initWithTitle:@"remote notification" message:userInfo[@"aps"][@"alert"] delegate:nil cancelButtonTitle:@"cancel" otherButtonTitles:nil, nil nil]; 15 [alert show]; 16 }
注意:app 前台运行时,会调用 remote notification;app后台运行时,点击提醒框,会调用remote notification,点击app 图标,不调用remote notification,没反应;app 没有运行时,点击提醒框,finishLaunching 中,launchOptions 传参,点击app 图标,launchOptions 不传参,不调用remote notification。