概述
APNs推送是IOS App的一个重要功能,一般情况下大部分人都会采用第三方的集成(极光、个推等等)。但是既然是第三方的,肯定有某些地方不能满足我们的实际要求,比如推送的频率、次数、优先级等其他方面。基于以上问题,我们还是有必要去搭建一个自己的推送后台的,把所有的可控因素掌握在自己的手里。
搭建一个自己的推送后台的话,主要分为推送证书制作
,收集设备信息
,推送通知消息
这三个步骤。
推送证书制作
推送证书的制作包括两个制作过程,一个是在苹果开发者后台制作推送证书,证书类型选择Apple Push Notification service SSL (Sandbox & Production)
,第二个是基于第一步的证书生成一个.pem格式的证书提供给后台使用。
第一步在苹果开发后台制作推送证书,网上有大把的文章,在这里就不再详细描述了,接下来的着重描述下生成后台使用的证书过程。
Step1
从钥匙串访问
中导出推送证书的公钥和私钥,命名为apns_cert.p12
和apns_key.p12
,导出的格式注意选择p12类型
Step2
把apns_cert.p12
,apns_key.p12
转换成对应的.pem文件,文件命名为apns_cert.pem
,apns_key.pem
openssl pkcs12 -clcerts -nokeys -out apns_cert.pem -in apns_cert.p12
openssl pkcs12 -nocerts -out apns_key.pem -in apns_key.p12
Step3
如果在使用的推送的时候不用输入密码,就要执行本步骤,把apns_key.pem中的密码给去掉,建议执行,得到新文件apns_key_noencrypt.pem
openssl rsa -in apns_key.pem -out apns_key_noencrypt.pem
Step4
把两个.pem文件合成一个 得到最终可供后台使用的文件 apns_product.pem
cat apns_cert.pem apns_key_noencrypt.pem > apns_product.pem
得到最终文件之后,我们可以使用以下命令测试以下我们生成的证书是否能用,如果能够输出SSL-Session的链接信息的话,就说明我们的证书没问题了
openssl s_client -connect gateway.sandbox.push.apple.com:2195 -cert apns_product.pem
收集设备信息
当我们需要去给一个设备进行推送消息的时候,我们需要知道该设备的一个标识,这个标识就是deviceToken了。deviceToken是一个64位长度的一个字符串,这个标识对于同一个设备的不用APP都是不同的。并且即使是同一个设备的同一个APP而言,如果APP反复卸载安装,这个标示也有可能发生变化。因此,一般来说我们应该在APP启动的过程中去收集deviceToken,然后传给服务器保存起来,服务器根据当前用户情况去选择是否更新用户的设备信息。
首先先去苹果服务器注册deviceToken
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//注册远程通知类型
UIUserNotificationSettings *sting = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge | UIUserNotificationTypeAlert | UIUserNotificationTypeSound categories:nil];
[application registerUserNotificationSettings:sting];
[application registerForRemoteNotifications];
}
当deviceToken注册成功之后,我们可以在这个方法里面获取到deviceToken
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{
//把deviceToken存起来发给服务器
}
当deviceToken注册失败后,我们可以再这个方法里面获取失败的信息
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{
//失败的信息
}
当APP接收到远程通知时,需要根据APP的三种状态进行不同的处理
1.APP未启动
这种情况下当点击通知栏进入到APP的时候会在调用(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
时将通知消息放在launchOptions中,而通过点击APP图标进入应用程序时,该字典是空的。
2.APP启动在后台
这种情况下点击通知栏进入APP时会调用(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
,通知消息会放在userInfo中
3.APP启动在前台
这种情况下接收到远程通知时,并没有通知栏的提示和提示声音,只能自己去在当前界面弹框提示或者在某个地方显示一个数字标示表示有新消息,并且会自动调用(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
方法。由于第二种和第三种情况都会调用该方法,所以我们需要在这个方法里面去根据APP在前台还是后台做不同的处理。
推送通知消息
到这一步,我们的准备工作就都做好了,接下来就可以去写后端的推送逻辑了。需要注意一点的是,苹果提供的APNs推送服务器有两个,一个是开发环境的,一个是正式环境的,要根据实际情况选择相应的服务器。
- 开发环境 ssl://gateway.sandbox.push.apple.com:2195 一般来说通过Xcode直接安装的都会走这个
- 正式环境 ssl://gateway.push.apple.com:2195 通过ipa包安装或者APP Store安装的会走这个
后台测试推送代码
array("alert" => $content,"badge" => 5,"sound"=>'default'),'url'=>'http://keeper.fxtrip.com/fun/index?t=12');
$ctx = stream_context_create();
stream_context_set_option($ctx,"ssl","local_cert",$pem);
$pass = ""; //如果有密码的话
stream_context_set_option($ctx, 'ssl', 'passphrase', $pass);
$fp = stream_socket_client($apnsHost, $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
if (!$fp) {
echo "Failed to connect $err $errstr";
return;
}
print "Connection OK\n";
$payload = json_encode($body);
$msg = chr(0) . pack("n",32) . pack("H*", str_replace(' ', '', $deviceToken)) . pack("n",strlen($payload)) . $payload;
echo "sending message :" . $payload ."\n";
fwrite($fp, $msg);
fclose($fp);
如果没有什么问题的话,执行完这段代码我们就能在手机上看到推送了。
注意:上述示例程序仅适用于测试推送,所以在建立连接后只发送了一个推送就关闭了这个链接。如果在实际环境中推送的话,我们不可能针对每一个推送都去建立一个链接,即使你这么干了的话,在推送的数量级别上去了之后可能会被苹果官方当做DDOS攻击。正确的做法是在建立连接后依次写入多条推送的内容