1. 程式人生 > >蘋果推送通知服務(APNs)程式設計

蘋果推送通知服務(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.
	}