1. 什么是推送通知
消息通知分本地通知和遠程推送通知,是沒有運行在前台的應用程序可以讓它們的用戶獲得相關消息通知的方式。消息通知可能是一條消息,即將發生的日歷事件,或遠程服務器的新數據。當被操作系統顯示時,本地通知和推送通知看起來一樣。它們可以顯示一個警告信息或在應用程序的圖標上面顯示一個徽標。它們也可以在警告窗或徽標顯示時播放一段聲音。推送通知是在 iOS 3.0 和 Mac OS X v7.0 之后引入的。本地通知是在 iOS 4.0 之后引入的。它們都不支持 Mac OS X,當用戶被通知相應的應用程序有消息,事件,或其他數據時,他們可以啟動該應用程序並查看詳情。他們也可以選擇忽略通知,此時應用程序沒有被激活。
本地通知和推送通知為不同的需求而設計的。本地通知是本地 iPhone、iPad、或iPod touch 上面的應用發起的。相反推送通知(又稱遠程通知)是從其他設備上面到達的。它來自一個遠程設備——應用程序的提供者——並在有新的消息需要查看或新的數據需要下載的時候被推送到本地設備上面的應用,常見的本地通知像iphone的日歷,微信或者qq這些都是本地推送,比如還安裝了優酷,qq視頻這些軟件,允許推送后,每天會給你發些新的視頻消息,這些就是遠程推送。
2. 什么是APNS?
蘋果推送通知服務(APNs)是推送通知的網關,iPhone ipad 對於應用程序在后台運行有諸多限制,考慮到手機電池電量,應用不允許在后台進行過多的操作。因此,當用戶切換到其他程序后,原先的程序無法保持運行狀態。對於那些需要保持持續連接狀態的應用程序(比如社區網絡應用),將不能收到實時的信息。推送是解決輪詢所造成的流量消耗和電量消耗的一個比較好的解決方案
為解決這一限制,蘋果推出了APNs(蘋果推送通知服務 Apple Push Notification services)。APNs 允許設備與蘋果的推送通知服務器保持常連接狀態。當你想發送一個推送通知給某個用戶的iPhone上的應用程序時,你可以使用 APNs 發送一個推送消息給目標設備上已安裝的某個應用程序。
蘋果的推送服務APNs基本原理簡單來說就是蘋果利用自己專門的推送服務器(APNs)接收來自我們自己應用服務器的需要被推送的信息,然后推送到指定的iOS設備上,然后由設備通知到我們的應用程序,設備以通知或者聲音的形式通知用戶有新的消息。推送的前提是裝有我們應用的設備需要向APNs服務器注冊,注冊成功后APNs服務器會返給我們一個device_token,拿到這個token后我們將這個token發給我們自己的應用服務器,當有需要被推送的消息時,我們的應用服務器會將消息按指定的格式打包,然后結合設備的device_token一並發給APNs服務器,由於我們的應用和APNs維持一個基於TCP的長連接,APNs將新消息推送到我們設備上,然后在屏幕上顯示出新消息來。
3. 推送流程
3.1 獲取設備device_token階段
整個過程基本就這樣,下面我們看一下設備注冊APNs的流程圖:
上圖完成了如下步驟:
1.Device連接APNs服務器並攜帶設備序列號
2.連接成功,APNs經過打包和處理產生device_token並返回給注冊的Device
3.Device攜帶獲取的device_token向我們自己的應用服務器注冊
4.完成需要被推送的Device在APNs服務器和我們自己的應用服務器注冊
執行順序如下所示:
這里要提到的一點是,我們的設備和APNS服務器之間的通訊是基於SSL協議的TCP流通訊,二者之間維持一個長連接,當從APNS服務器注冊成功后,一定要將device_token發送給我們的應用服務器,因為在推送過程中,首相是由我們的應用服務器(上圖中Provider)將需要推送的消息結合device_token按指定格式(后面會提到)打包然后發送給APNS服務器,然后由APNS服務器推送給我們的設備。
3.2 消息推送過程
好了,注冊設備的過程完成了,接下來就是如何推送了:
推送的過程經過如下步驟:
1.首先,安裝了具有推送功能的應用,我們的設備在有網絡的情況下會連接蘋果推送服務器,連接過程中,APNS會驗證device_token,連接成功后維持一個長連接;
2.Provider(我們自己的服務器)收到需要被推送的消息並結合被推送設備的device_token一起打包發送給APNS服務器;
3.APNS服務器將推送信息推送給指定device_token的設備;
4.設備收到推送消息后通知我們的應用程序並顯示和提示用戶(聲音、彈出框)
3.3 完整流程介紹
比較直觀的流程參照下圖:
- 應用啟用推送通知功能,需要用戶確認;
- 應用收到設備識別ID(device token),相當於接收推送通知的地址;
- 應用將設備識別ID發送到你開發的服務器;
- 當有推送通知的需要時,你就可以通過你開發的服務組件發送信息到蘋果的服務器上;
- 蘋果推送通知服務將信息推送到用戶的設備上。
上圖顯示了我們的應用服務器將消息推送到我們的App的完整路徑,其實真正完成推送的是APNS服務器,我們自己的應用服務器只是將需要推送的消息告訴蘋果服務器,至於如何維護消息隊列或如何保證消息能被推送到指定的設備上,這些都由蘋果APNS給我們做完了
4. Push機制類型
四種:徽章、提示框、聲音和橫幅,具體表現形式如下圖
Push機制的4個組件
Provider
APNS
iPhone設備
Client App
其中APNS(Apple Push Notification Service)是由蘋果提供的消息推送服務中心,所有的消息都經由這里轉發給相應的設備
5. 正式開工
5.1 准備工作
你得有台ios設備,iphone,ipad
5.1.2 為推送通知獲取授權
為了給提供者這邊開發和配置推送通知,你必須從開發者中心(即蘋果官網 DevCenter)取得 SSL 授權證書。每個授權證書限制必須對應一個單獨的應用,由應用的Bundle ID 標識。而且該授權證書也被限制用於以下兩個環境之一,沙箱環境(用於開發和測試)和生產環境。這些環境都擁有它自己的 IP 地址,而且需要它們自己的授權證書。你必須同時獲得這些環境的的配置文件(即 Provisioning profiles)。
5.1.3 提供者和APNs之間通過二進制接口通信
二進制接口是異步的,而且它使用 TCP 方式通過 sock 連接把二進制內容的推送通知發送給 APNs。沙箱和生產環境都有自己獨立的接口,每個都有它自己的地址和端口。對於每個接口,你需要使用 TLS(或 SSL)和已經拿到的 SSL 授權證書來建立一條到 APNs 的安全通信通道。提供者把推送通知打包並通過該通道發送給 APNs。APNs 包含了一個反饋服務,它負責維護每個應用的傳遞通知失敗的設備列表(即APNs 當前無法把推送通知傳遞到這些設備上面的對應的應用)。提供者應該周期性的連接到反饋服務來查看推送失敗的設備以便它可以把之前失敗的通知重復發送過
去。
開發狀態服務器地址 gateway.sandbox.push.apple.com 2195
產品狀態服務器地址 gateway.push.apple.com 2195
Development和Production兩個版本對應的apns device token是不同的,前者是develop的mobileprovision下獲取的。后者是production的mobileprovision獲取的。
Development和Production兩個版本可以共用一個App ID(不推薦。共用時每次調試前都要刪除設備上的app,重新打包生成。而且公用appid會經常抓狂,早上行,下午就不行了。所以不推薦),但是不能共用一個mobileprovision,所以要單獨生成Distribution的證書供production版本使用。
注:Distribution的版本是無法在設備上debug調試的!
Development和Production兩個版本的code sign是不同的,前者是iPhone Developer,后者是iPhone Distribution。注意不能搞錯。
無論是Development Push SSLCertificate還是Production Push SSL Certificate 都有過期時間的。Development Push SSL Certificate有效期大概四個月左右,而ProductionPush SSL Certificate的有效期是一年。需要注意在過期之前生成新的證書,以免影響使用。
5.2 證書生成
5.2.1 過程簡介
先概述下大致過程,然后下面會截圖給出詳細的步驟
在Mac上生成 Apple推送通知SSL許可證:
1. 登錄到 apple Developer Connection Portal 並點擊 App IDs
2. 創建一個不使用通配符的 App ID 。通配符 ID 不能用於推送通知服務。例如,我們的iPhone程序ID像這樣:54im.com.PushChat
3. 點擊App ID旁的“Configure”,然后按下按鈕生產 推送通知許可證。根據“向導”指導的步驟生成一個簽名並上傳,最后下載生成的許可證。
4. 通過雙擊.cer文件將你的 aps_developer_identity.cer 引入Keychain中。
5. 在Mac上啟動 Keychain助手,然后在login keychain中選擇 Certificates分類。你將看到一個可擴展選項“Apple Development Push Servicescom.54im.PushChat”
6. 擴展此選項然后右擊“Apple Development Push Services” > Export “Apple Development Push Services:com.54im.PushChat”。保存為 PushChat_cert.p12 文件。
7. 擴展“Apple Development Push Services” 對“Private Key”做同樣操作,保存為 PushChat_key.p12 文件。
8. 需要通過終端命令將這些文件轉換為PEM格式:
openssl pkcs12 -clcerts -nokeys -out cert.pem -in PushChat_cert.p12
9. 轉換得到key的pem:
openssl pkcs12 -nocerts -out key.pem -in PushChat_key.p12
10. 如果你想要移除密碼,要么在導出/轉換時不要設定或者執行:
openssl rsa -in key.pem -out key.unencrypted.pem
11. 最后,你需要將鍵和許可文件合成為apns-dev.pem文件,此文件在連接到APNS時需要使用:
cat apns-dev-cert.pem key.unencrypted.pem > ck.pem
5.2.2 配置詳解
1. 創建APPID
首先登陸我們的Apple Developer后台為將要使用推送服務的App新建一個App ID,如下圖,點擊新建后輸入基本信息
我把要改的地方截圖下來了,高手勿笑哦,屌絲第一次用mac,也是第一次進蘋果開發者后台。
APPID創建好后,我們點編輯剛剛生成好的APPID,生成下development證書,生產情況下用 Production證書
創建正式過程中,要求上傳一張Certificate Signing Request 證書請求簽名文件
2. 生成證書請求文件
這個請求文件在自己的mac上生成
輸入證書信息
3. 生成PUSH證書
還記得剛剛蘋果開發者那里要上傳的證書不,將生成好的這個.certSigningRequest證書上傳上去,
下載aps_development.cer這個證書到mac上,如果是發布版的推送證書,就為aps_production.cer。然后雙擊該證書,將推送證書安裝到我們的Mac機器上,安裝成功后會看到如下界面(如果是發布版,則證書的Development部分顯示的是Production)
需要為certificate和它之下的private key各自export出一個.p12文件。(會出現設置密碼過程)
4. 導出公鑰
導出私鑰
5. key轉換
需要將上面的2個.p12文件轉成.pem格式:
openssl pkcs12 -clcerts -nokeys -out cert.pem -in Push_Chat_cert.p12
openssl pkcs12 -nocerts -out key.pem -in Push_Chat_cert_key.p12
如果需要對key不進行加密
openssl rsa -in key.pem -out key.unencrypted.pem
然后就可以 合並兩個.pem文件, 這個ck.pem就是服務端需要的證書了
cat cert.pem key.unencrypted.pem > apns-dev.pem
創建 Provisioning Profile
5.3 創建 provisioning profile
接下來,需要創建 provisioning profile 以便允許應用程序安裝到真實設備上。
將該prifiles文件下載下來,名字是PushChat.mobileprovision,其實不用下載,xcode里面會根據你的項目id自動去拉對於的這個文件。
在制作測試demo前我們先了解下開源的一個應用 easyapns,或者你也可以寫demo,主要就是收集DeviceToken
啥是Easy APNS
Easy APNS 是一個用來管理蘋果推送通知的PHP腳本。如果你對蘋果推送通知后端部分比較感興趣,而恰巧你有熟悉PHP,那么Easy APNS是你工具箱中必須的工 具。Easy APNS完全開源,並且設置非常簡單。通過使用免費的、開源的PHP腳本,Easy APNS為開發者提供了一種很直觀的可以用來控制整個 推送通知后端部分的方式。
項目官網:http://www.easyapns.com/
github地址:https://github.com/manifestinteractive/easyapns
github里面有這幾個目錄
delegate 將這里面代碼添加到新建項目的 AppDelegate.m中
php 將文件放在一個可以訪問的web目錄下(要有php+mysql環境)
sql 將該目錄sql導入到數據庫
三、IOS 代碼編寫
首先,在AppDelegate.m 中:
1,注冊通知
1 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 2 // Override point for customization after application launch. 3 ViewController *mainCtrl=[[ViewController alloc] init]; 4 self.window.rootViewController=mainCtrl; 5 6 //注冊通知 7 if ([UIDevice currentDevice].systemVersion.doubleValue<8.0) { 8 [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeBadge)]; 9 } 10 else { 11 [[UIApplication sharedApplication] registerForRemoteNotifications]; 12 [[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge|UIUserNotificationTypeSound|UIUserNotificationTypeAlert categories:nil]]; 13 } 14 15 //判斷是否由遠程消息通知觸發應用程序啟動 16 if (launchOptions) { 17 //獲取應用程序消息通知標記數(即小紅圈中的數字) 18 NSInteger badge = [UIApplication sharedApplication].applicationIconBadgeNumber; 19 if (badge>0) { 20 //如果應用程序消息通知標記數(即小紅圈中的數字)大於0,清除標記。 21 badge--; 22 //清除標記。清除小紅圈中數字,小紅圈中數字為0,小紅圈才會消除。 23 [UIApplication sharedApplication].applicationIconBadgeNumber = badge; 24 NSDictionary *pushInfo = [launchOptions objectForKey:@"UIApplicationLaunchOptionsRemoteNotificationKey"]; 25 26 //獲取推送詳情 27 NSString *pushString = [NSString stringWithFormat:@"%@",[pushInfo objectForKey:@"aps"]]; 28 UIAlertView *alert=[[UIAlertView alloc] initWithTitle:@"finish Loaunch" message:pushString delegate:nil cancelButtonTitle:@"cancel" otherButtonTitles:nil, nil nil]; 29 [alert show]; 30 } 31 } 32 33 return YES; 34 }
2,注冊通知后,獲取device token
1 - (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { 2 NSString *token = [NSString stringWithFormat:@"%@", deviceToken]; 3 NSLog(@"My token is:%@", token); 4 //這里應將device token發送到服務器端 5 } 6 7 - (void)application:(UIApplication *)app didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { 8 NSString *error_str = [NSString stringWithFormat: @"%@", error]; 9 NSLog(@"Failed to get token, error:%@", error_str); 10 }
3,接收推送通知
1 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo 2 { 3 [UIApplication sharedApplication].applicationIconBadgeNumber=0; 4 for (id key in userInfo) { 5 NSLog(@"key: %@, value: %@", key, [userInfo objectForKey:key]); 6 } 7 /* eg. 8 key: aps, value: { 9 alert = "\U8fd9\U662f\U4e00\U6761\U6d4b\U8bd5\U4fe1\U606f"; 10 badge = 1; 11 sound = default; 12 } 13 */ 14 UIAlertView *alert=[[UIAlertView alloc] initWithTitle:@"remote notification" message:userInfo[@"aps"][@"alert"] delegate:nil cancelButtonTitle:@"cancel" otherButtonTitles:nil, nil nil]; 15 [alert show]; 16 }
注意:app 前台運行時,會調用 remote notification;app后台運行時,點擊提醒框,會調用remote notification,點擊app 圖標,不調用remote notification,沒反應;app 沒有運行時,點擊提醒框,finishLaunching 中,launchOptions 傳參,點擊app 圖標,launchOptions 不傳參,不調用remote notification。