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

一、消息推送原理:

 

在实现消息推送之前先提及几个于推送相关概念,如下图1-1

 

IOS 消息推送原理及实现总结 - Milo - IOS  JAVA KEY

 1-1

1、              Provider:就是为指定IOS设备应用程序提供Push的服务器,(如果IOS设备的应用程序是客户端的话,那么Provider可以理解为服务端[消息的发起者]);

2、              APNSApple Push Notification Service[苹果消息推送服务器]

3、              iPhone:用来接收APNS下发下来的消息;

4、              Client AppIOS设备上的应用程序,用来接收iphone传递APNS下发的消息到制定的一个客户端 app[消息的最终响应者]

上图可以分为三个阶段:

阶段一:Provider[服务端]把要发送的消息,目的IOS设备标识打包,发送给APNS

阶段二:APNS在自身的已注册Push服务的IOS设备列表中,查找有相应标识的IOS设备,并将消息发送到IOS设备;

阶段三:IOS设备把发送的消息传递给对应的应用程序,并且按照设定弹出Push通知。

具体过程,如下图1-2

IOS 消息推送原理及实现总结 - Milo - IOS  JAVA KEY

 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-12-2所示。

IOS 消息推送原理及实现总结 - Milo - IOS  JAVA KEY

 2-1

IOS 消息推送原理及实现总结 - Milo - IOS  JAVA KEY

 2-2

如果生成成功,则会在[钥匙串访问|登录|密钥]栏目中列出与*.certSigningRequest关联的密钥,这里是PushDemo,如图2-3所示:

IOS 消息推送原理及实现总结 - Milo - IOS  JAVA KEY

 2-3

2、             新建一个App ID(在苹果开发者账号中配置)

(1) 登录iOS Dev Center,登录成功后,点击(iOS Provisioning Portal对应链接),如图2-4所示:

IOS 消息推送原理及实现总结 - Milo - IOS  JAVA KEY

 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所示:

IOS 消息推送原理及实现总结 - Milo - IOS  JAVA KEY

 2-5

       这样就会生成下面这条记录,如图2-6所示:

IOS 消息推送原理及实现总结 - Milo - IOS  JAVA KEY

 2-6

(3) 配置上一步中生成的App ID,让其支持消息推送[点击2-6中的Configureà选中Enable for Apple Push Notification serviceà点击Configure],如图2-7所示:

IOS 消息推送原理及实现总结 - Milo - IOS  JAVA KEY

 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所示:

IOS 消息推送原理及实现总结 - Milo - IOS  JAVA KEY

 2-8

(5) 创建Development Provisioning Profiles[开发许可配置文件]Provisioning| Development|New Profile,具体操作流程如下图2-9所示:

IOS 消息推送原理及实现总结 - Milo - IOS  JAVA KEY

 

 2-9

       点击图2-9Submit,生成Development Provisioning Profiles[开发许可配置文件],这里是:iShopDevprofile.mobileprovision如下图2-10所示:

IOS 消息推送原理及实现总结 - Milo - IOS  JAVA KEY

 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)

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值