您的位置:首页 > 移动开发 > IOS开发

IOS 消息推送原理及实现总结

2012-08-17 12:22 387 查看
一、消息推送原理:

在实现消息推送之前先提及几个于推送相关概念,如下图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];
}


服务器端java 实现:

前提准备,

在编写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)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: