概述
APNs推送是IOS App的一個(gè)重要功能走触,一般情況下大部分人都會(huì)采用第三方的集成(極光、個(gè)推等等)刷袍。但是既然是第三方的谷丸,肯定有某些地方不能滿足我們的實(shí)際要求,比如推送的頻率购城、次數(shù)吕座、優(yōu)先級(jí)等其他方面”癜澹基于以上問(wèn)題吴趴,我們還是有必要去搭建一個(gè)自己的推送后臺(tái)的,把所有的可控因素掌握在自己的手里侮攀。
搭建一個(gè)自己的推送后臺(tái)的話锣枝,主要分為推送證書(shū)制作
,收集設(shè)備信息
,推送通知消息
這三個(gè)步驟。
推送證書(shū)制作
推送證書(shū)的制作包括兩個(gè)制作過(guò)程兰英,一個(gè)是在蘋(píng)果開(kāi)發(fā)者后臺(tái)制作推送證書(shū)撇叁,證書(shū)類型選擇Apple Push Notification service SSL (Sandbox & Production)
,第二個(gè)是基于第一步的證書(shū)生成一個(gè).pem格式的證書(shū)提供給后臺(tái)使用。
第一步在蘋(píng)果開(kāi)發(fā)后臺(tái)制作推送證書(shū)箭昵,網(wǎng)上有大把的文章税朴,在這里就不再詳細(xì)描述了,接下來(lái)的著重描述下生成后臺(tái)使用的證書(shū)過(guò)程家制。
Step1
從鑰匙串訪問(wèn)
中導(dǎo)出推送證書(shū)的公鑰和私鑰正林,命名為apns_cert.p12
和apns_key.p12
,導(dǎo)出的格式注意選擇p12類型
Step2
把apns_cert.p12
,apns_key.p12
轉(zhuǎn)換成對(duì)應(yīng)的.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
如果在使用的推送的時(shí)候不用輸入密碼颤殴,就要執(zhí)行本步驟觅廓,把a(bǔ)pns_key.pem中的密碼給去掉,建議執(zhí)行,得到新文件apns_key_noencrypt.pem
openssl rsa -in apns_key.pem -out apns_key_noencrypt.pem
Step4
把兩個(gè).pem文件合成一個(gè) 得到最終可供后臺(tái)使用的文件 apns_product.pem
cat apns_cert.pem apns_key_noencrypt.pem > apns_product.pem
得到最終文件之后涵但,我們可以使用以下命令測(cè)試以下我們生成的證書(shū)是否能用,如果能夠輸出SSL-Session的鏈接信息的話杈绸,就說(shuō)明我們的證書(shū)沒(méi)問(wèn)題了
openssl s_client -connect gateway.sandbox.push.apple.com:2195 -cert apns_product.pem
收集設(shè)備信息
當(dāng)我們需要去給一個(gè)設(shè)備進(jìn)行推送消息的時(shí)候,我們需要知道該設(shè)備的一個(gè)標(biāo)識(shí)矮瘟,這個(gè)標(biāo)識(shí)就是deviceToken了瞳脓。deviceToken是一個(gè)64位長(zhǎng)度的一個(gè)字符串,這個(gè)標(biāo)識(shí)對(duì)于同一個(gè)設(shè)備的不用APP都是不同的澈侠。并且即使是同一個(gè)設(shè)備的同一個(gè)APP而言劫侧,如果APP反復(fù)卸載安裝,這個(gè)標(biāo)示也有可能發(fā)生變化哨啃。因此烧栋,一般來(lái)說(shuō)我們應(yīng)該在APP啟動(dòng)的過(guò)程中去收集deviceToken,然后傳給服務(wù)器保存起來(lái)拳球,服務(wù)器根據(jù)當(dāng)前用戶情況去選擇是否更新用戶的設(shè)備信息审姓。
首先先去蘋(píng)果服務(wù)器注冊(cè)deviceToken
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//注冊(cè)遠(yuǎn)程通知類型
UIUserNotificationSettings *sting = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge | UIUserNotificationTypeAlert | UIUserNotificationTypeSound categories:nil];
[application registerUserNotificationSettings:sting];
[application registerForRemoteNotifications];
}
當(dāng)deviceToken注冊(cè)成功之后,我們可以在這個(gè)方法里面獲取到deviceToken
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{
//把deviceToken存起來(lái)發(fā)給服務(wù)器
}
當(dāng)deviceToken注冊(cè)失敗后祝峻,我們可以再這個(gè)方法里面獲取失敗的信息
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{
//失敗的信息
}
當(dāng)APP接收到遠(yuǎn)程通知時(shí)魔吐,需要根據(jù)APP的三種狀態(tài)進(jìn)行不同的處理
1.APP未啟動(dòng)
這種情況下當(dāng)點(diǎn)擊通知欄進(jìn)入到APP的時(shí)候會(huì)在調(diào)用(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
時(shí)將通知消息放在launchOptions中,而通過(guò)點(diǎn)擊APP圖標(biāo)進(jìn)入應(yīng)用程序時(shí)莱找,該字典是空的酬姆。
2.APP啟動(dòng)在后臺(tái)
這種情況下點(diǎn)擊通知欄進(jìn)入APP時(shí)會(huì)調(diào)用(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
,通知消息會(huì)放在userInfo中
3.APP啟動(dòng)在前臺(tái)
這種情況下接收到遠(yuǎn)程通知時(shí)宋距,并沒(méi)有通知欄的提示和提示聲音轴踱,只能自己去在當(dāng)前界面彈框提示或者在某個(gè)地方顯示一個(gè)數(shù)字標(biāo)示表示有新消息,并且會(huì)自動(dòng)調(diào)用(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
方法谚赎。由于第二種和第三種情況都會(huì)調(diào)用該方法淫僻,所以我們需要在這個(gè)方法里面去根據(jù)APP在前臺(tái)還是后臺(tái)做不同的處理。
推送通知消息
到這一步壶唤,我們的準(zhǔn)備工作就都做好了雳灵,接下來(lái)就可以去寫(xiě)后端的推送邏輯了。需要注意一點(diǎn)的是闸盔,蘋(píng)果提供的APNs推送服務(wù)器有兩個(gè)悯辙,一個(gè)是開(kāi)發(fā)環(huán)境的,一個(gè)是正式環(huán)境的,要根據(jù)實(shí)際情況選擇相應(yīng)的服務(wù)器躲撰。
- 開(kāi)發(fā)環(huán)境 ssl://gateway.sandbox.push.apple.com:2195 一般來(lái)說(shuō)通過(guò)Xcode直接安裝的都會(huì)走這個(gè)
- 正式環(huán)境 ssl://gateway.push.apple.com:2195 通過(guò)ipa包安裝或者APP Store安裝的會(huì)走這個(gè)
后臺(tái)測(cè)試推送代碼
<?php
//推送目標(biāo)設(shè)備號(hào)(測(cè)試環(huán)境和正式環(huán)境不一樣)
$deviceToken = '4f707f4eb373dfbad83188ae1c71ce3dd9eba983b8234b777a24157df20915f4';
//證書(shū)路徑
$pem = dirname(__FILE__) . '/' . 'apns-product.pem';
//測(cè)試服務(wù)器
$apnsHost = 'ssl://gateway.sandbox.push.apple.com:2195';
//正式服務(wù)器
//$apnsHost = 'ssl://gateway.push.apple.com:2195';
$content = '測(cè)試推送內(nèi)容';
$body = array("aps" => 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);
如果沒(méi)有什么問(wèn)題的話针贬,執(zhí)行完這段代碼我們就能在手機(jī)上看到推送了。
注意:上述示例程序僅適用于測(cè)試推送拢蛋,所以在建立連接后只發(fā)送了一個(gè)推送就關(guān)閉了這個(gè)鏈接桦他。如果在實(shí)際環(huán)境中推送的話,我們不可能針對(duì)每一個(gè)推送都去建立一個(gè)鏈接谆棱,即使你這么干了的話快压,在推送的數(shù)量級(jí)別上去了之后可能會(huì)被蘋(píng)果官方當(dāng)做DDOS攻擊。正確的做法是在建立連接后依次寫(xiě)入多條推送的內(nèi)容