蘋果推送通知服務(APNs)程式設計
iPhone 對於應用程式在後臺執行有諸多限制(除非你越獄)。因此,當用戶切換到其他程式後,原先的程式無法保持執行狀態。對於那些需要保持持續連線狀態的應用程式(比如社群網路應用),將不能收到實時的資訊。
為解決這一限制,蘋果推出了APNs(蘋果推送通知服務)。APNs 允許裝置與蘋果的推送通知伺服器保持常連線狀態。當你想傳送一個推送通知給某個使用者的iPhone上的應用程式時,你可以使用 APNs 傳送一個推送訊息給目標裝置上已安裝的某個應用程式。
本文中,你將學到建立使用 APNs 的iOS 應用的詳細步驟。
建立證書請求
使用APNs 的第一步是生成一個證書請求,使用該證書請求來申請一個用於開發的 SSL 證書。
1. 開啟“鑰匙串訪問”應用程式。
2. 選擇“KeychainAccess -> Certificate Assistant -> Request a Certificate From CertificateAuthority”(如圖1 所示):
3. 輸入所需的資訊,勾選“Saved to disk”選項,點選 Continue(如圖2 所示):
4. 使用預設檔名把證書請求進行儲存(圖3):在彈出視窗中,點選Done。
建立 App ID
每個使用 APNs 的 iOS 應用必須有一個唯一的 App ID。在本步驟中,你將學到如何建立推送通知中要用到的App ID。
1. 登入iPhoneDeveloper Program:http://developer.apple.com/iphone/。點選頁面右邊的“ iPhone Developer Program Portal ”(圖4):
2. 首先看到的是歡迎頁面(圖5):
3. 點選左邊的“App ID”,然後點選右邊的“New App ID”按鈕(圖6):
4. 在 Description 欄輸入“PushAppID”,在“Bundle Seed ID”欄中選擇“Generate New”。在“Bundle Identifier”欄,輸入“net.learn2develop.MyPushApp”,然後點選“Submit”(圖7):
5. 現在你應該能看到所建立的 App ID 了(圖8):
配置 App
一旦建立了 App ID,你還要為推送通知對 App ID 進行一些配置。
1. 點選App ID 右邊的 Configure 連結,會看到如下選項(圖9):
勾選“Enable for Apple Push Notificationservice”,點選“Development Push SSL Certificate”右邊的“Configure”按鈕。
2. 接下來你會看到“Apple Push Notification service SSL Certificate Assistant”頁面。點選Continue(圖10):
3. 點選Choose File 按鈕,選擇前面儲存的證書請求檔案存放地址。點選 Generate(圖11):
4. 你的SSL 證書會被生成。點選 Continue(圖12):
5. 點選Download Now 按鈕,下載 SSL 證書。點選 Done(圖13):
6. 下載的 SSL 證書檔名為 aps.developer.identity.cer。雙擊,將證書安裝到鑰匙串中(圖14)。這個證書會在你的程式中用到,它允許程式接收 APNs 傳送來的推送通知。
建立 Provisioning Profile
接下來,需要建立 provisioning profile 以便允許應用程式安裝到真實裝置上。
1. 回到iPhone Development Program Portal,點選 Provisioning 欄,點選 New Profile 按鈕(圖15):
2. Profile Name 欄輸入 MyDevicesProfile,在 App ID 欄選擇 PushAppID。在Devices 欄,勾選所有你想啟用的裝置(在 iPhone Developer Program Portal 的 Devices 頁中註冊的所有裝置)。點選 Submit(圖16)。
3. provisioning profile 會等待稽核。幾秒鐘後,它會顯示在頁面上。點選Download 按鈕下載該 provisioning profile(圖17):
4. 下載下來的provisioning profile 名為 MydevicesProfile.mobileprovision。
啟用裝置
建立 provision profile 後,你可以將它安裝到真實裝置中。
1. 將iPhone 或 iPod 連線到 Mac。
2. 把下載下來的 MyDevicesProfile.mobileprovision 檔案拖到Dock 欄的 Xcode 圖示上。
3. Xcode 的 Organizer 程式將啟動,選擇當前連機的裝置。可以看到MyDevicesProfile 已自動安裝到裝置上了(圖18)。
建立 iPhone 應用程式
1. 開啟Xcode,建立 View-Based Application 專案,命名為 ApplePushNotification。
2. 把一個 WAV 檔案(本例是 beep.wav)拖到Xcode 的 Resouces 資料夾(圖19)。
3. 展開Xcode 中的 Targets 專案,選擇ApplePushNotification,按下 ⌘+i,在 info 出口,點選Properties 標籤欄(圖20):
在 Identifier 文字框,輸入net.learn2develop.MyPushApp.
4. 點選 Build 標籤欄,在 search 輸入框中鍵入Code Signing。在 Any iPhone OS Device 選項,選擇正確的 profile(圖21):
5. 在 ApplePushNotificationAppDelegate.m 檔案中,輸入以下程式碼(加粗部分):
#import "ApplePushNotificationAppDelegate.h"
#import "ApplePushNotificationViewController.h"
@implementation ApplePushNotificationAppDelegate
@synthesize window;
@synthesize viewController;
- (void)applicationDidFinishLaunching:(UIApplication*)application {
[window addSubview:viewController.view];
[window makeKeyAndVisible];
NSLog(@"Registeringfor push notifications...");
[[UIApplication sharedApplication]
registerForRemoteNotificationTypes:
(UIRemoteNotificationTypeAlert |
UIRemoteNotificationTypeBadge |
UIRemoteNotificationTypeSound)];
}
- (void)application:(UIApplication*)appdidRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
stringWithFormat:@"Device Token=%@",deviceToken];
NSLog(str);
}
- (void)application:(UIApplication*)appdidFailToRegisterForRemoteNotificationsWithError:(NSError *)err {
NSString *str = [NSStringstringWithFormat: @"Error: %@", err];
NSLog(str);
}
- (void)application:(UIApplication*)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
for (id key in userInfo) {
NSLog(@"key: %@, value: %@", key, [userInfo objectForKey:key]);
}
}
- (void)dealloc {
[viewController release];
[window release];
[super dealloc];
}
@end
6. 按下 ⌘+R 執行程式(在真實裝置)。按下 shift+⌘+R 顯示Debugger Console 視窗。檢視裝置輸出到控制檯的 device token(圖22)。在下圖,device token 是 38c866dd bb323b39 ffa73487 5e157ee5 a85e0b7c e90d56e9fe145bcc 6c2c594b。記下device token(複製、貼上到一個文字檔案裡)
7. 如果檢視 iPhone/iPod 上的“Settings”程式,你會發現一個 Notifications 的項(圖23):
建立 Push Notification Provider
Push Notification Provider 是一個應用程式,用於通過 APNs 傳送推送通知給 iPhone 應用。
通過 APNs 傳送推送通知有幾個步驟:
1. 使用前面建立的 SSL 證書與 APNs 通訊;
2. 構造所要傳送的訊息載體;
3. 傳送載體到APNs;
APNs 是一個基於流的 TCP socket,你的 provider 以 SSL 協議與其通訊。推送通知(包括載體)是以二進位制流的方式傳送的。和APNs 建立連線後,你可以維持該連線並在連線中斷之前傳送多個通知。
技巧: 應避免每傳送一次推送通知就建立、關閉一次連線。頻繁的建立、關閉連線可能會被 APNs 認為是 DOS 攻擊,從而拒絕傳送 provider 的推送通知傳送請求。
一個推送通知訊息的格式如圖24 所示:
載體(payload)是 JSON 字串(最長 256 位元組),封裝了你傳送給 iOS 應用的資訊。這是一個 payload 的例子:
{
"aps": {
"alert" : "Yougot a new message!" ,
"badge" : 5,
"sound" : "beep.wav"},
"acme1" : "bar",
"acme2" : 42
}
寫provider之前,我們需要生成php Push Notification sender需要的證書檔案:
1)在Keychain Access.app裡選定這個新證書(Apple Development Push Services*),匯出到桌面,儲存為Certificates.p12.
2)然後執行如下命令:
1. openssl pkcs12 -clcerts -nokeys -out cert.pem -in Certificates.p12
2. openssl pkcs12 -nocerts -out key.pem -in Certificates.p12
3. openssl rsa -in key.pem -out key.unencrypted.pem
4. cat cert.pem key.unencrypted.pem > ck.pem
下面是一個簡單的push notification proivder寫法:
<?php
$deviceToken = '38c866dd bb323b39 ffa73487 5e157ee5 a85e0b7ce90d56e9 fe145bcc 6c2c594b'; // masked for security reason
// Passphrase for the private key (ck.pem file)
// $pass = '';
// Get the parameters from http get or from command line
$message = $_GET['message'] or $message = $argv[1] or $message = 'Message received from javacom';
$badge = (int)$_GET['badge'] or $badge = (int)$argv[2];
$sound = $_GET['sound'] or $sound = $argv[3];
// Construct the notification payload
$body = array();
$body['aps'] = array('alert' => $message);
if ($badge)
$body['aps']['badge'] = $badge;
if ($sound)
$body['aps']['sound'] = $sound;
/* End of Configurable Items */
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'ck.pem');
// assume the private key passphase was removed.
// stream_context_set_option($ctx, 'ssl', 'passphrase', $pass);
$fp = stream_socket_client('ssl://gateway.sandbox.push.apple.com:2195', $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
if (!$fp) {
print "Failed to connect $err $errstrn";
return;
}
else {
print "Connection OKn";
}
$payload = json_encode($body);
$msg = chr(0) . pack("n",32) . pack('H*', str_replace(' ', '', $deviceToken)) . pack("n",strlen($payload)) . $payload;
print "sending message :" . $payload . "n";
fwrite($fp, $msg);
fclose($fp);
?>
執行結果:Connection OKnsending message :{"aps":{"alert":"Message received from javacom"}}n
效果圖在下面。
為了省去自己編寫 push notification provider 的麻煩,你也可以使用 Stefan Hafeneger 寫的一個 Mac OS X 應用程式:PushMeBaby,下載地址
1. 在Xcode 中開啟 PushMeBaby。
2. 右擊 Resouces 資料夾,選擇 Add Existing Files…,選擇前面所下載到的aps.developer.identity.cer 檔案(圖25)。
3. 在 ApplicationDelegate.m 檔案中,修改如下程式碼(加粗部分):
- (id)init {
self = [super init];
if(self != nil) {
self.deviceToken = @"38c866dd bb323b39 ffa73487 5e157ee5 a85e0b7ce90d56e9 fe145bcc 6c2c594b";
self.payload = @"{\"aps\":{\"alert\":\"Yougot a new message!\",\"badge\":5,\"sound\":\"beep.wav\"},\"acme1\":\"bar\",\"acme2\":42}";
self.certificate = [[NSBundle mainBundle]
pathForResource:@"aps_developer_identity" ofType:@"cer"];
}
return self;
}
4. 按下 ⌘+R,執行程式。將會問你是否允許使用證書,點選Always Allow(總是允許)(圖26):
在 iPhone/iPod,確認 ApplePushNotification 程式未執行。點選 Push 按鈕,會向裝置傳送一條推送通知。伺服器實際上傳送了下列訊息給APN 伺服器:
{
"aps": {
"alert" : "Yougot a new message!" ,
"badge" : 5,
"sound" : "beep.wav"},
"acme1" : "bar",
"acme2" : 42
}
5. 如果訊息推送成功,將會在 iPhone/iPod 上出現下圖(圖27):
6. 如果現在按下 ⌘+R 除錯 ApplePushNotification 程式,然後從 PushMeBaby 中傳送一條訊息,控制檯會顯示如下輸出:
2009-11-24 21:11:49.182 ApplePushNotification[1461:207]key: acme1, value: bar
2009-11-24 21:11:49.187 ApplePushNotification[1461:207]key: aps, value: {
alert = "You got a new message!";
badge = 5;
sound = "beep.wav";
}
2009-11-24 21:11:49.191 ApplePushNotification[1461:207]key: acme2, value: 42
幾個注意的問題:
1.如果申請ssl 證書時不是用的新的apple id,而是原來已經存在的,那麼設定好之後要把對應的provisioning profile
也更新一下, 然後去下載新的profile替換掉老的,不然執行會有錯。
2.如果你用的是企業版的開發者證書,別人可能沒有許可權去申請這個ssl 證書,當你替他申請好證書後,應該把證書和證書對
的私鑰一起發給他,這樣他在本地安裝私鑰時才會有對應的金鑰。
3.當push notification到達時,程式狀態不同,效果也是不一樣的,一般來說程式可以分為下面三種狀態:
1)程式不在執行(後臺和前臺都不在執行)
這時候如果push notification到了,會彈出一個alertview,當你點選action按鈕時,會啟動程式,並執行程式delegate.m檔案裡的
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
方法,所以你可以在這裡做一些處理:
//看是否有push notification到達,並做相應處理,這個方法和local notification相同,但注意key要對應就行
UILocalNotification * remoteNotification = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if (remoteNotification) {
//彈出一個alertview,顯示相應資訊
UIAlertView * al = [[UIAlertView alloc]initWithTitle:@"receive remote notification!" message:@"hello" delegate:self cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil];
[al show];
[al release];
}
2)程式在執行(不論是在前臺還是後臺)
當push notification到達時,如果程式在前臺執行並不會彈出alertview,而是直接執行下面方法:
/**
* Remote Notification Received while application was open.
*/
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
UIAlertView * al = [[UIAlertView alloc]initWithTitle:@"receive remote notification!" message:@"hey" delegate:self cancelButtonTitle:@"ok" otherButtonTitles:nil, nil];
[al show];
[al release];
}
在這個方法裡你可以獲取到userInfo字典來進行相應處理。
如果程式是在後臺執行,則會彈出一個alertview,當你點選action按鈕,也會執行上面一樣的方法。
所以如果你想要程式在push notification到達時,針對前臺和後臺執行做區分處理,你可以在上面方法裡先做一個狀態的
判斷:
//可以根據application狀態來判斷,程式當前是在前臺還是後臺
UIApplicationState state = [application applicationState];
if (state == UIApplicationStateInactive) {
// Application was in the background when notification
// was delivered.
}