利用APNS进行消息推送
原理
APNS 是Apple Push Notification Service(Apple Push服务器)的缩写,是苹果的服务器。
APNS推送可以分为三个阶段:
第一阶段:推送服务器应用程序把要发送的消息、目的iphone的标识打包,发给APNS。
第二阶段:APNS在自身的已注册推送服务的iPhone列表中,查找有相应标识的iPhone,并把消息发到iPhone。
第三阶段:iPhone把发来的消息传递给相应的应用程序,并且按照设定弹出推送通知。
详细流程如下:
1、首先是应用程序注册消息推送服务。
2、APNS向应用程序返回deviceToken。
3、应用程序将deviceToken发送给推送服务端程序。
4、服务端程序向APNS服务发送消息。
5、APNS服务将消息发送给iPhone应用程序。
证书生成
网上有很多关于证书生成的详细步骤,这里不再说明了。
最终生成的证书共包含下面四个
1、pushNotification.certSigningRequest
2、aps_development.cer(下载生成的支持推送服务的证书。)
3、pushNotificationDevPRofile.mobileprovision
4、pushNotification.p12
下面直接上代码。
客户端
1、应用程序注册消息推送服务
在AppDelegate.m的(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法中加入注册消息通知推送服务。
1- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions2{3//判断是否由远程消息通知触发应用程序启动4if([launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey]!=nil) {5NSLog(@"远程消息通知触发应用程序启动");6}7//消息推送注册8[[UIApplication sharedApplication] registerForRemoteNotificationTypes:UIRemoteNotificationTypeSound|UIRemoteNotificationTypeAlert|UIRemoteNotificationTypeBadge];9}
2、接收deviceToken的方法
在项目的AppDelegate.m中加入以下两个代理方法
1- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {2NSString *token = [NSString stringWithFormat:@"%@", deviceToken];3//获取终端设备标识,标识获取后需要将其发送到服务器端,服务器端推送消息到APNS时需要知道终端的标识,APNS通过注册的终端标识找到终端设备。4NSLog(@"My token is:%@", token);5}6- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {7NSString *error_str = [NSString stringWithFormat:@"%@", error];8NSLog(@"Failed to get token, error:%@", error_str);9}
3、接收消息的处理方法
在项目AppDelegate.m中加入消息接收处理代理方法。
1- (void)application:(UIApplication *)application2didReceiveRemoteNotification:(NSDictionary *)userInfo3{4//在此处理接收到的消息。5NSLog(@"Receive remote notification : %@",userInfo);6}
至此,IOS端的代码已经码完了。(^_^)
服务器
服务器端可以用php、java、.net等语言实现。本文使用Java语言实现
1、导入jar包
工程建好之后,将JavaPNS_2.2.jar、javapns-jdk16-163.jar、bcprov-jdk16-145.jar这几个jar包拷贝到工程的lib目录下。
2、生成服务器端所用的.p12文件(.net或Java等后台)
在mac终端下执行以下指令:
(1)、将aps_development.cer转换成aps_development.pem格式
$ openssl x509 -in aps_development.cer -inform DER -out aps_development.pem -outform PEM
(2)、将p12格式的私钥转换成pem
$ openssl pkcs12 -nocerts -out Push_Noenc.pem -in pushNotification.p12
(3)、创建p12文件
$ openssl pkcs12 -export -in aps_development.pem -inkey Push_Noenc.pem -certfile pushNotification.certSigningRequest -name "aps_development" -out aps_development.p12
这样我们就得到了在.net或java等后台应用程序中使用的证书文件:aps_development.p12
3、编写服务器端代码
1package com.push.server;2import java.io.FileInputStream;3import java.io.FileNotFoundException;4import java.io.IOException;5import java.util.ArrayList;6import java.util.List;7import java.util.Properties;89import javapns.devices.Device;10import javapns.devices.implementations.basic.BasicDevice;11import javapns.notification.AppleNotificationServerBasicImpl;12import javapns.notification.PushNotificationManager;13import javapns.notification.PushNotificationPayload;14import javapns.notification.PushedNotification;1516publicclassSendToAPNS {17//从客户端获取的deviceToken,在此为了测试,设一个固定值18privatestaticfinal String DEVICE_TOKEN =1920"13b11050a3fc064b3692e25c0fbd3b774b39ecb0c55a51ff4fb1373e004577a0";21List<String> deviceToken =newArrayList<String>();2223publicvoidsend () {24//证书文件(.p12)在服务器端的目录25String filePath =null;26try{27String path =this.getClass().getClassLoader().getResource("/").getPath();28filePath = java.net.URLDecoder.decode(path,"utf-8");29}catch(Exception e){30e.printStackTrace();31}32System.out.println("filePath="+filePath);33String certificatePath = (filePath +"conf/ios_development.p12");34//获取ios_development.p12的密码35Properties prop =newProperties();36FileInputStream fis =null;37try{38fis =newFileInputStream(filePath +"conf/pushmessage.properties");3940}catch(FileNotFoundException e) {41//TODO Auto-generated catch block42e.printStackTrace();43}44try{45prop.load(fis);46}catch(IOException e) {47//TODO Auto-generated catch block48e.printStackTrace();49}50String certificatePassWord= prop.getProperty("password");5152//构建发送的消息53String message="{'aps':{'alert':'this is a push message'}}";5455//设别标识56deviceToken.add(DEVICE_TOKEN);57//发送消息58sendpush(deviceToken, certificatePath, certificatePassword, message,4,false);59}6061/************************************************62测试用URL gateway.sandbox.push.apple.com /219563正式发布用URL gateway.push.apple.com / 219564javaPNS_2.2.jar必须65***************************************************/66/**67* @param tokens iphone设备的唯一标识6869* @param path .p12证书文件的路径7071* @param password .p12证书文件的密码7273* @param message 发送的消息内容7475* @param count7677* @param sendCount true 一对一发送 false 群发7879*/80publicvoidsendpush(List<String>tokens,String path, String password, String message,Integer8182count,boolean sendCount) {83try{84//message:{"aps":{"alert":"一条新消息"}}85PushNotificationPayload payLoad =PushNotificationPayload.fromJSON(message);86//payLoad.addAlert(message);87payLoad.addBadge(count);88payLoad.addSound("default");8990PushNotificationManager pushManager =newPushNotificationManager();91//true 正式发布用URL92//false 测试用URL93pushManager.initializeConnection(newAppleNotificationServerBasicImpl(path, password,9495false));96List<PushedNotification> notifications =newArrayList<PushedNotification>();97//推送方式98if(sendCount) {99System.out.println("-------现在进行一对一推送-------");100Device device =newBasicDevice();101device.setToken(tokens.get(0));102PushedNotification notification =pushManager.sendNotification(device, payLoad,103104true);105notifications.add(notification);106}else{107System.out.println("------现在进行群发-------");108List<Device> device =newArrayList<Device>();109for(String token : tokens) {110device.add(newBasicDevice(token));111}112notifications =pushManager.sendNotifications(payLoad, device);113}114115List<PushedNotification> failedNotifications =PushedNotification.findFailedNotifications116117(notifications);118List<PushedNotification> successfulNotifications =119120PushedNotification.findSuccessfulNotifications(notifications);121intfailed =failedNotifications.size();122intsuccessful =successfulNotifications.size();123124if(successful >0&& failed ==0) {125//log.debug("-----All notifications pushed success (" +126127successfulNotifications.size() +"):");128System.out.println("-----All notifications pushed success ("+129130successfulNotifications.size() +"):");131}132elseif(successful ==0&& failed >0) {133//log.debug("-----All notifications pushed failed(" + failedNotifications.size() +134135"):");136System.out.println("-----All notifications pushed failed("+137138failedNotifications.size() +"):");139}140elseif(successful ==0&& failed ==0) {141System.out.println("No notifications could be sent, probably because of a critical142143error");144}145else{146//log.debug("------Some notifications pushed failed (" + failedNotifications.size147148() +"):");149//log.debug("------Others pushed success(" + successfulNotifications.size() +150151"):");152System.out.println("------Some notifications pushed failed ("+153154failedNotifications.size() +"):");155System.out.println("------Others pushed success("+successfulNotifications.size()156157+"):");158}159pushManager.stopConnection();160161}catch(Exception e) {162e.printStackTrace();163}164}165}
至此,服务器端代码也码完了。(^_^)
APNS推送的特点
从APNS的文档中大概总结了以下几点:
1、提供单一发送和群发功能。
2、当用户手机不在线(可能没有信号或者关机),APNs会存储转发,等用户在线时再发送。
3、如果用户长时间不在线,这条信息会被忽略。
4、如果用户不在线,通知会合并,只会保留最新的一条。
5、payload,就是最后生成的那段Json,不得超过256字节。如果超过了,建议去掉一些不需要的参数,把alert,就是提示
信息的字数减少。
6、发送成功的没有返回,只有发送失败的才会返回。
7、如果有error-response,那么这条之后的通知都需要重发。
8、如果出错了,需要关闭当前的连接,并且重新连接再发。error-response中返回的通知ID,可以帮助我们找出哪条出错
了,这样就能知道哪些需要重发了。
9、不要反复多次连接、终止与APNS的连接,否则会被APNS拒绝连接。
10、APNS的feedback service会返回那些已经卸载的设备的deviceToken。对于这些token,下次就不用再发了。可以节省
点资源。需要注意的是:feedback的接口读取一次,APNS就会清空它的列表,下次再读取时,返回的就是这两次读取之间这
段时间新产生的deviceToken