写这篇文章的目的是为了自己做笔记,以免下次再出现同样的错误,如果对你有帮助,你可以接着往下看看。。。。
1、配置APNs相关证书
关于相关证书的配置,参考的是网上的博客:http://zxs19861202.iteye.com/blog/1532460。其中公钥和私钥的文件的p12导出一定要导正确,不然在服务器发送时会报错,楼主就犯了这个错误。这块会在后面提及。
特别注意的是,在开发者帐号中创建的APP ID必须与项目中 的Bundle Identifier标识一致才能接收服务器发出的推送!!!!!
然后创建一个Demo,用来测试远程推送,其主要实在AppDelegate.m中加入以下代码:
#define push_server @"http://localhost/simplepush/simplepush.php"
#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
@interface AppDelegate ()
@end
@implementation AppDelegate
// 注册推送通知功能
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//判断系统版本,然后进行通知配置,因为ios8以后skd换了
if(SYSTEM_VERSION_LESS_THAN(@"8.0")){
[application registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];
}else{
//1.创建消息上面要添加的动作(按钮的形式显示出来)
UIMutableUserNotificationAction *action = [[UIMutableUserNotificationAction alloc] init];
action.identifier = @"action";//按钮的标示
action.title=@"Accept";//按钮的标题
action.activationMode = UIUserNotificationActivationModeForeground;//当点击的时候启动程序
// action.authenticationRequired = YES;
// action.destructive = YES;
UIMutableUserNotificationAction *action2 = [[UIMutableUserNotificationAction alloc] init];
action2.identifier = @"action2";
action2.title=@"Reject";
action2.activationMode = UIUserNotificationActivationModeBackground;//当点击的时候不启动程序,在后台处理
action.authenticationRequired = YES;//需要解锁才能处理,如果action.activationMode = UIUserNotificationActivationModeForeground;则这个属性被忽略;
action.destructive = YES;
//2.创建动作(按钮)的类别集合
UIMutableUserNotificationCategory *categorys = [[UIMutableUserNotificationCategory alloc] init];
categorys.identifier = @"alert";//这组动作的唯一标示,推送通知的时候也是根据这个来区分
[categorys setActions:@[action,action2] forContext:(UIUserNotificationActionContextMinimal)];
//3.创建UIUserNotificationSettings,并设置消息的显示类类型
UIUserNotificationSettings *notiSettings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge | UIUserNotificationTypeAlert | UIUserNotificationTypeSound) categories:[NSSet setWithObjects:categorys, nil]];
[application registerUserNotificationSettings:notiSettings];
}
return YES;
}
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
//注册远程通知,ios8以上的注册
[application registerForRemoteNotifications];
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)pToken {
//设备号
NSLog(@"regisger success:%@", pToken);
//注册成功,将deviceToken保存到应用服务器数据库中
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{
//接收到通知
NSDictionary *info = [userInfo objectForKey:@"aps"];
// 处理推送消息
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:@"通知" message:info[@"alert"] delegate:self cancelButtonTitle:@"取消" otherButtonTitles:nil, nil];
[alert show];
NSLog(@"%@", userInfo);
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
NSLog(@"Regist fail%@",error);
//通知错误时候代理
}
#define push_server @"http://localhost/simplepush/simplepush.php"
#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
@interface AppDelegate ()
@end
@implementation AppDelegate
// 注册推送通知功能
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//判断系统版本,然后进行通知配置,因为ios8以后skd换了
if(SYSTEM_VERSION_LESS_THAN(@"8.0")){
[application registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];
}else{
//1.创建消息上面要添加的动作(按钮的形式显示出来)
UIMutableUserNotificationAction *action = [[UIMutableUserNotificationAction alloc] init];
action.identifier = @"action";//按钮的标示
action.title=@"Accept";//按钮的标题
action.activationMode = UIUserNotificationActivationModeForeground;//当点击的时候启动程序
// action.authenticationRequired = YES;
// action.destructive = YES;
UIMutableUserNotificationAction *action2 = [[UIMutableUserNotificationAction alloc] init];
action2.identifier = @"action2";
action2.title=@"Reject";
action2.activationMode = UIUserNotificationActivationModeBackground;//当点击的时候不启动程序,在后台处理
action.authenticationRequired = YES;//需要解锁才能处理,如果action.activationMode = UIUserNotificationActivationModeForeground;则这个属性被忽略;
action.destructive = YES;
//2.创建动作(按钮)的类别集合
UIMutableUserNotificationCategory *categorys = [[UIMutableUserNotificationCategory alloc] init];
categorys.identifier = @"alert";//这组动作的唯一标示,推送通知的时候也是根据这个来区分
[categorys setActions:@[action,action2] forContext:(UIUserNotificationActionContextMinimal)];
//3.创建UIUserNotificationSettings,并设置消息的显示类类型
UIUserNotificationSettings *notiSettings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge | UIUserNotificationTypeAlert | UIUserNotificationTypeSound) categories:[NSSet setWithObjects:categorys, nil]];
[application registerUserNotificationSettings:notiSettings];
}
return YES;
}
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
//注册远程通知,ios8以上的注册
[application registerForRemoteNotifications];
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)pToken {
//设备号
NSLog(@"regisger success:%@", pToken);
//注册成功,将deviceToken保存到应用服务器数据库中
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{
//接收到通知
NSDictionary *info = [userInfo objectForKey:@"aps"];
// 处理推送消息
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:@"通知" message:info[@"alert"] delegate:self cancelButtonTitle:@"取消" otherButtonTitles:nil, nil];
[alert show];
NSLog(@"%@", userInfo);
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
NSLog(@"Regist fail%@",error);
//通知错误时候代理
}
#define push_server @"http://localhost/simplepush/simplepush.php"
#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
@interface AppDelegate ()
@end
@implementation AppDelegate
// 注册推送通知功能
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//判断系统版本,然后进行通知配置,因为ios8以后skd换了
if(SYSTEM_VERSION_LESS_THAN(@"8.0")){
[application registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];
}else{
//1.创建消息上面要添加的动作(按钮的形式显示出来)
UIMutableUserNotificationAction *action = [[UIMutableUserNotificationAction alloc] init];
action.identifier = @"action";//按钮的标示
action.title=@"Accept";//按钮的标题
action.activationMode = UIUserNotificationActivationModeForeground;//当点击的时候启动程序
// action.authenticationRequired = YES;
// action.destructive = YES;
UIMutableUserNotificationAction *action2 = [[UIMutableUserNotificationAction alloc] init];
action2.identifier = @"action2";
action2.title=@"Reject";
action2.activationMode = UIUserNotificationActivationModeBackground;//当点击的时候不启动程序,在后台处理
action.authenticationRequired = YES;//需要解锁才能处理,如果action.activationMode = UIUserNotificationActivationModeForeground;则这个属性被忽略;
action.destructive = YES;
//2.创建动作(按钮)的类别集合
UIMutableUserNotificationCategory *categorys = [[UIMutableUserNotificationCategory alloc] init];
categorys.identifier = @"alert";//这组动作的唯一标示,推送通知的时候也是根据这个来区分
[categorys setActions:@[action,action2] forContext:(UIUserNotificationActionContextMinimal)];
//3.创建UIUserNotificationSettings,并设置消息的显示类类型
UIUserNotificationSettings *notiSettings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge | UIUserNotificationTypeAlert | UIUserNotificationTypeSound) categories:[NSSet setWithObjects:categorys, nil]];
[application registerUserNotificationSettings:notiSettings];
}
return YES;
}
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
//注册远程通知,ios8以上的注册
[application registerForRemoteNotifications];
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)pToken {
//设备号
NSLog(@"regisger success:%@", pToken);
//注册成功,将deviceToken保存到应用服务器数据库中
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{
//接收到通知
NSDictionary *info = [userInfo objectForKey:@"aps"];
// 处理推送消息
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:@"通知" message:info[@"alert"] delegate:self cancelButtonTitle:@"取消" otherButtonTitles:nil, nil];
[alert show];
NSLog(@"%@", userInfo);
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
NSLog(@"Regist fail%@",error);
//通知错误时候代理
}
#define push_server @"http://localhost/simplepush/simplepush.php"
#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
@interface AppDelegate ()
@end
@implementation AppDelegate
// 注册推送通知功能
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//判断系统版本,然后进行通知配置,因为ios8以后skd换了
if(SYSTEM_VERSION_LESS_THAN(@"8.0")){
[application registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];
}else{
//1.创建消息上面要添加的动作(按钮的形式显示出来)
UIMutableUserNotificationAction *action = [[UIMutableUserNotificationAction alloc] init];
action.identifier = @"action";//按钮的标示
action.title=@"Accept";//按钮的标题
action.activationMode = UIUserNotificationActivationModeForeground;//当点击的时候启动程序
// action.authenticationRequired = YES;
// action.destructive = YES;
UIMutableUserNotificationAction *action2 = [[UIMutableUserNotificationAction alloc] init];
action2.identifier = @"action2";
action2.title=@"Reject";
action2.activationMode = UIUserNotificationActivationModeBackground;//当点击的时候不启动程序,在后台处理
action.authenticationRequired = YES;//需要解锁才能处理,如果action.activationMode = UIUserNotificationActivationModeForeground;则这个属性被忽略;
action.destructive = YES;
//2.创建动作(按钮)的类别集合
UIMutableUserNotificationCategory *categorys = [[UIMutableUserNotificationCategory alloc] init];
categorys.identifier = @"alert";//这组动作的唯一标示,推送通知的时候也是根据这个来区分
[categorys setActions:@[action,action2] forContext:(UIUserNotificationActionContextMinimal)];
//3.创建UIUserNotificationSettings,并设置消息的显示类类型
UIUserNotificationSettings *notiSettings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge | UIUserNotificationTypeAlert | UIUserNotificationTypeSound) categories:[NSSet setWithObjects:categorys, nil]];
[application registerUserNotificationSettings:notiSettings];
}
return YES;
}
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
//注册远程通知,ios8以上的注册
[application registerForRemoteNotifications];
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)pToken {
//设备号
NSLog(@"regisger success:%@", pToken);
//注册成功,将deviceToken保存到应用服务器数据库中
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{
//接收到通知
NSDictionary *info = [userInfo objectForKey:@"aps"];
// 处理推送消息
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:@"通知" message:info[@"alert"] delegate:self cancelButtonTitle:@"取消" otherButtonTitles:nil, nil];
[alert show];
NSLog(@"%@", userInfo);
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
NSLog(@"Regist fail%@",error);
//通知错误时候代理
}
#define push_server @"http://localhost/simplepush/simplepush.php"
#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
@interface AppDelegate ()
@end
@implementation AppDelegate
// 注册推送通知功能
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//判断系统版本,然后进行通知配置,因为ios8以后skd换了
if(SYSTEM_VERSION_LESS_THAN(@"8.0")){
[application registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];
}else{
//1.创建消息上面要添加的动作(按钮的形式显示出来)
UIMutableUserNotificationAction *action = [[UIMutableUserNotificationAction alloc] init];
action.identifier = @"action";//按钮的标示
action.title=@"Accept";//按钮的标题
action.activationMode = UIUserNotificationActivationModeForeground;//当点击的时候启动程序
// action.authenticationRequired = YES;
// action.destructive = YES;
UIMutableUserNotificationAction *action2 = [[UIMutableUserNotificationAction alloc] init];
action2.identifier = @"action2";
action2.title=@"Reject";
action2.activationMode = UIUserNotificationActivationModeBackground;//当点击的时候不启动程序,在后台处理
action.authenticationRequired = YES;//需要解锁才能处理,如果action.activationMode = UIUserNotificationActivationModeForeground;则这个属性被忽略;
action.destructive = YES;
//2.创建动作(按钮)的类别集合
UIMutableUserNotificationCategory *categorys = [[UIMutableUserNotificationCategory alloc] init];
categorys.identifier = @"alert";//这组动作的唯一标示,推送通知的时候也是根据这个来区分
[categorys setActions:@[action,action2] forContext:(UIUserNotificationActionContextMinimal)];
//3.创建UIUserNotificationSettings,并设置消息的显示类类型
UIUserNotificationSettings *notiSettings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge | UIUserNotificationTypeAlert | UIUserNotificationTypeSound) categories:[NSSet setWithObjects:categorys, nil]];
[application registerUserNotificationSettings:notiSettings];
}
return YES;
}
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
//注册远程通知,ios8以上的注册
[application registerForRemoteNotifications];
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)pToken {
//设备号
NSLog(@"regisger success:%@", pToken);
//注册成功,将deviceToken保存到应用服务器数据库中
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{
//接收到通知
NSDictionary *info = [userInfo objectForKey:@"aps"];
// 处理推送消息
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:@"通知" message:info[@"alert"] delegate:self cancelButtonTitle:@"取消" otherButtonTitles:nil, nil];
[alert show];
NSLog(@"%@", userInfo);
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
NSLog(@"Regist fail%@",error);
//通知错误时候代理
}
代码写好了,我们还需要在target ->Capabilities ->打开Push Notification,如图:
最后在真机上运行程序,会获得真机的token。
2、配置服务器
至于配置服务器,首先需要在mac的终端启动Apache和PHP,由于楼主不懂PHP,所以借鉴这篇文章:http://www.tuicool.com/articles/ZjmqYb3
原本我在没搭建简单的PHP服务器的情况下,以为会很难,不过确实很难,难在我不懂PHP啊!所以不停的查资料看看怎么搭建,以下是楼主的搭建过程(基于PHP已经开启的状态):
1)Finder -> 前往文件夹 -> 输入: /Library/WebServer/Documents/ -> 前往。跳转到服务器文件夹,在里面创建一个文件夹simple push -> 将之前公钥和私钥合成的pem文件复制到这个文件夹 -> 创建一个PHP文件simplepush.php,其代码主要为:
set_time_limit(0);
sleep(5);
// 这里是我们上面得到的deviceToken,直接复制过来(记得去掉空格)
$deviceToken = 'da7a0ac7db89f6707d67627d5702673c8fa3d666b0c52ba793159351bc9f4a9d';
// Put your private key's passphrase here:
$passphrase = '123456';
// Put your alert message here:
$message = 'My first push test!';
////////////////////////////////////////////////////////////////////////////////
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'allow_self_signed', true);
stream_context_set_option($ctx, 'ssl', 'verify_peer', false);
stream_context_set_option($ctx, 'ssl', 'local_cert', 'ck.pem');
stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);
// Open a connection to the APNS server
//这个为正是的发布地址
// $fp = stream_socket_client('ssl://gateway.push.apple.com:2195', $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
//这个是沙盒测试地址,发布到appstore后记得修改哦
$fp = stream_socket_client('ssl://gateway.sandbox.push.apple.com:2195', $err,$errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);
//$fp=stream_socket_client("udp://127.0.0.1:1113", $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
if (!$fp)
exit("Failed to connect: $err $errstr" . PHP_EOL);
echo 'Connected to APNS' . PHP_EOL;
// Create the payload body
$body['aps'] = array(
'alert' => $message,
'sound' => 'default'
);
// Encode the payload as JSON
$payload = json_encode($body);
// Build the binary notification
$msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;
// Send it to the server
$result = fwrite($fp, $msg, strlen($msg));
if (!$result)
echo 'Message not delivered' . PHP_EOL;
else
echo 'Message successfully delivered' . PHP_EOL;
// Close the connection to the server
fclose($fp);
?>
将上面代码复制粘贴到simplepush.php中,只需要改动其中三个地方:
第一个是:deviceToken,将1中获取的token复制过来
第二个是:passphrase,是在合成私钥时输入的密码
第三个是:local_cert后面的ck.pem,改成自己合成的公钥和私钥pem的合成文件xx.pem。
最后在浏览器输入:http://localhost/simplepush/simplepush.php
如果显示为:Connected to APNS Message successfully delivered则已经推送成功了。手机将收到推送,如图
3、楼主在运行时出现的各种问题以及解决方案
主要遇到了两个大问题:
第一个是PHP服务器的,我在浏览器上输入http://localhost/simplepush/simplepush.php运行服务器时报错:
Warning: stream_socket_client(): SSL operation failed with code 1. OpenSSL Error messages: error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure in /Library/WebServer/Documents/simplepush/simplepush.php on line 123
Warning: stream_socket_client(): Failed to enable crypto in /Library/WebServer/Documents/simplepush/simplepush.php on line 123
Warning: stream_socket_client(): unable to connect to ssl://gateway.sandbox.push.apple.com:2195 (Unknown error) in /Library/WebServer/Documents/simplepush/simplepush.php on line 123
Failed to connect: 0
如果是这个错误,一般需要检查你合成的证书有没有错!楼主就是在导出公钥时出错了。。。最后的解决方案是:
导出私钥Key.p12 在终端输入命令 openssl pkcs12 -nocerts -out Key.pem -in Key.p12,然后将aps_development.cer转换成p12.命令为:openssl x509 -in aps_development.cer -inform der -out Cert.pem,最后再将Key.pem和Cert.pem合成ck.pem。再次运行服务器就可以啦!
第二个问题是关于ID的问题,由于证书的App ID必须与Bundle Identifier一致,但楼主傻了,去target -> General -> Team添加了开发者帐号,导致identifier是一致的,但是老是提示楼主这个ID不可用,需要重新创建。
这个问题困扰了楼主好几天。。。一直没想通。。看了好多前辈的方案什么的,,还是不行。。。今天突然灵机一动,重新创建了一个程序,没加入Team之前在Capabilites里面会有Push Notification这个选项。。于是我就打开它,运行程序,,就可以了。。真的就可以了。。当时真的懵了、、不过能运行起来就很开心了