一、消息推送原理:
在实现消息推送之前先提及几个于推送相关概念,如下图1-1:
1-1
1、 Provider:就是为指定IOS设备应用程序提供Push的服务器,(如果IOS设备的应用程序是客户端的话,那么Provider可以理解为服务端[消息的发起者]);
2、 APNS:Apple Push Notification Service[苹果消息推送服务器];
3、 iPhone:用来接收APNS下发下来的消息;
4、 Client App:IOS设备上的应用程序,用来接收iphone传递APNS下发的消息到制定的一个客户端 app[消息的最终响应者];
上图可以分为三个阶段:
阶段一:Provider[服务端]把要发送的消息,目的IOS设备标识打包,发送给APNS;
阶段二:APNS在自身的已注册Push服务的IOS设备列表中,查找有相应标识的IOS设备,并将消息发送到IOS设备;
阶段三:IOS设备把发送的消息传递给对应的应用程序,并且按照设定弹出Push通知。
具体过程,如下图1-2:
1-2
1、 [Client App]注册消息推送;
2、 [Client App]跟[APNS Service]要deviceToken, Client App接收deviceToken;
3、 [Client App]将deviceToken发送给[Provider]Push服务端程序;
4、 当Push服务端程序满足发送消息条件了,[Provider]向[APNS Service]发送消息;
5、 [APNS Service]将消息发送给[Client App].
消息推送实现:
1、 生成*.certSigningRequest文件,步骤如下:
[MacBookà应用程序à实用工具à钥匙串访问à证书助手à从证书机构求证书?à证书信息(用户电子邮箱地址{填写您的邮箱,如:your@email.com},常用名称{任意,如:PushDemo},请求是:{单选,选择‘存储到磁盘’})à继续à保存],这时会在您指定的地方生成你指定的文件,默认为CertificateSigningRequest.certSigningRequest文件,这里命名为:PushDemo.certSigningRequest.在此*.certSigningRequest已经生成,具体操作步骤如图2-1、2-2所示。
2-1
2-2
如果生成成功,则会在[钥匙串访问|登录|密钥]栏目中列出与*.certSigningRequest关联的密钥,这里是PushDemo,如图2-3所示:
2-3
2、 新建一个App ID(在苹果开发者账号中配置)
(1) 登录iOS Dev Center,登录成功后,点击(iOS Provisioning Portal对应链接),如图2-4所示:
2-4
(2) 创建New App ID[App IDsàManageàNew App ID]( Description{填写您对此App ID 的描述,如:iShop},Bundle Seed ID(App ID Prefix){选择绑定App ID前缀,如:默认选择Generate New},Bundle Identifier(App ID Suffix){填写绑定App ID后缀,如:com.yourcorp.iShop}),如下图2-5所示:
2-5
这样就会生成下面这条记录,如图2-6所示:
2-6
(3) 配置上一步中生成的App ID,让其支持消息推送[点击2-6中的Configureà选中Enable for Apple Push Notification serviceà点击Configure],如图2-7所示:
2-7
(4) Generate a Certificate Signing Request(生成部署请求认证)[点击2-7中的2ConfigureàContinueà步骤1生成的*certSigningRequest文件(这里是iShop. certSigningRequest)à Generateà生成完成后将其下载下来,命名为:aps_developer_identity.cer],双击aps_developer_identity.cer证书{将证书与密钥关联,并将证书导入到MacBook中},如下图2-8所示:
2-8
(5) 创建Development Provisioning Profiles[开发许可配置文件](Provisioning| Development|New Profile),具体操作流程如下图2-9所示:
2-9
点击图2-9中Submit,生成Development Provisioning Profiles[开发许可配置文件],这里是:iShopDevprofile.mobileprovision如下图2-10所示:
2-10
下载此开发许可证书(用于联机调试)。
总结,到现在为止,我们已经生成:A:*.certSigningRequest文件(在步骤(4)中使用,用于生成证书B)、B:aps_developer_identity.cer证书(在Provider[Push服务器]服务端应用使用)、C:*..mobileprovision开发许可配置文件(在Client App客户端应用联机调试使用)。
至此,消息推送的配置已经全部完成,接下来的工作就是编写Provider[Push服务器]服务端应用和Client App客户端应用的程序
Client App客户端
推送通知的步骤:
1、询问是否允许推送通知。
2、如果用户允许在APPDELEGATE 中实现
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{
}
3、将token发送到服务器上
4、服务器收到toke后 发送推送通知,客户端相应该推送同通知
代码如下:
- (void)applicationDidBecomeActive:(UIApplication *)application
{
/*
Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
*/
//每次醒来都需要去判断是否得到device token
[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(registerForRemoteNotificationToGetToken) userInfo:nil repeats:NO];
//hide the badge
application.applicationIconBadgeNumber = 0;
[[CheckVersion sharedCVInstance] checkVersionOfServer];
[[AnalyticsUtil sharedAnalyticsUtil] appLaunch];
AnalyticsJSONElement *viewElement = [[AnalyticsJSONElement alloc] init];
viewElement.jsonType = AnalyticsJSONTypeView;
viewElement.typeID = @"0";
[[AnalyticsUtil sharedAnalyticsUtil] postAnalyticsMsgToServerWithElement:viewElement];
[viewElement release];
}
- (void)applicationWillTerminate:(UIApplication *)application
{
/*
Called when the application is about to terminate.
Save data if appropriate.
See also applicationDidEnterBackground:.
*/
}
#pragma mark -
#pragma mark - Getting Device token for Notification support
//向服务器申请发送token 判断事前有没有发送过
- (void)registerForRemoteNotificationToGetToken
{
NSLog(@"Registering for push notifications...");
//注册Device Token, 需要注册remote notification
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
if (![userDefaults boolForKey:DeviceTokenRegisteredKEY]) //如果没有注册到令牌 则重新发送注册请求
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:
(UIRemoteNotificationTypeNewsstandContentAvailability |
UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge |
UIRemoteNotificationTypeSound)];
});
}
//将远程通知的数量置零
dispatch_async(dispatch_get_global_queue(0,0), ^{
//1 hide the local badge
if ([[UIApplication sharedApplication] applicationIconBadgeNumber] == 0) {
return;
}
// [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
//2 ask the provider to set the BadgeNumber to zero
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSString *deviceTokenStr = [userDefaults objectForKey:DeviceTokenStringKEY];
[self resetBadgeNumberOnProviderWithDeviceToken:deviceTokenStr];
});
}
//允许的话 自动回调的函数
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
//将device token转换为字符串
NSString *deviceTokenStr = [NSString stringWithFormat:@"%@",deviceToken];
//modify the token, remove the "<, >"
NSLog(@" deviceTokenStr lentgh: %d ->%@", [deviceTokenStr length], [[deviceTokenStr substringWithRange:NSMakeRange(0, 72)] substringWithRange:NSMakeRange(1, 71)]);
deviceTokenStr = [[deviceTokenStr substringWithRange:NSMakeRange(0, 72)] substringWithRange:NSMakeRange(1, 71)];
NSLog(@"deviceTokenStr = %@",deviceTokenStr);
//将deviceToken保存在NSUserDefaults
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
//保存 device token 令牌,并且去掉空格
[userDefaults setObject:[deviceTokenStr stringByReplacingOccurrencesOfString:@" " withString:@""] forKey:DeviceTokenStringKEY];
//send deviceToken to the service provider
dispatch_async(dispatch_get_global_queue(0,0), ^{
//没有在service provider注册Device Token, 需要发送令牌到服务器
if ( ![userDefaults boolForKey:DeviceTokenRegisteredKEY] )
{
NSLog(@" 没有 注册Device Token");
[self sendProviderDeviceToken:deviceTokenStr];
}
});
}
- (void)application:(UIApplication *)app didFailToRegisterForRemoteNotificationsWithError:(NSError *)err {
NSString *str = [NSString stringWithFormat: @"Error: %@", err];
NSLog(@"获取令牌失败: %@",str);
//如果device token获取失败则需要重新获取一次
//[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(registerForRemoteNotificationToGetToken) userInfo:nil repeats:NO];
}
//获取远程通知
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
NSLog(@"received badge number ---%@ ----",[[userInfo objectForKey:@"aps"] objectForKey:@"badge"]);
for (id key in userInfo) {
NSLog(@"key: %@, value: %@", key, [userInfo objectForKey:key]);
}
NSLog(@"the badge number is %d", [[UIApplication sharedApplication] applicationIconBadgeNumber]);
NSLog(@"the application badge number is %d", application.applicationIconBadgeNumber);
application.applicationIconBadgeNumber += 1;
// We can determine whether an application is launched as a result of the user tapping the action
// button or whether the notification was delivered to the already-running application by examining
// the application state.
//当用户打开程序时候收到远程通知后执行
if (application.applicationState == UIApplicationStateActive) {
// Nothing to do if applicationState is Inactive, the iOS already displayed an alert view.
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"温馨提示"
message:[NSString stringWithFormat:@"\n%@",
[[userInfo objectForKey:@"aps"] objectForKey:@"alert"]]
delegate:self
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
dispatch_async(dispatch_get_global_queue(0,0), ^{
//hide the badge
application.applicationIconBadgeNumber = 0;
//ask the provider to set the BadgeNumber to zero
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSString *deviceTokenStr = [userDefaults objectForKey:DeviceTokenStringKEY];
[self resetBadgeNumberOnProviderWithDeviceToken:deviceTokenStr];
});
[alertView show];
[alertView release];
}
}
// http://192.168.11.24/ClientInterface.ashx?action= savetoken&clientid=3898329492492424924932&token=343424324242
#pragma mark -
#pragma mark - Getting Device token for Notification support
//发送token
- (void)sendProviderDeviceToken: (NSString *)deviceTokenString
{
// Establish the request
NSLog(@"sendProviderDeviceToken = %@", deviceTokenString);
NSString *UDIDString = [[UIDevice currentDevice] uniqueIdentifier];
NSString *body = [NSString stringWithFormat:@"action=savetoken&clientid=%@&token=%@", UDIDString, deviceTokenString];
NSString *baseurl = [NSString stringWithFormat:@"%@?",URL_OF_PUSH_NOTIFICATION_SERVER]; //服务器地址
NSLog(@"send provider device token = %@", baseurl);
NSURL *url = [NSURL URLWithString:baseurl];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];
[urlRequest setHTTPMethod: @"POST"];
[urlRequest setHTTPBody:[body dataUsingEncoding:NSUTF8StringEncoding]];
[urlRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
NSURLConnection *tConnection = [[NSURLConnection alloc] initWithRequest: urlRequest delegate: self];
self.deviceTokenConnetion = [tConnection retain];
[tConnection release];
}
#pragma mark -
#pragma mark - reset Badge Number
- (void)resetBadgeNumberOnProviderWithDeviceToken: (NSString *)deviceTokenString
{
NSLog(@" reset Provider DeviceToken %@", deviceTokenString);
isNotificationSetBadge = YES;
// Establish the request
NSString *body = [NSString stringWithFormat:@"action=setbadge&token=%@", [deviceTokenString stringByReplacingOccurrencesOfString:@" " withString:@""]];
NSString *baseurl = [NSString stringWithFormat:@"%@?", URL_OF_PUSH_NOTIFICATION_SERVER]; //服务器地址
NSURL *url = [NSURL URLWithString:baseurl];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];
[urlRequest setHTTPMethod: @"POST"];
[urlRequest setHTTPBody:[body dataUsingEncoding:NSUTF8StringEncoding]];
[urlRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
NSURLConnection *tConnection = [[NSURLConnection alloc] initWithRequest: urlRequest delegate: self];
self.deviceTokenConnetion = [tConnection retain];
[tConnection release];
}
#pragma mark -
#pragma mark - NSURLConnection delegate function
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSHTTPURLResponse *resp = (NSHTTPURLResponse *)response;
NSLog(@"Response statusCode: %d", resp.statusCode);
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSString *rsp = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"connection 2 Received data = %@ ", rsp);
//if the string from provider is "true", means the devicetoken is stored in the provider server
//so the app won't send the devicetoken next time.
if (isNotificationSetBadge == NO) {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
if([rsp isEqualToString:@"true"])
{
NSLog(@"connection 2.2 Received data = %@ ", rsp);
[userDefaults setBool:YES forKey:DeviceTokenRegisteredKEY];
}
}else{//isNotificationSetBadge == YES;
NSLog(@"connection 2 reset");
isNotificationSetBadge = NO;
}
[rsp release];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(@"connection 3 Did Finish Loading ");
[self.deviceTokenConnetion cancel];
}
前提准备,
在编写push notification之获取device token中拿到device token以后,需要把token字符串发送给应用 的服务 器端,即provider。
对于越狱手机获取不到 device token的可以通过cydia安装pushdoctor,安装方法可以google一下在这就不多说了,我的越狱手机通过安装push补丁可以获取token了。
provider将token号、通知内容、通知形式(比如是否弹出提示 窗口、是否发声等)发送给苹果的服务器(apns)。
最简单的provider实现,其实就是通过证书,和苹果服务器建立安全连接(tsl或ssl),通过认证建立连接后,向苹果服务器发送符合苹果要求的数据流。
获得证书
苹果提供两种接入方式的证书:
developer,用于测试
production,用于产品
如果是内部测试,使用developer方式即可。
下载证书,通过ios provisioning portal:
这要求:
登录的apple developer program帐号必须是级别最高的agent(这是针对企业帐号来说的,如果是个人帐号就无所谓了),agent帐号即创始帐号,否则看不到configure链接;
必须经过configure操作,已经enable了developer和product。
然后进入configure链接,点击download按钮即可:
处理证书
如果是编写在mac下跑的objc程序,无需对证书做处理,可跳过这一步。
如果是在java下使用 ,需要把打证书用的私有专用密钥和上述的支持通知的证书(注意,不是iphone developer证书)合并导出。
生成证书:
点击存储的时候,会提示生成一个文件密码:
当然可以密码为空。
之后会提示:
这里需要输入mac登录用户的密码。
文件生成。
编写发送通知的实例
如果是编写mac代码,有一个现成的项目可用:http://stefan.hafeneger.name/download/PushMeBabySource.zip
导入到xcode中,只需将:
deviceToken填写成设备的token字符串,另外,pathForResource改为上面图中的:
aps_developer_identity
另外,要把刚才获得证书步骤中下载的证书复制到xcode项目Resources目录下:
可以看到文件名和上面的pathForResource的参数一致。
之后运行程序就可以在设备上收到推送通知
第三方依赖包(下载在下面):
bcprov-jdk16-145-1.jar
commons-io-2.0.1.jar
commons-lang-2.5.jar
log4j-1.2.16.jar
javapns-jdk16-163.jar
服务器段代码如下(JAVA):
package com.sdunisi.iphone.apns.send;
import java.util.HashMap;
import java.util.Iterator;
import javapns.back.PushNotificationManager;
import javapns.back.SSLConnectionHelper;
import javapns.data.Device;
import javapns.data.PayLoad;
public class MainApnsSend {
public static void main(String[] args) throws Exception {
try {
String deviceToken = "e775b5892f3334427c14def8aa4d8189a4ec1c795020072f4baa7ee92e50b1db";//iphone手机获取的token
PayLoad payLoad = new PayLoad();
payLoad.addAlert("我的push测试");//push的内容
payLoad.addBadge(1);//图标小红圈的数值
payLoad.addSound("default");//铃音
PushNotificationManager pushManager = PushNotificationManager.getInstance();
pushManager.addDevice("iPhone", deviceToken);
//Connect to APNs
/************************************************
测试的服务器地址:gateway.sandbox.push.apple.com /端口2195
产品推送服务器地址:gateway.push.apple.com / 2195
***************************************************/
String host= "gateway.sandbox.push.apple.com";
int port = 2195;
String certificatePath= "/Users/jcjc/Desktop/push_p.p12";//导出的证书
String certificatePassword= "sunlg";//此处注意导出的证书密码不能为空因为空密码会报错
pushManager.initializeConnection(host,port, certificatePath,certificatePassword, SSLConnectionHelper.KEYSTORE_TYPE_PKCS12);
//Send Push
Device client = pushManager.getDevice("iPhone");
pushManager.sendNotification(client, payLoad);
pushManager.stopConnection();
pushManager.removeDevice("iPhone");
}
catch (Exception e) {
e.printStackTrace();
}
}
}
依赖包 开发工具包.zip (2384 K)
工程代码 apns_iphone.zip (2348 K)