轉(zhuǎn)自豐俊文的博客
經(jīng)常有同學(xué)問(wèn)我們,iOS上推送究竟怎么做啊,為什么我的設(shè)備總收不到推送呢,這里跟大家集中討論一下iOS上推送的實(shí)現(xiàn)細(xì)節(jié)。
APNS的推送機(jī)制
與Android上我們自己實(shí)現(xiàn)的推送服務(wù)不一樣,Apple對(duì)設(shè)備的控制非常嚴(yán)格,消息推送的流程必須要經(jīng)過(guò)APNs:
這里 Provider 是指某個(gè)應(yīng)用的Developer,當(dāng)然如果開(kāi)發(fā)者使用AVOS Cloud的服務(wù),把發(fā)送消息的請(qǐng)求委托給我們,那么這里的Provider就是AVOS Cloud的推送服務(wù)程序了。上圖可以分為三步:
第一步:AVOS Cloud推送服務(wù)程序把要發(fā)送的消息、目的設(shè)備的唯一標(biāo)識(shí)打包,發(fā)給APNs。
第二步:APNs在自身的已注冊(cè)Push服務(wù)的應(yīng)用列表中,查找有相應(yīng)標(biāo)識(shí)的設(shè)備,并把消息發(fā)送到設(shè)備。
第三步:iOS系統(tǒng)把發(fā)來(lái)的消息傳遞給相應(yīng)的應(yīng)用程序,并且按照設(shè)定彈出Push通知
為了實(shí)現(xiàn)消息推送,有兩點(diǎn)非常重要:
1,App的推送證書(shū)
要能夠完整實(shí)現(xiàn)一條消息推送,需要我們?cè)贏pp ID中打開(kāi)Push Notifications,需要我們準(zhǔn)備好Provisioning Profile和SSL證書(shū),并且一定要注意Development和Distribution環(huán)境是需要分開(kāi)的。最后,把SSL證書(shū)導(dǎo)入到AVOS Cloud平臺(tái),就可以嘗試遠(yuǎn)程消息推送了。具體的操作流程可以參考我們的使用指南:iOS推送證書(shū)設(shè)置指南。
2,設(shè)備標(biāo)識(shí)DeviceToken
知道了誰(shuí)要推送,或者說(shuō)要推送給哪個(gè)App之后,APNs還需要知道推到哪臺(tái)設(shè)備上,這就是設(shè)備標(biāo)識(shí)的作用。獲取設(shè)備標(biāo)識(shí)的流程如下:
第一步:App打開(kāi)推送開(kāi)關(guān),用戶要確認(rèn)TA希望獲得該App的推送消息
第二步:App獲得一個(gè)DeviceToken
第三步:App將DeviceToken保存起來(lái),這里就是通過(guò)[AVInstallation saveInBackground]將DeviceToken保存到AVOS Cloud
第四步:當(dāng)某些特定事件發(fā)生,開(kāi)發(fā)者委托AVOS Cloud來(lái)發(fā)送推送消息,這時(shí)候AVOS Cloud的推送服務(wù)器就會(huì)給APNs發(fā)送一則推送消息,APNs最后消息送到用戶設(shè)備
推送相關(guān)的幾個(gè)概念
消息類(lèi)型
一條消息推送過(guò)來(lái),可以有如下幾種表現(xiàn)形式:
1. 顯示一個(gè)alert或者banner,展現(xiàn)具體內(nèi)容
2. 在應(yīng)用icon上提示一個(gè)新到消息數(shù)
3. 播放一段聲音
開(kāi)發(fā)者可以在每次推送的時(shí)候設(shè)置,在推送達(dá)到用戶設(shè)備時(shí)開(kāi)發(fā)者也可以選擇不同的提示方式。
本地消息通知
iOS上有兩種消息通知,一種是本地消息(Local Notification),一種是遠(yuǎn)程消息(Push Notification,也叫Remote Notification),設(shè)計(jì)這兩種通知的目的都是為了提醒用戶,現(xiàn)在有些什么新鮮的事情發(fā)生了,吸引用戶重新打開(kāi)應(yīng)用。
本地消息什么時(shí)候有用呢?譬如你正在做一個(gè)To-do的工具類(lèi)應(yīng)用,對(duì)于用戶加入的每一個(gè)事項(xiàng),都會(huì)有一個(gè)完成的時(shí)間點(diǎn),用戶可以要求這個(gè)To-do應(yīng)用在事項(xiàng)過(guò)期之前的某一個(gè)時(shí)間點(diǎn)提醒一下TA。為了達(dá)到這一目的,App就可以調(diào)度一個(gè)本地通知,在時(shí)間點(diǎn)到了之后發(fā)出一個(gè)Alert消息或者其他提示。
我們?cè)谔幚硗扑拖⒌臅r(shí)候,也可以綜合運(yùn)用這兩種方式。
代碼里面如何實(shí)現(xiàn)推送
首先,我們要獲取DeviceToken。
App需要每次啟動(dòng)的時(shí)候都去注冊(cè)遠(yuǎn)程通知——通過(guò)調(diào)用UIApplication的registerForRemoteNotificationTypes:方法,傳遞給它你希望支持的消息類(lèi)型參數(shù)即可,例如:
- - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- {
- // do some initiale working
- ...
-
- [application registerForRemoteNotificationTypes:UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound];
- return YES;
- }
如果注冊(cè)成功,APNs會(huì)返回給你設(shè)備的token,iOS系統(tǒng)會(huì)把它傳遞給app delegate代理——application:didRegisterForRemoteNotificationsWithDeviceToken:方法,你應(yīng)該在這個(gè)方法里面把token保存到AVOS Cloud后臺(tái),例如:
- - (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
- NSLog(@"Receive DeviceToken: %@", deviceToken);
- AVInstallation *currentInstallation = [AVInstallation currentInstallation];
- [currentInstallation setDeviceTokenFromData:deviceToken];
- [currentInstallation saveInBackground];
- }
如果注冊(cè)失敗,application:didFailToRegisterForRemoteNotificationsWithError:方法會(huì)被調(diào)用,通過(guò)NSError參數(shù)你可以看到具體的出錯(cuò)信息,例如:
- - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
- NSLog(@"注冊(cè)失敗,無(wú)法獲取設(shè)備ID, 具體錯(cuò)誤: %@", error);
- }
請(qǐng)注意:注冊(cè)流程需要在app每次啟動(dòng)時(shí)調(diào)用,這并不不會(huì)帶來(lái)額外的負(fù)擔(dān),因?yàn)閕OS操作系統(tǒng)在第一次獲得了有效的device token之后,會(huì)本地緩存起來(lái),以后app再調(diào)用registerForRemoteNotificationTypes:的時(shí)候會(huì)立刻返回,并不會(huì)再進(jìn)行網(wǎng)絡(luò)請(qǐng)求。另外,app層面不應(yīng)該對(duì)device token進(jìn)行緩存,因?yàn)閐evice token也有可能變化——如果用戶重裝了操作系統(tǒng),那么APNs再次給出的device token就會(huì)和之前的不一樣,又或者是,用戶restore了原來(lái)的backup到新的設(shè)備上,那么原來(lái)的device token也會(huì)失效。
其次,我們要處理收到消息之后的回調(diào)
我們可以設(shè)想一下消息通知的幾種使用場(chǎng)景:
1,在app沒(méi)有被啟動(dòng)的時(shí)候,接收到了消息通知。這時(shí)候操作系統(tǒng)會(huì)按照默認(rèn)的方式來(lái)展現(xiàn)一個(gè)alert消息,在app icon上標(biāo)記一個(gè)數(shù)字,甚至播放一段聲音。
2,用戶看到消息之后,點(diǎn)擊了一下action按鈕或者點(diǎn)擊了應(yīng)用圖標(biāo)。如果action按鈕被點(diǎn)擊了,系統(tǒng)會(huì)通過(guò)調(diào)用application:didFinishLaunchingWithOptions:這個(gè)代理方法來(lái)啟動(dòng)應(yīng)用,并且會(huì)把notification的payload數(shù)據(jù)傳遞進(jìn)去。如果應(yīng)用圖標(biāo)被點(diǎn)擊了,系統(tǒng)也一樣會(huì)調(diào)用application:didFinishLaunchingWithOptions:這個(gè)代理方法來(lái)啟動(dòng)應(yīng)用,唯一不同的是這時(shí)候啟動(dòng)參數(shù)里面不會(huì)有任何notification的信息。
示例代碼如下:
- - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- {
- // do initializing works
- ...
-
- if (launchOptions) {
- // do something else
- ...
-
- [AVAnalytics trackAppOpenedWithLaunchOptions:launchOptions];
- }
-
- [application registerForRemoteNotificationTypes:UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound];
-
- return YES;
- }
3,如果遠(yuǎn)程消息發(fā)送過(guò)來(lái)的時(shí)候,app正在運(yùn)行,這時(shí)候會(huì)發(fā)生什么呢?
app代理的application:didReceiveRemoteNotification:方法會(huì)被調(diào)用,同時(shí)遠(yuǎn)程消息中的payload數(shù)據(jù)會(huì)作為參數(shù)傳遞進(jìn)去。
示例代碼如下:
- - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
- if (application.applicationState == UIApplicationStateActive) {
- // 轉(zhuǎn)換成一個(gè)本地通知,顯示到通知欄,你也可以直接顯示出一個(gè)alertView,只是那樣稍顯aggressive:)
- UILocalNotification *localNotification = [[UILocalNotification alloc] init];
- localNotification.userInfo = userInfo;
- localNotification.soundName = UILocalNotificationDefaultSoundName;
- localNotification.alertBody = [[userInfo objectForKey:@"aps"] objectForKey:@"alert"];
- localNotification.fireDate = [NSDate date];
- [[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
- } else {
- [AVAnalytics trackAppOpenedWithRemoteNotificationPayload:userInfo];
- }
- }
常見(jiàn)問(wèn)題FAQ
1. 我能推送長(zhǎng)消息嗎?
不能,APNs限制了每個(gè)notification的payload最大長(zhǎng)度是256字節(jié),超長(zhǎng)的消息是不能發(fā)送的。
2. 推送怎么加聲音提醒?
消息推送是可以指定聲音的。譬如你可以對(duì)正面的反饋使用歡快的聲音,對(duì)負(fù)面的反饋使用低沉一點(diǎn)的聲音,都可以達(dá)到別出心裁讓人眼前一亮的目的。
你需要先放一些aiff、wav或者caf音頻文件到app的資源文件中,然后在推送的時(shí)候指定不同的音頻文件名就可以了。
3. 推送的Badge是怎么回事?
推送并不一定會(huì)導(dǎo)致應(yīng)用圖標(biāo)上紅色數(shù)字增加,是否顯示這一數(shù)字,顯示成多少,都取決于開(kāi)發(fā)者自己。
在發(fā)送推送消息的時(shí)候,我們可以選擇是否遞增這一數(shù)字;如果不選擇這一項(xiàng),那么消息推送并不會(huì)導(dǎo)致應(yīng)用圖標(biāo)上紅色數(shù)字的出現(xiàn)。
好,現(xiàn)在問(wèn)題來(lái)了,這個(gè)數(shù)字如果搞出來(lái)了,怎么讓它消失掉呢?
其實(shí)我們只需要在任何時(shí)候設(shè)置 UIApplication.applicationIconBadgeNumber 屬性為0,就可以讓這個(gè)數(shù)字消失掉。
一般我們會(huì)選擇在應(yīng)用啟動(dòng)的時(shí)候(application:didFinishLaunchingWithOptions:方法中),或者干脆一點(diǎn),在應(yīng)用每次被切換到前臺(tái)的時(shí)候(applicationWillEnterForeground:方法中),調(diào)用這一行代碼,即可立刻清除掉Badge數(shù)字了。
4. AVOS Cloud平臺(tái)發(fā)出去的通知格式究竟是什么樣子的
對(duì)于每一條推送消息,都包含一個(gè)payload,通常是組成了一個(gè)JSON的Dictionary,這其中必不可少的是aps屬性,它對(duì)應(yīng)的value也是一個(gè)Dictionary,包含下面一些內(nèi)容:
1)alert消息(文本或Dictionary)
2)應(yīng)用圖標(biāo)上的紅色數(shù)字
3)播放的聲音文件名
在由推送激活的app打開(kāi)事件中,application:didFinishLaunchingWithOptions:的options參數(shù)就是這個(gè)大的Dictionary對(duì)象。
- {
- aps = {
- alert = "hello, everyone";
- badge = 2;
- sound = default;
- };
- }
這里要注意的時(shí)alert部分,它的值可以是一個(gè)String(文本消息),也可以是一個(gè)JSON的Dictionary。當(dāng)它是文本消息的時(shí)候,系統(tǒng)就會(huì)把這些文字顯示到一個(gè)alertview中;如果它也是由一個(gè)JSON Dictionary組成的話,其格式如下:
* body
* action-loc-key
* loc-key
* loc-args
* launch-image
body部分就是alertView中將要展現(xiàn)出來(lái)的文本消息,loc-屬性主要是用來(lái)實(shí)現(xiàn)本地化消息,launch-image只是app主bundle里的一個(gè)圖片文件的名稱,一般來(lái)說(shuō)我們不指定這一屬性。
5. 如何顯示本地化的消息
有兩種辦法可以實(shí)現(xiàn)推送消息的本地化:
1)在推送的payload中使用loc-key和loc-args來(lái)指定進(jìn)行本地化,這樣Provider方只需要按照統(tǒng)一的格式來(lái)發(fā)送即可,消息的解析和組裝則由客戶端來(lái)完成。
2)如果推送的payload里面不包含loc-key/loc-args信息,那么Provider方就需要自己做本地化處理,然后給不同的device發(fā)送不同的消息,為了做到這一點(diǎn),還需要app在上傳device token的時(shí)候也把用戶的語(yǔ)言設(shè)置信息傳回來(lái)。
目前,因?yàn)锳VOS Cloud主要就是瞄準(zhǔn)中國(guó)大陸市場(chǎng)和海外中文用戶,所以我們?cè)谕扑蜕线€不提供多語(yǔ)言支持。
6. 應(yīng)用該怎么響應(yīng)推送消息
上面說(shuō)的處理流程,只能簡(jiǎn)單展示一下遠(yuǎn)程消息,激活用戶讓他們重新回到app中來(lái)。但是有時(shí)候,我們希望帶給用戶更好的使用體驗(yàn),譬如如果我們告訴用戶:張三剛剛評(píng)論了你的照片。這時(shí)候用戶如果點(diǎn)擊action按鈕進(jìn)入app,我們是展示具體的評(píng)論頁(yè)面為好,還是展示通常的啟動(dòng)頁(yè)面然后讓用戶自己去找張三的評(píng)論好?我想負(fù)責(zé)任的開(kāi)發(fā)者都會(huì)選擇前者。
要做到靈活響應(yīng)不同類(lèi)型的通知消息,我們需要在通知的payload中增加更多信息,而不能僅僅只有alert出來(lái)的文字信息。對(duì)于AVOS Cloud消息推送平臺(tái)來(lái)講,就需要開(kāi)發(fā)者使用更高級(jí)功能的JSON格式。譬如我們發(fā)送這樣的json字符串{"action":{"type":4},"alert":"hello, everyone”} 最終在app內(nèi)會(huì)收到這樣的UserInfo Dictionary:
- {
- action = {
- type = 4;
- };
- aps = {
- alert = "hello, everyone";
- badge = 4;
- };
- }
“hello, everyone”會(huì)顯示到alertView中,但是整個(gè)Dictionary會(huì)通過(guò)launchOptions傳遞給application: didFinishLaunchingWithOptions: 方法,這樣我們?cè)诔绦蚶锩婢涂梢詫?duì)不同的消息實(shí)現(xiàn)不同的跳轉(zhuǎn)了。