APNS推送以及app內部訊息處理、本地通知的使用
APNS具體的流程大概就是:app註冊APNS推送功能,app就會通過iOS系統向APNS伺服器要devicetoken,然後將devicetoken傳給app的推送伺服器,推送伺服器帶著P12檔案和device token,以及要推送的訊息,傳送給蘋果伺服器。隨後就是上述步驟啦。需要注意的是:xcode必須配置Profile,才能接受訊息。而推送服務端則必須要,攜帶推送證書的P12檔案,與APNS推送伺服器傳輸訊息。
關於device token,還要多少兩句,在開發和生產環境中的同一個iPhone和app對應的device token是不一樣的,而且它也會改變。如果改變了,我們怎麼辦。這個人說的很好,這裡轉載一下。
正是因為device有可能改變,所以建議在app start時(即在didFinishLaunchingWithOptions 裡)呼叫registerForRemoteNotificationTypes來獲取device token以檢查device token是否改變,如果改變了就應該把新token傳給push provider。(官方描述:An application should register every time it launches and give its provider the current token)device token應該儲存在NSUserDefaults來達到新舊比較的目的
那麼舊device token在push provider對應的record怎麼辦?
方案1:把舊device token send to provider and request delete record
方案2:使用apns feedback service。
方案2可能更好些,因為總是需要使用apns feedback service來處理使用者在device裡刪除app的情況。
呼叫registerForRemoteNotificationTypes方法後,成功註冊後,APNS就會返回一個device
token,然後回撥delegate methoddidRegisterForRemoteNotificationsWithDeviceToken
注意:
* 在第一次呼叫registerForRemoteNotificationTypes方法時沒有聯網,則既不會呼叫didRegisterForRemoteNotificationsWithDeviceToken,也不會呼叫didFailToRegisterForRemoteNotificationsWithError
*在第一次呼叫registerForRemoteNotificationTypes註冊成功後,之後即使沒有聯網,再呼叫registerForRemoteNotificationTypes時都會以最上一次的device token作為引數回撥didRegisterForRemoteNotificationsWithDeviceToken方法。
* (官方描述) If
your application has previously registered, calling registerForRemoteNotificationTypes:
results
in the operating system passing the device token to the delegate immediately without incurring additional overhead.
看完這位說的,那麼我的問題來了:什麼是apns feedback service。然後就看到下面這位說的重要規則。記錄下來。
前段時間,仔細研究了APNS的文件,把一些關鍵的地方記錄了下來,弄懂這些對於理解APNS的規則,至關重要。
1. If APNs attempts to deliver a notification but the device is offline, the notification is stored for a limited period of time, and delivered to the device when it becomes available.
假如使用者手機不線上,可能沒有訊號或者關機吧,APNs會儲存轉發,等使用者線上時再發送
2.Only one recent notification for a particular application is stored. If multiple notifications are sent while the device is offline, each new notification causes the prior notification to be discarded. This behavior of keeping only the newest notification is referred to as coalescing notifications.
如果使用者不線上,通知會合並,只會保留最新的一條。假如你給使用者發了兩條通知,但使用者反饋說,只收到了一條,那麼很可能是使用者當時不線上,APNs的合併策略生效,只保留了最近一條
3.If the device remains offline for a long time, any notifications that were being stored for it are discarded
4.The maximum size allowed for a notification payload is 256 bytes; Apple Push Notification Service refuses any notification that exceeds this limit.
這個很重要,payload,就是最後生成的那段Json,不得超過256位元組。如果超過了,建議去掉一些不需要的引數,把alert,就是提示資訊的字數減少
5.don’t repeatedly open and close connections. APNs treats rapid connection and disconnection as a denial-of-service attack.
6.If you send a notification that is accepted by APNs, nothing is returned.
傳送成功的木有返回,只有傳送失敗的才會返回
7.If you send a notification that is malformed or otherwise unintelligible, APNs returns an error-response packet and closes the connection. Any notifications that you sent after the malformed notification using the same connection are discarded, and must be resent.
這條非常重要,如果有error-response,那麼這條之後的通知都需要重發。有很多開源的庫,在發蘋果通知時都沒有檢測error-response,如果你不小心用了,那麼使用者很可能反饋“怎麼沒有通知啊”
8.The notification identifier in the error response indicates the last notification that was successfully sent(實際情況不是,實際上返回的是出錯的那條通知的ID). Any notifications you sent after it have been discarded and must be resent.When you receive this status code, stop using this connection and open a new connection.
這是對上一條的補充,如果出錯了,需要關閉當前的連線,並且重新連線再發。error-response中返回的通知ID,可以幫助我們找出哪條出錯了,這樣就能知道哪些需要重發了
9.When a push notification cannot be delivered because the intended app does not exist on the device, the feedback service adds that device’s token to its list.
APNS的feedback service會返回那些已經解除安裝的裝置的token--device_token。儲存這些token,下次就不用再給他們發了,可以節省點資源。需要注意的是:feedback的介面讀取一次,APNS就會清空它的列表,下次再讀取時,返回的就是這兩次讀取之間這段時間新產生的device_token。
接下來是另外一位的更新版
蘋果APNs’ device token特性和過期更新
APNs全名是Apple Push Notification Service。用iPhone的應該都習慣了,每次安裝完一個新應用啟動後,幾乎都會彈出個警告框,“XXX應用”想要給您傳送推送通知。這個警告框的許可權申請就是為了APNs推送,使用者授權後,應用提供商就可以通過APNs給使用者推送訊息。
APNs的工作機制簡單來說可以分為兩步,第一步是註冊推送服務從APNs獲取device token來告知應用提供商服務端,第二步是應用提供商服務端通過APNs給裝置推送訊息,device token是作為裝置的唯一標示。
上圖就是device token生成的一個過程。我們以第一次安裝啟動360兒童衛士應用為例,首先應用會彈出個警告框,請求使用者允許傳送推送通知,使用者允許後–>兒童衛士會向系統註冊推送服務,系統接到註冊請求後就會自動連線APNs伺服器請求獲取裝置令牌(即device token)–>APNs伺服器生成包含device id的device token並下發給裝置–>兒童衛士接受到device token,儲存在本地同時傳送給兒童衛士伺服器,到此第一步就完成了。
上圖就是推送訊息的示圖了,裝置通過device token和APNs伺服器保持連線狀態。還以360兒童衛士為例,當孩子到家了,兒童衛士伺服器就需要發到達提醒給家長。這時兒童衛士伺服器就會通過device token作為目的裝置標示來推送加密的到達提醒訊息給APNs,APNs解密後再根據device token推送給指定裝置。這樣,一次推送就完成了。
瞭解了APNs工作機制,很明顯能夠看到device token在其中起了至關重要的串聯指向作用。如果device token錯誤或缺失,推送就無法送達目標裝置了。所以測試也罷開發也好,都很有必要了解一下device token的一些特性:
1.每個device token都是唯一的,只會對應一臺裝置。
2.device token與裝置系統相關(注意不是和裝置繫結的!詳解見後文),同一裝置系統上不同應用獲取的token是同一個。
3.應用解除安裝重新安裝,獲取到的device token不會變化,而且不會再彈出推送許可權申請的彈窗,會自動繼承前一次安裝的設定資訊。這個特性容易引發一些安全問題,使用者解除安裝重新安裝一個應用後,還沒有登入應用,就可能接到上次登入帳號的推送訊息了。我使用iPhone QQ和Skype都碰到過這種情況。客戶端沒有辦法處理這個問題,因為被解除安裝時客戶端是沒法做出反應來通知伺服器的。蘋果有一個feedback的機制可以解決這個問題,蘋果為每個應用程式維護了一個不斷更新的推送失敗的裝置列表。服務端可以去定期檢查並更新推送裝置列表,這樣能解決大部分問題,也能減少不必要的報文開銷。
4.第三點客戶端不能處理,但退出登入通知伺服器就是客戶端的工作了。使用者退出登入客戶端時,客戶端應該告知伺服器,停止對這個裝置繼續推送使用者退出登入帳號的訊息了。這點應該不算device token的特性了,是一個標準處理方法。
相信很多人都有這樣一個疑問,作為一個裝置推送的唯一標示,device token是否會變化或者過期呢?蘋果在這點上有些含糊其辭,只是在官方文件上建議開發者在每次啟動應用時應該都向APNs獲取device token並上傳給伺服器。從這句話來看,device token是會變化的,不然不用每次啟動都去獲取。因為蘋果官方沒有給出明確的device token變化的情況,所以以下列舉的都是一些前人總結的經驗,主要援引了stackoverflow上關於這個問題一個回答,回答者稱是和蘋果的一個工程師交流及自己實驗得出的結果。
1.升級系統device token有可能變化,確認的是升級到iOS5會變化,猜測是升級大的系統版本後device token會變化。
2.抹掉所有內容和設定,reset裝置後,device token會變化。
3.恢復一個非本機的備份後,device token會變化。
4.device token會過期,這個眾說紛紜,有說是半年的,有說一年,有說兩年的,不過會過期應該是確鑿的。
5.備份或者恢復本機的備份,device token不會變化。
所以保險起見,按照蘋果的每次啟動應用時檢查device token併發送到伺服器是比較穩妥的做法。
接下來是一位php coder的一些注意點
注意事項:
1.建議和feedback伺服器建立長連線,連線過於頻繁有可能被當做攻擊(簡簡單單的做一些測試時沒有關係的);
2.獲取的token是在上次你給你的應用發推送失敗時加feedback服務的,裡面會返回失敗的具體時間
3.返回的資料由三部分組成,請看下面的圖
結構中包含三個部分,第一部分是一個上次發推送失敗的時間戳,第二個部分是device_token的長度,第三部分就是失效的device_token
現在開始處理推送的訊息。首先來說一下這些方法:
1.func applicationWillResignActive(application: UIApplication){} 當App既將進入後臺、鎖屏、有電話進來時會觸發此事件
2.func applicationDidEnterBackground(application: UIApplication) {} 當App進入後臺時觸發此事件
3.func applicationWillEnterForeground(application: UIApplication) {} 當App從後臺即將回到前臺時觸發此事件
4.func applicationDidBecomeActive(application: UIApplication) {}當App變成活動狀態時觸發此事件
5.func applicationWillTerminate(application: UIApplication) {} 當App退出時觸發此方法,一般用於儲存某些特定的資料
- (void)application:(UIApplication *)applicationdidRegisterForRemoteNotificationsWithDeviceToken:(NSData *)pToken
- (void)application:(UIApplication *)applicationdidFailToRegisterForRemoteNotificationsWithError:(NSError *)error
為了讓device端可以接收到推送訊息,需要將裝置的token傳送到蘋果的伺服器,這個token就相當於裝置的識別碼,每一臺蘋果裝置都有唯一的token,蘋果的伺服器就是通過這個token找到對應的裝置,並傳送相應地訊息。這兩個函式就是在傳送token成功或者失敗後呼叫的,使用者在對應的函式裡面做一些相應地處理。 一般都是在成功接受到token的地方處理,將token傳給自己應用的推送伺服器。如果app處於前臺或者後天的時候,收到推送的時候,進入方法:
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
都是程式在執行過程中(無論當前程式處於前臺還是後臺)接收到推送訊息的處理函式。根據蘋果的官方文件,建議大家使用
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
因為前者在程式處於後臺的時候是無法接收到推送資訊的(經實測-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo其實可以接收到,不知道是怎麼回事,希望大蝦解疑)。另外就是-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler 還有一個作用。根據蘋果給出的文件,系統給出30s的時間對推送的訊息進行處理,此後就會執行CompletionHandler程式塊。
在處理這類推送訊息(即程式被啟動後接收到推送訊息)的時候,通常會遇到這樣的問題,就是當前的推送訊息是當前程式正在前臺執行時接收到的還是說是程式在後臺執行,使用者點選系統訊息通知欄對應項進入程式時而接收到的?這個其實很簡單,用下面的程式碼就可以解決:
?1 2 3 4 5 6 7 8 9 10 11 12 |
void application:(UIApplication*)application
didReceiveRemoteNotification:NSDictionary)userInfo fetchCompletionHandler:((^)UIBackgroundFetchResult)completionHandler{
if (application.applicationState
== UIApplicationStateActive) {
NSLog(@ "active" );
//程式當前正處於前臺
}
else if (application.applicationState
== UIApplicationStateInactive)
{
NSLog(@ "inactive" );
//程式處於後臺
}
}
|
?
|