1,背景
最近項(xiàng)目集成客服系統(tǒng)涉及到推送消息, app 進(jìn)行整體消息改版,所有我把項(xiàng)目中的推送相關(guān)的代碼和邏輯整合了一下,總結(jié)了一下 iOS7 - iOS10 系統(tǒng)的推送注冊(cè)和點(diǎn)擊通知進(jìn)入 app 時(shí)走的代理方法。由于在程序被kill時(shí)收到推送時(shí)不能鏈接 Xcode 進(jìn)行代碼的斷點(diǎn)調(diào)試呜呐,所以我使用 http-server ,在涉及到推送代理的每一個(gè)代理方法中,利用 http-server 獲得 app 點(diǎn)擊通知進(jìn)入 app 時(shí)悍募,走的每一個(gè)方法名蘑辑。
2,HTTP-SERVER安裝和使用
npm install http-server -g
使用 npm 安裝 http-server(確保是全局安裝)坠宴,直接在終端執(zhí)行命令 http-server 洋魂,如下圖,找到與自己電腦的ip一致的地址,直接請(qǐng)求那個(gè)地址(GET)副砍,就可以在終端中看見你發(fā)來(lái)的請(qǐng)求了衔肢,當(dāng)然參數(shù)也一起發(fā)過(guò)來(lái)了。我就是用這種方法豁翎,來(lái)定位程序點(diǎn)擊推送時(shí)角骤,走的是哪個(gè)代理方法。(先確認(rèn)你的工程支持http請(qǐng)求心剥,需要暫時(shí)關(guān)閉ATS)
例如我在代理中使用這個(gè)方法來(lái)發(fā)送請(qǐng)求邦尊,把方法名作為參數(shù)傳到終端,請(qǐng)求URL后拼接時(shí)間是防止有緩存优烧。
- (void)uploadMethodName:(NSString *)methodName
{
NSString *path = [NSString stringWithFormat:@"http://10.2.0.243:8080/?methodName=%@&%lf", methodName, [[NSDate date] timeIntervalSince1970]];
[[XHCNetWorking sharedClient] requestWithPath:path params:nil httpMethod:XHCRequestGet callback:^(BOOL rs, NSObject *obj) {
DLog(@"%@", obj);
}];
}
3蝉揍,注冊(cè)推送
#define IOS_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
/** 注冊(cè)用戶通知 */
- (void)registerUserNotification
{
/*
注冊(cè)通知(推送)
申請(qǐng)App需要接受來(lái)自服務(wù)商提供推送消息
*/
// 在iOS10開始,蘋果將通知統(tǒng)一到了UserNotifications這個(gè)類中,首先導(dǎo)入這個(gè)類
// #import <UserNotifications/UserNotifications.h>
// iOS10的注冊(cè)方法
if (IOS_GREATER_THAN_OR_EQUAL_TO(@"10")) {
UNUserNotificationCenter *notificationCenter = [UNUserNotificationCenter currentNotificationCenter];
notificationCenter.delegate = XHCAPPDELEGATE;
[notificationCenter requestAuthorizationWithOptions:UNAuthorizationOptionBadge | UNAuthorizationOptionSound |UNAuthorizationOptionAlert completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (granted) {
DLog(@"request authorization succeeded!");
}
else {
DLog(@"request authorization fail!");
if (error) {
DLog(@"authorization error = %@", error.localizedDescription);
}
}
}];
[[UIApplication sharedApplication] registerForRemoteNotifications];
}
// iOS8以上系統(tǒng)的注冊(cè)方法
else if (IOS_GREATER_THAN_OR_EQUAL_TO(@"8") ||
[UIApplication instancesRespondToSelector:@selector(registerUserNotificationSettings:)]) {
// 定義用戶通知類型(Remote.遠(yuǎn)程 - Badge.標(biāo)記 Alert.提示 Sound.聲音)
UIUserNotificationType types = UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound;
// 定義用戶通知設(shè)置
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
// 注冊(cè)用戶通知 - 根據(jù)用戶通知設(shè)置
[[UIApplication sharedApplication] registerForRemoteNotifications];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
}
else { // iOS8.0 以前遠(yuǎn)程推送設(shè)置方式
// 定義遠(yuǎn)程通知類型(Remote.遠(yuǎn)程 - Badge.標(biāo)記 Alert.提示 Sound.聲音)
UIRemoteNotificationType myTypes = UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound;
// 注冊(cè)遠(yuǎn)程通知 -根據(jù)遠(yuǎn)程通知類型
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:myTypes];
}
}
4,推送的代理方法
4.1匙隔,iOS 7 - iOS 9系統(tǒng):
// iOS7-iOS9點(diǎn)擊消息進(jìn)入app疑苫,走此方法
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler
{
// 監(jiān)聽走的是什么方法
[self uploadMethodName:@"application_didReceiveRemoteNotification_fetchCompletionHandler"];
// 當(dāng)app被壓入后臺(tái)存活時(shí),收到通知就回相應(yīng)這個(gè)方法纷责,需要進(jìn)行一下判斷此時(shí)是什么狀態(tài)捍掺。(iOS7-10,收到消息都會(huì)默認(rèn)走一遍這個(gè)方法)
if (userInfo && application.applicationState == UIApplicationStateActive) {
//這是程序運(yùn)行的時(shí)候再膳,收到通知[這時(shí)推送直接調(diào)此方法]
DLog(@"在前臺(tái)");
}
else if (userInfo && application.applicationState==UIApplicationStateInactive) {
DLog(@"點(diǎn)擊通知進(jìn)入app挺勿,在此處處理跳轉(zhuǎn)邏輯");
[[AppNotificationManager sharedInstance] handleReceiveRemoteNotification:userInfo];
}
else if (userInfo&&application.applicationState==UIApplicationStateBackground) {
DLog(@"在后臺(tái)");
}
// 這個(gè)block必須實(shí)現(xiàn)
completionHandler(UIBackgroundFetchResultNewData);
}
4.2,iOS 10 系統(tǒng):
// iOS10點(diǎn)擊通知進(jìn)入app時(shí)喂柒,調(diào)用的方法不瓶,iOS10在此處處理跳轉(zhuǎn)邏輯
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler {
[self uploadMethodName:@"userNotificationCenter_didReceiveNotificationResponse_withCompletionHandler"];
UNNotification *notification = response.notification;
UNNotificationRequest *request = notification.request; // 收到推送的請(qǐng)求
UNNotificationContent *content = request.content; // 收到推送的消息內(nèi)容
NSDictionary *userInfo = content.userInfo;
NSNumber *badge = content.badge; // 推送消息的角標(biāo)
NSString *body = content.body; // 推送消息體
UNNotificationSound *sound = content.sound; // 推送消息的聲音
NSString *subtitle = content.subtitle; // 推送消息的副標(biāo)題
NSString *title = content.title; // 推送消息的標(biāo)題
if ([request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {// 收到的是遠(yuǎn)程推送
// 處理推送跳轉(zhuǎn)
[[AppNotificationManager sharedInstance] handleReceiveRemoteNotification:userInfo];
}
else {// 收到本地推送
}
completionHandler(); // 系統(tǒng)要求執(zhí)行這個(gè)方法
// 在點(diǎn)擊事件中,如果我們不寫completionHandler()這個(gè)方法灾杰,可能會(huì)報(bào)一下的錯(cuò)誤蚊丐,希望大家注意下~
//Warning: UNUserNotificationCenter delegate received call to -userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler: but the completion handler was never called.
}
4.3,App點(diǎn)擊通知冷啟動(dòng)
就是指當(dāng)app被kill掉時(shí)艳吠,通過(guò)點(diǎn)擊通知啟動(dòng)app時(shí)麦备,在application_didFinishLaunchingWithOptions方法中,推送信息會(huì)包含在options屬性中昭娩。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)options
{
// options 中包含推送的信息
}
4.4凛篙,Tips:
走完這個(gè)函數(shù)之后,系統(tǒng)還是會(huì)走1栏渺,2中aps的代理方法呛梆,要避免對(duì)通知邏輯進(jìn)行重復(fù)處理,我覺得在啟動(dòng)函數(shù)中磕诊,不應(yīng)處理邏輯跳轉(zhuǎn)填物,統(tǒng)一在推送代理中進(jìn)行統(tǒng)一處理纹腌。
運(yùn)行實(shí)踐結(jié)果:
kill:
ios7.1:
application_didReceiveRemoteNotification_fetchCompletionHandler
application_didFinishLaunchingWithOptions
application_didRegisterForRemoteNotificationsWithDeviceToken
ios8.4:
application_didReceiveRemoteNotification_fetchCompletionHandler
application_didRegisterForRemoteNotificationsWithDeviceToken
application_didFinishLaunchingWithOptions
ios9.3.5:
application_didReceiveRemoteNotification_fetchCompletionHandler
application_didFinishLaunchingWithOptions
application_didRegisterForRemoteNotificationsWithDeviceToken
ios10:
application_didFinishLaunchingWithOptions
application_didRegisterForRemoteNotificationsWithDeviceToken
userNotificationCenter_didReceiveNotificationResponse_withCompletionHandler
壓入后臺(tái)
ios7.1.2:
application_didReceiveRemoteNotification_fetchCompletionHandler
ios8.4.1
application_didReceiveRemoteNotification_fetchCompletionHandler
ios9.3.5
application_didReceiveRemoteNotification_fetchCompletionHandler
ios10.0.1
userNotificationCenter_didReceiveNotificationResponse_withCompletionHandler
5,本地推送以及自定義推送聲音
不管是遠(yuǎn)程退送還是本地推送滞磺,都需要先注冊(cè)推送壶笼。(查看第3步)
// 初始化本地通知
UILocalNotification *noti = [[UILocalNotification alloc] init];
// 推送的聲音
noti.soundName = @"iosPush.caf";
// 推送執(zhí)行的時(shí)間
noti.fireDate = [NSDate dateWithTimeInterval:5 sinceDate:[NSDate date]];
// app上顯示的角標(biāo)
noti.applicationIconBadgeNumber = 12;
// 推送的內(nèi)容,必填項(xiàng)雁刷,不然會(huì)推送失敗。
noti.alertBody = @"aaaa";
// 推送的標(biāo)題
noti.alertTitle = @"vvvv";
// 注冊(cè)推送到系統(tǒng)
[[UIApplication sharedApplication] scheduleLocalNotification:noti];
[[UIApplication sharedApplication] presentLocalNotificationNow:noti];
5.1保礼,自定義聲音
由于自定義的聲音由系統(tǒng)去播放沛励,所以對(duì)其格式還是有要求限制的,可以是以下四種:
1)Linear PCM
2)MA4 (IMA/ADPCM)
3)μLaw
4)aLaw
對(duì)應(yīng)音頻文件格式是 aiff炮障,wav目派,caf 文件,文件也必須放到 app 的 mainBundle 目錄中胁赢。自定義通知聲音的播放時(shí)間必須在 30s 內(nèi)企蹭,如果超過(guò)這個(gè)限制,則將用系統(tǒng)默認(rèn)通知聲音替代智末。
可以使用 afconvert 工具來(lái)處理音頻文件格式谅摄,在終端中敲入如下命令就可以將一個(gè) mp3 文件轉(zhuǎn)換成 caf 文件:
afconvert iosPush.mp3 iosPush.caf -d ima4 -f caff -v
發(fā)送推送通知時(shí),只需配置 sound 字段即可系馆,就是 iosPush.caf 送漠。遠(yuǎn)程推送的時(shí)候只需讓后臺(tái)將sound配置成相應(yīng)的名字即可。
5.2由蘑,sound決定推送聲音和震動(dòng)的有無(wú)
APNs 通知通過(guò)sound字段來(lái)控制聲音闽寡,默認(rèn)為default,即系統(tǒng)的默認(rèn)聲音尼酿,如果設(shè)置為空值爷狈,則為靜音。如果設(shè)為特殊的名稱裳擎,需要在app的bundle文件中添加相應(yīng)的聲音文件涎永。
5.3,總結(jié)
1) 聲音鍵開啟時(shí):
想要既有聲音和震動(dòng)句惯,sound需要設(shè)為非空值土辩。
想要自定義聲音,sound需要設(shè)為工程中某個(gè)文件的名字(帶后綴)抢野。
想要既沒聲音也沒震動(dòng)拷淘,sound=nil;
想要有震動(dòng)沒聲音,sound需要設(shè)為工程中某個(gè)沒有聲音的音頻文件指孤。
2)聲音鍵關(guān)閉時(shí):
想要震動(dòng)启涯,_notification.soundName需要設(shè)為非空值贬堵。
想無(wú)震動(dòng),_notification.soundName=nil;
6结洼,iOS 10推送總結(jié)(圖片黎做,聲音)
Apple 在 iOS 10 中新增了 Notification Service Extension 機(jī)制,可在消息送達(dá)時(shí)進(jìn)行業(yè)務(wù)處理松忍,所以我們可以通過(guò)這個(gè)自定義通知的樣式蒸殿。
6.1,在項(xiàng)目中添加 Notification Service Extension
打開 Xcode 8鸣峭,菜單選擇 File
-> New
-> Target
-> Notification Service Extension
:
填寫 Target 的時(shí)候需要注意以下兩點(diǎn):
- Extension 的 Bundle Identifier 不能和 Main Target(也就是你自己的 App Target)的 Bundle Identifier 相同宏所,否則會(huì)報(bào) BundeID 重復(fù)的錯(cuò)誤。
- Extension 的 Bundle Identifier 需要在 Main Target 的命名空間下摊溶,比如說(shuō) Main Target 的 BundleID 為 com.xiaohongchun.xxx爬骤,那么Extension的BundleID應(yīng)該類似與com.xiaohongchun.xxx.yyy這樣的格式。如果不這么做莫换,會(huì)引起命名錯(cuò)誤霞玄。(建議使用<Main Target Bundle ID>.NotificationService)
- 添加 Notification Service Extension 后會(huì)生成相應(yīng)的 Target。點(diǎn) Finish 按鈕后會(huì)彈出是否激活該 Target 對(duì)應(yīng)的 scheme 的選項(xiàng)框拉岁,選擇 Activate坷剧。如下圖:
Notification Service Extension 添加成功后會(huì)在項(xiàng)目中自動(dòng)生成 NotificationService.h 和 NotificationService.m 兩個(gè)類,包含以下兩個(gè)方法:
didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent *contentToDeliver))contentHandler
我們可以在這個(gè)方法中處理我們的 APNs 通知喊暖,并個(gè)性化展示給用戶听隐。APNs 推送的消息送達(dá)時(shí)會(huì)調(diào)用這個(gè)方法,此時(shí)你可以對(duì)推送的內(nèi)容進(jìn)行處理哄啄,然后使用contentHandler方法結(jié)束這次處理雅任。但是如果處理時(shí)間過(guò)長(zhǎng),將會(huì)進(jìn)入serviceExtensionTimeWillExpire方法進(jìn)行最后的緊急處理咨跌。
- (void)serviceExtensionTimeWillExpire {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
self.contentHandler(self.bestAttemptContent);
}
如果didReceiveNotificationRequest方法在限定時(shí)間內(nèi)沒有調(diào)用 contentHandler方法結(jié)束處理沪么,則會(huì)在過(guò)期之前進(jìn)行回調(diào)本方法。此時(shí)你可以對(duì)你的 APNs 消息進(jìn)行緊急處理后展示锌半,如果沒有處理禽车,則顯示原始 APNs 推送。
6.2刊殉,配置個(gè)性化通知
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler = contentHandler;
// 1殉摔,把推送內(nèi)容轉(zhuǎn)為可變類型
self.bestAttemptContent = [request.content mutableCopy];
// 2,獲取自定義字段, msgimg是與后臺(tái)定義好的圖片鏈接
NSString *urlString = [request.content.userInfo valueForKey:@"msgimg"];
// 3记焊,根據(jù) url 創(chuàng)建 attachment
// 3.1 下載圖片
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:urlString]];
// 3.2 將圖片保存到沙盒
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *localPath = [documentPath stringByAppendingPathComponent:@"localNotificationImage.jpg"];
[imageData writeToFile:localPath atomically:YES];
//3.3設(shè)置通知的attachment
if (localPath && ![localPath isEqualToString:@""]) {
UNNotificationAttachment * attachment = [UNNotificationAttachment attachmentWithIdentifier:@"pushImage" URL:[NSURL URLWithString:[@"file://" stringByAppendingString:localPath]] options:nil error:nil];
if (attachment) {
self.bestAttemptContent.attachments = @[attachment];
}
}
self.contentHandler(self.bestAttemptContent);
}
6.2.1逸月,小提示
// Creates an attachment for the data at URL with an optional options dictionary. URL must be a file URL. Returns nil if the data at URL is not supported.
+ (nullable instancetype)attachmentWithIdentifier:(NSString *)identifier URL:(NSURL *)URL options:(nullable NSDictionary *)options error:(NSError *__nullable *__nullable)error;
1)設(shè)置通知的attachment時(shí),其中url必須是文件路徑, 所以需要拼接file:// 遍膜;
2)存在沙盒的推送圖片碗硬,會(huì)在推送完成后自動(dòng)刪除瓤湘;
3)發(fā)送 payload 需依照下述格式:
{
aps : {
alert : {...},
mutable-content : 1 //必須
}
your-attachment : aPicture.png //必須
}
-
mutable-content : 1
說(shuō)明該推送在接收后可被修改,這個(gè)字段決定了系統(tǒng)是否會(huì)調(diào)用 Notification Service 中的方法恩尾。 -
your-attachment:
是自定義的字段弛说,key 可以自定義(你自己要記住)翰意,value 需要是一個(gè)完整的文件名(或 url)木人,即你想要展示的文件。
6.3冀偶,測(cè)試推送效果
先運(yùn)行你的項(xiàng)目 target 使之在手機(jī)上安裝虎囚,再運(yùn)行 Notification Service 的 target,并選擇在你的項(xiàng)目上運(yùn)行該 Extension蔫磨。此時(shí)可進(jìn)行 Notification Service 代碼的調(diào)試,即在 NotificationService.m 中打斷點(diǎn)可以調(diào)試圃伶,但是在你的項(xiàng)目中的斷點(diǎn)無(wú)法調(diào)試堤如。
6.4,帶 Notification Service Extension 項(xiàng)目上傳 AppStore 須知
使用了 Notification Service Extension 的項(xiàng)目在制作待上傳至 AppStore 的 IPA 包時(shí)窒朋,編譯設(shè)備需要選擇 Generic iOS Device搀罢,然后再進(jìn)行 Product -> Archive 操作。只有選擇 Generic iOS Device 才能打包編譯出帶有 Notification Service Extension 且適配全機(jī)型設(shè)備的 IPA 包侥猩。如下圖所示:
以上就是近期做推送時(shí)的總結(jié)啦榔至,關(guān)于自定義3D touch功能還沒有進(jìn)行研究,后續(xù)學(xué)習(xí)之后再補(bǔ)全欺劳。
寫些簡(jiǎn)書文章唧取,全當(dāng)是記個(gè)筆記,希望能幫助到其他人划提,至少證明自己曾經(jīng)做過(guò)開發(fā)枫弟,今后看到自己的文章,就能想到當(dāng)時(shí)的自己在做些什么鹏往,回憶起來(lái)也是一段美好的時(shí)光吧淡诗。