理解 iOS 本地通知和遠程通知
本文主要講述了iOS的本地和遠程通知的基本使用,以及某些不易注意的問題。
Note:文章有不少身旁同學提供了幫助,大量引用或轉載本文請聲明原文地址,多謝。
一:用戶通知簡介
用戶通知是什么
iOS中存在三種常見的事件通知方式:NSNofiticationCenter、KVO Notification 和 User Notifications,其中 User Notifications,就是本文將要探討的用戶通知。
我們都知道 iOS 系統經常的有一些與 App 相關的通知欄消息,這些消息往往伴隨著提示音以及 App 的桌面圖標右上角的未讀消息提示,這些通知就是 iOS 的用戶通知。
用戶通知的分類
用戶通知分為兩類:本地通知和遠程通知,其中遠程通知又稱為推送通知。
兩者最主要的區別是:本地通知是由 App 發送到當前設備上,不需要網絡支持;而遠程通知是由 App 的服務器發送到蘋果的 APNs 服務器,并由 APNs 服務器轉發到相應設備(由 App 服務器指定接收通知的設備)。
兩者最主要的共同點是:本地通知和遠程通知對用戶的表現形式是相同的,兩者均可以采用通知欄消息、App 桌面圖標右上角角標和提示音的方式通知用戶。
用戶通知有什么用處
及時有效的(無論是在前臺還是后臺)向用戶發送消息(聊天信息、新聞、待辦事項、天氣變化等)是用戶通知最大的優勢。
此外,有效合理的使用用戶通知,可以讓我們的 App 有更好的體驗,如:
- 當待辦事項將要過期時可以及時提醒用戶;
- 當用戶執行下載大文件任務時進入后臺,當下載完成后可以通知用戶;
- 當用戶環球旅行時,可以根據用戶的地理位置推送天氣變化等信息;
- 當用戶訂閱的某雜志或新聞主題有更新時,通知用戶;
- ……
本文后續內容將以應用開發者的角度對用戶通知進行深入的探討,本文討論內容針對iOS7/8/9,有關 iOS10 系統的用戶通知會另做講解。
本文中的遠程通知使用了 Simplepush.php ,內部代碼很簡單,可使用該腳本自定義遠程通知的內容,
本文主要參考了蘋果官方的 Local and Remote Notification Programming Guide 以及本文用到的接口的官方文檔。
二:本地通知的使用
開啟本地通知功能
-
對于 iOS7,如果用戶沒有在系統設置里關閉該 App 的通知功能,那么開發者無需做任何操作即可使用本地通知功能。
-
對于 iOS8 及以后的系統,若需要使用本地通知功能,則需要注冊通知類型。
通知類型有四種:角標(UIUserNotificationTypeBadge)、提示音(UIUserNotificationTypeSound)、提示信息(UIUserNotificationTypeAlert)和無任何通知(UIUserNotificationTypeNone)。
你可以注冊上訴四種通知類型的任意組合,但最終可用的通知形式需要根據用戶對此 App 通知的設置確定。比如:App 內部注冊了角標、提示音和提示信息,但是用戶關閉了聲音通知,那么收到本地通知時是不會有提示音的。
對于 iOS8 及以后的系統,注冊本地通知的代碼示例如下:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // 只有 iOS8 and later 才需要 if ([[UIApplication sharedApplication] respondsToSelector:@selector(registerForRemoteNotifications)]) { // 這里 types 可以自定義,如果 types 為 0,那么所有的用戶通知均會靜默的接收,系統不會給用戶任何提示(當然,App 可以自己處理并給出提示) UIUserNotificationType types = (UIUserNotificationType) (UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert); // 這里 categories 可暫不深入,本文后面會詳細講解。 UIUserNotificationSettings *mySettings = [UIUserNotificationSettings settingsForTypes:types categories:nil]; // 當應用安裝后第一次調用該方法時,系統會彈窗提示用戶是否允許接收通知 [[UIApplication sharedApplication] registerUserNotificationSettings:mySettings]; } // Your own other codes. return YES; }
當系統彈窗提示用戶是否允許接收通知后,用戶可能會拒絕;我們可以在 AppDelegate 的 application:didRegisterUserNotificationSettings:
方法中用來查看注冊成功的通知類型,我們可以在拿到注冊結果后做自定義操作(比如失敗時彈個窗提示用戶當前無法使用用戶通知)。
蘋果推薦在之后發送的本地通知時,要避免使用沒有注冊成功的通知類型(并不是強制要求)。
- (void)application: (UIApplication*)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
if (notificationSettings.types & UIUserNotificationTypeBadge) {
NSLog(@"Badge Nofitication type is allowed");
}
if (notificationSettings.types & UIUserNotificationTypeAlert) {
NSLog(@"Alert Notfication type is allowed");
}
if (notificationSettings.types & UIUserNotificationTypeSound) {
NSLog(@"Sound Notfication type is allowed");
}
}
發送本地通知
發送一個本地通知主要有如下步驟:
- 首先要按照上述 "開啟本地通知功能" 步驟注冊通知類型;
- 創建一個 UILocalNotification 對象;
- 設置 UILocalNotification 對象的 fireDate 屬性,該屬性表示什么時間點發送這條本地通知;
同時可以設置 timeZone 屬性表示時區,設置 timeZone 后,當用戶跨越時區時,fireDate 會按照時區被調整(類似于鐘表調整);
此外,可以使用 repeatInterval 和 repeatCalendar 來設置周期性的通知。 - 設置通知的提示信息:
- 設置 alertTitle 作為通知的概要,設置 alertBody 作為通知的具體信息;注意這里強烈建議使用本地化的字符串,即 NSLocalizedString(@"This is alert body", nil); 。
注意 alertTitle 屬性只適用于 iOS8.2 及以后的系統 - 設置 applicationIconBadgeNumber 用于展示 App 桌面圖標的右上角角標;
- 設置 soundName, 我們一般設置為 UILocalNotificationDefaultSoundName;使用自定義 sound 在后面會進一步講解;
- 在設置提醒方式的值時,對于 iOS8 及以后的系統,可以檢查下當前提醒方式是否已經注冊成功(可以用 [[UIApplication sharedApplication] currentUserNotificationSettings] 獲取注冊成功的通知類型)。
- 設置 alertTitle 作為通知的概要,設置 alertBody 作為通知的具體信息;注意這里強烈建議使用本地化的字符串,即 NSLocalizedString(@"This is alert body", nil); 。
- 可以選擇設置 userInfo 屬性,該屬性一般可以存放業務有關的信息(如 ID 等),這樣收到通知后可以方便處理業務相關邏輯;
- 將上面創建的 UILocalnotification 放入通知隊列中:使用方法 scheduleLocalNotification: 會按照 UILocalnotification 中的 fireDate 進行通知的發送,
而使用 presentLocalNotificationNow: 會立即發送該本地通知。
下面給出一段示例代碼:
- (void)scheduleLocalNotification {
NSDate *itemDate = [NSDate date];
UILocalNotification *localNotif = [[UILocalNotification alloc] init];
if (localNotif == nil)
return;
localNotif.fireDate = [itemDate dateByAddingTimeInterval:10];
localNotif.timeZone = [NSTimeZone defaultTimeZone];
localNotif.alertBody = [NSString stringWithFormat:NSLocalizedString(@"%@ after %i seconds scheduled.", nil), @"本地通知", 10];
localNotif.alertTitle = NSLocalizedString(@"Local Notification Title", nil);
localNotif.soundName = UILocalNotificationDefaultSoundName;
localNotif.applicationIconBadgeNumber = 1;
NSDictionary *infoDict = [NSDictionary dictionaryWithObject:@"ID:10" forKey:@"LocalNotification"];
localNotif.userInfo = infoDict;
[[UIApplication sharedApplication] scheduleLocalNotification:localNotif];
}
處理收到的本地通知
這里分三種情況討論如何處理本地通知:
應用處于前臺
應用處于前臺時,本地通知到達時,不會有提示音、通知欄橫幅提示,但是 App 桌面圖標的右上角角標是有數值顯示的,所以即使在前臺,我們也應該對角標數量做處理
此時,我們可以在 application:didReceiveLocalNotification: 方法中獲取到本地通知,示例代碼如下:
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
NSString *itemName = [notification.userInfo objectForKey:@"LocalNotification"];
[self.windowRootController displayNotification:[NSString stringWithFormat:@"%@ receive from didReceiveLocalNotificaition", itemName]];
// 這里將角標數量減一,注意系統不會幫助我們處理角標數量
application.applicationIconBadgeNumber -= 1;
}
應用處于后臺
當應用處于后臺時,本地通知到達時,會根據本地通知設置的通知類型以及用戶設置的通知類型進行提示,例如鎖屏界面通知、通知欄通知、聲音、角標。
此時如果滑動鎖屏界面通知或點擊通知欄通知,則會切換應用到前臺,我們可以使用與應用處于前臺時相同的獲取通知的方式。
但是如果我們點擊 App 桌面圖標,則無法獲取到用戶通知,此時通知欄消息仍然會存在。此外,角標也不會變化,如果希望修改角標,則需要 App 進入前臺后將其修改。
應用沒有運行
如果應用沒有運行,當本地通知到達時,會根據本地通知設置的通知類型以及用戶設置的通知類型進行提示,例如鎖屏界面通知、通知欄通知、聲音、角標。
此時如果滑動鎖屏界面通知或點擊通知欄通知,則會打開應用,但這時我們獲取通知的方式與前面有所不同,通過 application:didReceiveLocalNotification: 是無法獲取通知的。
這種情況我們需要通過 application:didFinishLaunchingWithOptions: 中的 LaunchOptions 獲取通知,示例代碼如下:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UILocalNotification *localNotif = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if (localNotif) {
NSString *itemName = [localNotif.userInfo objectForKey:@"LocalNotification"];
[self.windowRootController displayNotification:[NSString stringWithFormat:@"%@ receive from didFinishLaunch", itemName]]; // custom method
[UIApplication sharedApplication].applicationIconBadgeNumber = localNotif.applicationIconBadgeNumber-1;
}
// Your own other codes.
return YES;
}
同樣的,但是如果我們點擊 App 桌面圖標,則無法獲取到用戶通知,此時通知欄消息仍然會存在。此外,角標也不會變化,如果希望修改角標,則需要 App 進入前臺后將其修改。
地理位置相關的本地通知
在 iOS8 及以后系統中,我們可以定義一個與地理位置有關的本地通知,這樣當我們跨過設定的地理區域時,系統會發送本地通知。
注冊位置相關的本地通知
- 需要創建一個 CLLocationManager 對象,并為其設置一個 delegate;
-
請求用戶允許使用定位服務:調用 CLLocationManager 的 =requestWhenInUseAuthorization=,
注意工程的 plist 中需要配置 NSLocationWhenInUseUsageDescription 選項,否則定位服務無法正常啟用;示例代碼如下:
- (void)registerLocationBasedNotification { CLLocationManager *locationManager = [[CLLocationManager alloc] init]; locationManager.delegate = self; // 申請定位權限 [locationManager requestWhenInUseAuthorization]; }
-
通過 CLLocationManagerDelegate 回調檢查用戶是否允許使用定位服務,如果允許了服務,那么可以發送一個位置相關的本地通知。
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status { // 因為上面我們是使用了 requestWhenInUseAuthorization,所以這里檢查的是 kCLAuthorizationStatusAuthorizedWhenInUse if (status == kCLAuthorizationStatusAuthorizedWhenInUse) { [self scheduleLocalBasedNotification]; } }
-
創建一個位置相關的本地通知,并將其交由系統處理。
- (void)scheduleLocalBasedNotification { UILocalNotification *locationNotification = [[UILocalNotification alloc] init]; locationNotification.alertBody = @"到達xxx"; locationNotification.regionTriggersOnce = NO; // 表示每次跨越指定區域就會發送通知 locationNotification.region = [[CLCircularRegion alloc] initWithCenter:LOC_COORDINATE radius:LOC_RADIUS identifier:LOC_IDENTIFIER]; [[UIApplication sharedApplication] scheduleLocalNotification:locNotification]; }
處理位置相關的本地通知
與上面講過的 “處理收到的本地通知” 比較,這里可以在通知里獲取到 region,然后可以做自定義操作,其余所有操作均與 “處理收到的本地通知” 一致。
注意如果用戶沒有允許使用定位權限,則無法收到位置相關的本地通知。
三:遠程通知的使用
APNs 簡介
APNs 是蘋果提供的遠程通知的服務器,當 App 處于后臺或者沒有運行時,如果 App 的服務器(之后我們稱為 Provider)需要發送通知信息給客戶端,則需要借助于 APNs 服務器。
使用 APNs 服務時,遠程通知的路由路徑為: Provider –> 蘋果的 APNs 服務器 –> 手機設備 –> App。
在這個路徑中,Provider 與 APNs 服務器之間有一個 TLS 連接,Provider 通過這個連接將遠程通知推送到蘋果的 APNs 服務器;
手機設備與 APNs 服務器之間也會有一個 TLS 連接,所有發往手機設備的 APNs 遠程通知都是使用這一個 TLS連接,然后由設備區分遠程通知所屬的 App,進而通知給用戶某應用有遠程通知。
下面簡單介紹下這個流程:
設備 與 APNs
設備與 APNs 建立連接的過程如圖:
需要明確的要點:
- 此連接由系統建立并維持,無需開發人員管理;
- 上圖中的證書是蘋果設備本身的證書,與開發者賬號中申請的證書無關;
- 每個設備與 APNs 服務器只需維持一條連接。
Provider 與 APNs
Provider 與 APNs 建立連接的過程如圖:
需要明確的要點:
- 此連接由 App 的 bundle ID 唯一確定;
- 上圖中 Provider certificate 需要通過開發者賬號申請生成,其中包含 App 的 bundle ID。
APNs 工作的流程
- 首先客戶端需要向 APNs 服務器注冊當前 App,APNs 會返回一個 Token(注意這個過程要求 App 有合法的證書,有關證書這里不做詳細描述);注意不同應用在同一設備上獲取的 Token 不同,同一應用在不同設備上獲取的 Token也不同,所以 Token 是跟設備與 App 唯一綁定的;
- App 拿到 Token 后需要將其發送給 Provider;
- Provider 發送推送通知時,指定 Token 和通知內容,并發送給 APNs 服務器;
- APNs 服務器會將通知發送給 Token 對應的設備上;
- 設備收到通知后,根據 APNs 發過來的通知中帶有的 bundleID 信息區分是哪個App的遠程通知(這里應該是根據 Token 來獲取 bundleID)。
Feedback 機制
Feedback 是 APNs 服務器提供的用于減少服務器壓力以及優化網絡的服務,基本的工作流程如下圖:
- Provider 發送一個遠程通知給 APNs 服務器,APNs 服務器會檢測目的設備是否在線,如果不在線,那么 APNs 服務器會暫存該消息;
- 當目的設備上線后,APNs 會發送暫存的消息給目的設備(按照蘋果官方說法暫存消息只會暫存最后一條消息,之前的消息會被丟棄);
- 如果目的設備很久都沒有上線,那么 APNs 消息會把該設備加入 feedback 名單。Provider 可以定期去 APNs 拉新 feedback 名單;
- 當 Provider 再次給之前的設備發送遠程通知時,需要檢查一下 feedback 名單,如果設備在這個名單,則不再發送給 APNs 了;
- 當設備重新上線后,Provider 可以再將此設備移除 feedback 名單,當 Provider 更新 feedback list 后,就可以重新給該設備發送遠程通知了。當然,feedback list 的更新可能會有周期,如果需要及時有效的更新 feedback list,那么需要 App 打開后,及時通知 Provider;
- 這種機制的好處就是防止發送多余無用的遠程通知消息,一方面可以減緩 APNs 服務器的壓力,另一方面也可以減少網絡流量;
開啟遠程通知功能
注冊通知類型
- 對于 iOS7,無需此步驟;
- 對于 iOS8 及以后的系統,若需要使用遠程通知功能,則需要注冊通知類型。步驟與 "本地通知的使用" 中 "開啟本地通知功能" 是完全相同的,此處不再重復。
注冊遠程通知
基本流程為:
- 注冊通知類型,上一小節已經做了介紹;
- 使用 registerForRemoteNotifications 注冊遠程通知(對于 iOS7 使用 registerForRemoteNotificationTypes: );
- 使用 application:didRegisterForRemoteNotificationsWithDeviceToken: 接收 APNs 返回的 Token,
使用 application:didFailToRegisterForRemoteNotificationsWithError: 處理注冊錯誤; - 如果上一步驟中注冊成功了,那么將得到的 Token 發送給 Provider。
注意:
- 目前看來,對于 iOS9,每次重新安裝應用后得到的 Token 是不一樣的,而且每次重裝系統也會改變,所以 每次應用啟動時都需要按上面的步驟注冊一次 ;
- 不要將之前的 Token 緩存,當需要將 Token 傳送到 Provider 時,一定要使用 registerForRemoteNotifications 獲取,并使用回調處理注冊結果;
當應用注冊過通知,而且 Token 沒有改變時,系統會立即返回結果,不會去 APNs 請求。
這里猜測系統幫助將 Token 緩存下來,且與應用的狀態進行了關聯,如果應用當前狀態沒有改變,那么會立即將系統存下的 Token 返回。
為了證明這點,可以將網絡關閉進行測試,如果 App 沒有卸載,也是可以獲取到 Token 的; - 一定要有開啟了 Push 功能的證書,才能正常使用遠程推送。
注冊遠程通知的示例代碼如下:
- (void)registerRemoteNotifications {
// 區分是否是 iOS8 or later
if ([[UIApplication sharedApplication] respondsToSelector:@selector(registerForRemoteNotifications)]) {
// 這里 types 可以自定義,如果 types 為 0,那么所有的用戶通知均會靜默的接收,系統不會給用戶任何提示(當然,App 可以自己處理并給出提示)
UIUserNotificationType types = (UIUserNotificationType) (UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert);
// 這里 categories 可暫不深入,本文后面會詳細講解。
UIUserNotificationSettings *mySettings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
// 當應用安裝后第一次調用該方法時,系統會彈窗提示用戶是否允許接收通知
[[UIApplication sharedApplication] registerUserNotificationSettings:mySettings];
} else {
UIRemoteNotificationType types = UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound;
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:types];
}
}
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(nonnull UIUserNotificationSettings *)notificationSettings {
// Register for remote notifications.
[[UIApplication sharedApplication] registerForRemoteNotifications];
}
// Handle register result.
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
//獲得 device token,這一步處理為字符串的操作很重要
NSString *token = [[[deviceToken description]
stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]]
stringByReplacingOccurrencesOfString:@" "
withString:@""];
NSLog(@"DeviceToken string, %@", token);
[UIApplication sharedApplication].applicationIconBadgeNumber = 0;
// 將 token 發送給 Provider
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
NSLog(@"Error in registration for apns service. Error: %@", error);
}
發送遠程通知
遠程通知的內容
Provider 發送給 APNs 服務器的內容格式如下:
{
// aps key 是必須要有的
"aps" : {
"alert" : {
"title" : "通知的概要,對 8.2 以前的系統本選項無效"
"body" : "通知的具體內容",
"loc-key" : "GAME_PLAY_REQUEST_FORMAT",
"loc-args" : [ "Jenna", "Frank"]
},
"badge" : 3, // 角標數值
"sound" : “chime.aiff" // 可以自定義提示音
},
"userName" : "username", // aps key 之外可以有自定義內容,需要符合 json 格式
"message" : ["hello", "world", "programmer"]
}
遠程通知的本地化處理
有兩種方式:
-
在 Provider 端進行本地化
App 可以將當前使用的語言發送給 Provider,Provider 在發送遠程通知前,檢查當前設備使用的語言,并做好本地化后發送給 APNs 服務器。App 發送當前使用的語言給 Provider 的示例代碼:
NSString *preferredLang = [[NSLocale preferredLanguages] objectAtIndex:0]; const char *langStr = [preferredLang UTF8String]; [self sendProviderCurrentLanguage:langStr]; // custom method
一般來說,將當前系統語言信息發送給 Provider 時,也會將 Token 一起發送,這樣 Provider 才能夠在發送遠程通知時根據不同目的設備進行本地化處理。
此外,當應用啟動后,用戶可能會修改系統語言,這時,App 需要監聽 NSCurrentLocaleDidChangeNotification 通知,并在處理通知的方法中重新向 Provider 發送當前使用的語言。
-
在客戶端本地化
這種模式下,Provider 在發送遠程通知時,需要設置 Payload -> alert 中的本地化相關屬性,如下:
{ // aps key 是必須要有的 "aps" : { "alert" : { "title" : "通知的概要,對 8.2 以前的系統本選項無效", "loc-key" : "Remote Notification", "loc-args" : [ "hello", "world"] }, "badge" : 3, // 角標數值 "sound" : “chime.aiff" // 可以自定義提示音 } }
上面 loc-key 以及 loc-args 就是本地化相關的屬性,用于本地化 alert 中的 body。
當 App 收到此消息時,會根據系統當前的語言設置去相應的本地化文件中查找與 loc-key 對應的 value,如果 loc-key 對應的 value 是一個格式化的字符串,那么可以用 loc-args 傳遞參數。
假設本地化文件中: "Remote Notification" = "我們程序員通常鐘愛:%@ %@" ,那么提示信息就是: "我們程序員鐘愛:hello world";
此外在本地化文件中我們也可以用 %n$@ 代替 %@ 用來表示使用 loc-args 的第幾個參數。
例如:"Remote Notification" = "我們程序員通常鐘愛:%2$@ %1$@",那么提示信息是:"我們程序員鐘愛:world hello"。
同樣的,title-loc-key 和 title-loc-args 是對 alert 中的 title 做本地化的。
處理收到的遠程通知
這里分三種情況討論如何處理遠程通知:
應用處于前臺
應用處于前臺時,本地通知到達時,不會有提示音、通知欄橫幅提示。但是 App 桌面圖標的右上角角標是有數值顯示的,所以即使在前臺,我們也應該對角標數量做處理;
此時,我們可以在 application:didReceiveRemoteNotification:fetchCompletionHandler: 方法中獲取到遠程通知,示例代碼如下:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo fetchCompletionHandler:(nonnull void (^)(UIBackgroundFetchResult))completionHandler {
NSData *infoData = [NSJSONSerialization dataWithJSONObject:userInfo options:0 error:nil];
NSString *info = [[NSString alloc] initWithData:infoData encoding:NSUTF8StringEncoding];
[self.windowRootController displayNotification:[NSString stringWithFormat:@"From didReceiveRemoteNotification: %@", info]];
// 這里將角標數量減一,注意系統不會幫助我們處理角標數量
application.applicationIconBadgeNumber = notification.applicationIconBadgeNumber - 1;
}
應用處于后臺
當應用處于后臺時,遠程通知到達時,會根據注冊通知是設置的通知類型以及用戶設置的通知類型進行提示,例如鎖屏界面通知、通知欄通知、聲音、角標。
此時如果滑動鎖屏界面通知或點擊通知欄通知,則會切換應用到前臺,我們可以使用與應用處于前臺時相同的獲取通知的方式。
但是如果我們點擊 App 桌面圖標,則無法獲取到用戶通知,此時通知欄消息仍然會存在。此外,角標也不會變化,如果希望修改角標,則需要 App 進入前臺后將其修改。
應用沒有運行
這里有兩種處理方式:
-
與本地通知的處理方式相同,在 application:didFinishLaunchingWithOptions: 的 LaunchOptions 中獲取通知,不過內部代碼會略有不同,示例如下:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSDictionary *remoteNotif = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey]; if (remoteNotif) { NSData *infoData = [NSJSONSerialization dataWithJSONObject:remoteNotif options:0 error:nil]; NSString *info = [[NSString alloc] initWithData:infoData encoding:NSUTF8StringEncoding]; [self.windowRootController displayNotification:[NSString stringWithFormat:@"From didFinishLaunch: %@", info]]; [UIApplication sharedApplication].applicationIconBadgeNumber -= 1; } // Your own other codes. return YES; }
-
與應用處于前后臺時處理方式相同,使用 application:didReceiveRemoteNotification:fetchCompletionHandler: 方法,示例代碼見 "應用處于前臺" 時的處理。
對于遠程通知,推薦使用此種方式處理。
此外,對于遠程通知,如果我們點擊 App 桌面圖標,則無法獲取到用戶通知,此時通知欄消息仍然會存在。此外,角標也不會變化,如果希望修改角標,則需要 App 進入前臺后將其修改。
遠程通知-靜默推送
靜默推送是指應用在前臺或后臺狀態下,收到遠程通知時,沒有彈窗或橫幅提示,即使處于后臺也可以處理遠程通知。具體使用流程如下:
- 打開應用工程 Target 的 Capacities,將 Background Modes 選項打開,并且勾選 Remote Notifications;
-
在 Provider 發送遠程通知時,需要將遠程通知 Payload 中的 aps 內的 content-available 設置為 1,如下:
aps { content-available: 1 alert: {...} }
-
應用需要實現 application:didReceiveRemoteNotification:fetchCompletionHandler: 方法接收靜默推送。
有幾點需要注意:
- 使用靜默推送時,alert 字段不應有任何信息,但可以設置 aps 內的自定義字段;
- sound 和 badge 字段可以設置,但最好不設置,否則會有提示音;
- 靜默推送只有當應用處于前臺或后臺時才能處理,當應用沒有啟動時是收不到靜默推送的;
- 處理靜默推送時,不能做耗時操作,因為系統只為這種處理行為分配少量時間,如下載文件之類的操作請使用后臺下載服務。
可操作通知
首先需要注意的是,可操作通知只適用于 iOS8 及以后的系統。
可操作通知其實并不是一種新的通知形式,它只是在這本地通知和遠程通知的基礎上加了一些可操作的行為而已。為了直觀說明什么是可操作通知,可以參考下圖:
可操作通知為用戶提供了在通知提示中方便執行操作的方式,在使用橫幅提示通知消息時,最多可以有兩個操作,在使用彈窗提示通知消息是,最多可以有四個操作。下面講解如何使用:
四:定義可操作通知的行為
基本使用方法:
-
創建一個 UIMutableUserNotificationAction 對象,并按需求配置該對象的屬性,示例代碼:
UIMutableUserNotificationAction *acceptAction = [[UIMutableUserNotificationAction alloc] init]; // 為該操作設置一個 id acceptAction.identifier = @"accept"; // 設置該操作對應的 button 顯示的字符串 acceptAction.title = @"Accept"; // 指定是否需要應用處于運行狀態 acceptAction.activationMode = UIUserNotificationActivationModeBackground; // 表示該操作是否有害,若設置為 YES,則對應的button會有高亮 acceptAction.destructive = NO; // 當鎖屏時收到可操作通知,該屬性表示是否必須解鎖才能執行該操作 acceptAction.authenticationRequired = YES;
-
創建一個 UIMutableUserNotificationCategory 對象,并將自定義的操作通過 setActions: 的方式設置給 category 對象。代碼如下:
// 這里為了測試,又新建了兩個 action,declineAction 和 maybeAction ,代碼可見 demo UIMutableUserNotificationCategory *inviteCategory = [[UIMutableUserNotificationCategory alloc] init]; // 設置一個 ID,用于本地通知或遠程通知時指定該通知可執行的操作group inviteCategory.identifier = @"Action"; // 為彈窗模式設置 actions [inviteCategory setActions:@[acceptAction, maybeAction, declineAction] forContext:UIUserNotificationActionContextDefault]; // 為橫幅模式設置 actions [inviteCategory setActions:@[acceptAction, declineAction] forContext:UIUserNotificationActionContextMinimal];
-
注冊通知類型以及可操作的actions
類似于本地通知和遠程通知,調用 registerUserNotificationSettings: 注冊通知,只是這里的 setting 加入了我們上面定義的 category。
NSSet *categories = [NSSet setWithObjects:inviteCategory, nil]; // 這里 types 可以自定義,如果 types 為 0,那么所有的用戶通知均會靜默的接收,系統不會給用戶任何提示(當然,App 可以自己處理并給出提示) UIUserNotificationType types = (UIUserNotificationType) (UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert); // 這里 categories 可暫不深入,本文后面會詳細講解。 UIUserNotificationSettings *mySettings = [UIUserNotificationSettings settingsForTypes:types categories:categories]; // 當應用安裝后第一次調用該方法時,系統會彈窗提示用戶是否允許接收通知 [[UIApplication sharedApplication] registerUserNotificationSettings:mySettings];
如果將可執行通知用于遠程通知,那么需要按照遠程通知的注冊方式獲取 token,可參考遠程通知的注冊。
發送可操作通知
之前說過,可操作通知只是在本地通知和遠程通知的基礎上加了自定義的操作,所以發送可操作通知就是發送本地通知或遠程通知。
不過,如果希望我們自定義的 action 有效,在發送本地通知或遠程通知時需要進行一些改變:
-
本地通知的可操作通知
為 UILocalNotification 對象設置我們自定義的 category。如下:
UILocalNotification *notification = [[UILocalNotification alloc] init];
// Other configurations
notification.category = @"Action";
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
-
遠程通知的可操作通知
在遠程通知的 Payload 中設置我們自定義的 category,如下:
{
"aps" : { "alert" : "You’re invited!", "category" : "Action" }
}
處理可操作通知
處理可操作通知與處理本地通知和遠程通知相同,唯一的不同點就是當用戶執行了某個操作后,
應用可以在后臺運行 application:handleActionWithIdentifier:forRemoteNotification:completionHandler: 處理通知(例如后臺更新數據等操作),我們可以在這個回調里快速的執行操作:
- (void)application:(UIApplication *) application
handleActionWithIdentifier: (NSString *) identifier
// either forLocalNotification: (NSDictionary *) notification or
forRemoteNotification: (NSDictionary *) notification
completionHandler: (void (^)()) completionHandler {
if ([identifier isEqualToString: @"accept"]) {
[self handleAcceptActionWithNotification:notification];
}
// 執行自定義代碼完成后必須調用
completionHandler();
}
對于本地通知我們可以使用 application:handleActionWithIdentifier:forLocalNotification:completionHandler: 。
可操作通知到底有什么好處?
這里舉個例子說明:
假如A向B發出了一個出席發布會邀請,并且 App 是以遠程通知的方式接收到該信息,那么當不使用可操作通知的時候,我們需要做的事情主要包括:
- 用戶需要打開應用;
- App 查看遠程通知的內容是一個邀請,那么 App 應該彈窗提示用戶是否接受該邀請;
- 用戶選擇后,App 通過 Http(也可以使用其他通信協議) 將結果返回給服務器;
- 邀請通知處理完畢。
那么,如果我們使用可操作通知,可以很簡單的做到這件事情:
- 用戶選擇接受或拒絕邀請(用戶無需打開 App);
- App 通過可操作通知的回調處理用戶操作結果,將結果發送給服務器;
-
邀請通知處理完畢。
可以看到,不論是從用戶角度還是開發者角度,可操作通知都極大的方便了處理具有可操作動作的這類通知。
五:總結
到這里已經講解完成了用戶通知的內容,文章包含了蘋果給出的用戶通知的基本所有用法.
來自:http://www.jianshu.com/p/ad43bc1a970a