前言:
前段時(shí)間, 產(chǎn)品提出一個(gè)新的需求闸溃,要求實(shí)現(xiàn)外賣訂單的語(yǔ)音播報(bào)功能, 之前的開(kāi)發(fā)僅僅實(shí)現(xiàn)了前臺(tái)的語(yǔ)音播報(bào)功能潘拱,我這邊要實(shí)現(xiàn)的功能就是點(diǎn)擊APP進(jìn)入后臺(tái)或者APP進(jìn)程殺死之后揩瞪,依舊能收到語(yǔ)音播報(bào)赋朦,于是先是考慮用IOS7支持的靜默推送,不過(guò)沒(méi)有達(dá)到要求李破,聲音沒(méi)有播放出來(lái)宠哄,又想到了VoIP,蘋(píng)果推出的支持網(wǎng)絡(luò)電話的那一套嗤攻,看到網(wǎng)上的介紹毛嫉,后臺(tái)不好上線,就放棄了妇菱,在迷茫之時(shí)承粤,想到了IOS 10之后
推出的新功能,就是蘋(píng)果的推送擴(kuò)展(Notification Service Extension), 網(wǎng)上也有不少教程闯团,然后就按照教程一步一步的進(jìn)行下去...... 在此過(guò)程中遇到不少問(wèn)題辛臊,特此根據(jù)自己的實(shí)現(xiàn)過(guò)程(已上線),總結(jié)分析房交,如有錯(cuò)誤之處彻舰,請(qǐng)指出
一:Notification Service Extension通知服務(wù)擴(kuò)展
解析: 通俗點(diǎn)說(shuō)就是我們?cè)谑盏酵扑颓以趶椏蛘故局埃瑢?duì)通知內(nèi)容進(jìn)行修改(只針對(duì)遠(yuǎn)程推送)候味,如圖簡(jiǎn)易表示:
在實(shí)現(xiàn)功能之前刃唤,我也查閱不少資料,剛開(kāi)始看的時(shí)候白群,有點(diǎn)暈尚胞,因?yàn)樯婕暗搅酥匦屡渲米C書(shū)的問(wèn)題,不過(guò)帜慢,有的工程是直接自動(dòng)適配證書(shū)笼裳,如下:
但是我們的工程關(guān)于證書(shū)這方面,是手動(dòng)配置的崖堤,類似:
所以接下來(lái)侍咱,我所講述的大多涉及手動(dòng)配置證書(shū)方面,關(guān)于主工程的開(kāi)發(fā)環(huán)境和生產(chǎn)環(huán)境的Provisioning Profile密幔,具體的步驟,網(wǎng)上又不少講解撩轰,我這邊就直接省略了胯甩,上面廢話居多昧廷,接下來(lái)講述實(shí)現(xiàn)后臺(tái)語(yǔ)音播報(bào)功能的具體步驟。
二:
主工程需要如下配置:
創(chuàng)建Service Extension
1:點(diǎn)擊上圖右下角的Activate,這個(gè)時(shí)候最基本的操作就創(chuàng)建好了.
三:
經(jīng)過(guò)上面的步驟之后偎箫,會(huì)出現(xiàn)如下的截圖顯示
在TARGETS里面會(huì)出現(xiàn)一個(gè)新的推送擴(kuò)展工程木柬,它的Bundle Identifier與主工程的有一定的關(guān)聯(lián)性,這個(gè)時(shí)候涉及了Provisioning Profile淹办,剛開(kāi)始弄的時(shí)候眉枕,心情是萬(wàn)馬奔騰的,心想怜森,這難道還要再弄配置文件嘛速挑?還要再制作推送證書(shū)嗎?如果對(duì)這個(gè)推送擴(kuò)展制作推送證書(shū)的之后副硅,我的極光推送平臺(tái)上配置的關(guān)于推送的p12證書(shū)可需要替換姥宝?總之,一些列問(wèn)題涌向心頭......
1: 關(guān)于是否需要制作新的Provisioning Profile恐疲,答案是肯定的腊满,具體的制作方法和之前主工程制作推送證書(shū)的步驟是一致的,就是按部就班的從APP IDs 到 Provisioning Profiles(我這邊也配置了推送證書(shū)培己,不過(guò)后期沒(méi)用到), 配置好開(kāi)發(fā)環(huán)境碳蛋、生產(chǎn)環(huán)境的Provisioning Profile之后,按照下圖選擇好相對(duì)應(yīng)的配置文件省咨,即可
到了這一步疮蹦,關(guān)于推送擴(kuò)展的證書(shū)配置就完成了.
2: 關(guān)于是否制作推送證書(shū),我制作了茸炒,但是沒(méi)用到
3: 極光推送之前配置的P12證書(shū)愕乎,不需要改動(dòng),用之前的就行了
四:
1: 在設(shè)置推送的時(shí)候壁公,一定要帶上這個(gè)字段:"mutable -content" 感论,只有將該字段設(shè)置為1,下面的方法才會(huì)有效(具體如何添加紊册,和后臺(tái)協(xié)商)比肄。已極光推送為例,在測(cè)試環(huán)境下發(fā)推送的時(shí)候,可以打印出類似如下的數(shù)據(jù)(下面是我簡(jiǎn)單的寫(xiě)一下囊陡,方便參考)芳绩,
aps = {
alert = ‘輸入文字’;
badge = 1;
“mutable-content” = 1;
}
2: 下面兩個(gè)截圖是測(cè)試時(shí)在極光推送后臺(tái)配置的情況:
3: 此時(shí)打開(kāi)推送擴(kuò)展的.m文件,
語(yǔ)音播報(bào)就是在這個(gè)NotificationService.m文件里面實(shí)現(xiàn)的撞反,
#import "NotificationService.h"
#import <AVFoundation/AVFAudio.h>
@interface NotificationService ()
@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
@property (nonatomic, strong) AVSpeechSynthesizer *speechSynthesizer;
@end
@implementation NotificationService
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
/**
* 備注: 后臺(tái)推送過(guò)來(lái)的數(shù)據(jù)要協(xié)商好格式妥色,只要格式協(xié)商好,這部分就不會(huì)有問(wèn)題
*/
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
self.speechSynthesizer = [[AVSpeechSynthesizer alloc] init];
/*語(yǔ)音播報(bào)的內(nèi)容*/
NSString* soundTitle = self.bestAttemptContent.userInfo[@"aps"][@"alert"];
AVSpeechUtterance* speechUtterance = [[AVSpeechUtterance alloc] initWithString:soundTitle];
/*設(shè)置語(yǔ)速*/
speechUtterance.rate = 0.5;
/*設(shè)置音量*/
speechUtterance.volume = 1.0;
/*設(shè)置語(yǔ)調(diào)*/
speechUtterance.pitchMultiplier = 3.0;
/*設(shè)置哪國(guó)語(yǔ)言*/
speechUtterance.voice = [AVSpeechSynthesisVoice voiceWithLanguage:@"zh-CN"];
[self.speechSynthesizer speakUtterance:speechUtterance];
self.contentHandler(self.bestAttemptContent);
}
- (void)serviceExtensionTimeWillExpire {
self.contentHandler(self.bestAttemptContent);
}
@end
按照上面的步驟遏片,在極光平臺(tái)測(cè)試推送嘹害,就可以實(shí)現(xiàn)簡(jiǎn)單的語(yǔ)音播報(bào).
注意點(diǎn):
1: Debug測(cè)試撮竿,如下圖設(shè)置,可以對(duì)NotificationService.m進(jìn)行斷點(diǎn)操作
2: 推送的過(guò)程中會(huì)對(duì)數(shù)據(jù)進(jìn)行相應(yīng)的處理笔呀,這個(gè)與極光推送后臺(tái)API發(fā)送推送數(shù)據(jù)會(huì)有些誤差幢踏,我當(dāng)時(shí)也是踩了不少坑,我這邊的處理方法如下:
格式(大致的數(shù)據(jù)):
{
"aps": {
"alert": "您有一個(gè)新的商品訂單许师,點(diǎn)擊查看詳情",
"extras": {
"has_voice": true,
"type": "weixin_order"
},
"mutable-content": 1
}
}
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.bestAttemptContent = [request.content mutableCopy];
NSMutableDictionary *extras = [NSMutableDictionary dictionaryWithDictionary: self.bestAttemptContent.userInfo];
[extras removeObjectForKey:@"aps"];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setValue:extras forKey:@"extras"];
NSString* type = params[@"extras"][@"type"];
BOOL has_voice = [params[@"extras"][@"has_voice"] boolValue];
}
看到上面的方法房蝉,估計(jì)你會(huì)有點(diǎn)郁悶,我之前嘗試過(guò)
NSString* type = self.attemptContent.userInfo[@"aps"][@"extras"][@"type"];
不過(guò)沒(méi)有獲取type的值.
3: 如果感覺(jué)系統(tǒng)自帶的文字轉(zhuǎn)語(yǔ)音, 播放的音質(zhì)不太滿意微渠,可以嘗試語(yǔ)音合成的方法搭幻,具體可參考這個(gè)鏈接(如果有不理解的,我這邊也是用合成語(yǔ)音的方法實(shí)現(xiàn)語(yǔ)音播報(bào)的敛助,可分享):
自定義語(yǔ)音合成播報(bào)
下面是關(guān)于iOS12.1之后粗卜,語(yǔ)音播報(bào)失效的情況分析與處理.
1: 下圖是官方給出的說(shuō)明,之前給出這個(gè)拓展推送主要是為了豐富推送的UI樣式纳击,推送信息加密之類的续扔,結(jié)果卻被用做推送語(yǔ)音播報(bào),所以就發(fā)了這個(gè)聲明焕数,在12.1之后纱昧,在這個(gè)推送擴(kuò)展里面AVAudioPlayer就失效了.
2: 解決方法:
既然我們可以修改推送內(nèi)容的title、subtitle和body堡赔,那么由此類推识脆,同樣的話,我們也可以修改推送的sound善已,又因?yàn)橥扑蛿U(kuò)展和主工程是相互隔離的狀態(tài)灼捂,這點(diǎn)在外面debug的時(shí)候已經(jīng)明確了,這個(gè)時(shí)候采用本地通送的方式换团,在推送擴(kuò)展里面發(fā)出本地推送去調(diào)取主目錄下的音頻文件悉稠。
具體實(shí)現(xiàn)方法如下:
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
if (@available(iOS 12.1, *)) {
[self registerNotificationServiceType:type completeHandler:^{
weakSelf.contentHandler(weakSelf.attemptContent);
}];
} else {
// 此處調(diào)用之前的語(yǔ)音播報(bào)的內(nèi)容
}
}
// type: 后臺(tái)推送過(guò)來(lái)的
- (void)registerNotificationServiceType:(NSString*) type completeHandler:(void(^)(void))completeHandler{
[[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:UNAuthorizationOptionBadge|UNAuthorizationOptionSound|UNAuthorizationOptionAlert completionHandler:^(BOOL granted, NSError * _Nullable error){
if(granted) {
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc]init];
content.title = @"";
content.subtitle = @"";
content.body = @"";
NSString* sound = @"";
if ([type isEqualToString:@"take_order"]) {
sound = @"外賣訂單";
}else if ([type isEqualToString:@"weixin_order"]) {
sound = @"商品訂單";
}else if ([type isEqualToString:@"food_order"]) {
sound = @"點(diǎn)餐訂單";
}else if ([type isEqualToString:@"online_yuyue"]) {
sound = @"預(yù)約訂單";
}
// mp3格式的也是可以的
content.sound = [UNNotificationSound soundNamed:[NSString stringWithFormat:@"%@.m4a",sound]];
content.categoryIdentifier = [NSString stringWithFormat:@"noti_%@",type];
UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1 repeats:NO];
UNNotificationRequest* notificationRequest =
[UNNotificationRequest requestWithIdentifier:[NSString stringWithFormat:@"noti_%@",type] content:content trigger:trigger];
[[UNUserNotificationCenter currentNotificationCenter]addNotificationRequest:notificationRequest withCompletionHandler:^(NSError * _Nullable error) {
if(error == nil) {
completeHandler();
}
}];
}
}];
}
3: 其實(shí)上面的代碼實(shí)現(xiàn)起來(lái)不難,但是我卻碰到一個(gè)問(wèn)題艘包,就是剛開(kāi)始的時(shí)候的猛,我怎么推送都沒(méi)聲音,當(dāng)時(shí)以為是不是音頻格式的問(wèn)題想虎,最后發(fā)現(xiàn)是都不是卦尊,然后無(wú)意間點(diǎn)擊home鍵讓app進(jìn)入后臺(tái)狀態(tài),卻有了推送聲音舌厨,此時(shí)我想既然進(jìn)入后臺(tái)和殺死進(jìn)程狀態(tài)下都有語(yǔ)音播報(bào)聲音岂却,那么前臺(tái)情況下就更好處理了,然后打開(kāi)appdelegate.m文件下,看看能不能在
- (void)jpushNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(NSInteger))completionHandler{
completionHandler(UNNotificationPresentationOptionAlert);
}
處理一番淌友,結(jié)果發(fā)現(xiàn)煌恢,之前只寫(xiě)了 UNNotificationPresentationOptionAlert 骇陈,恍然大悟震庭,然后做了如下處理就可以了.
- (void)jpushNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(NSInteger))completionHandler {
completionHandler(UNNotificationPresentationOptionAlert |UNNotificationPresentationOptionBadge| UNNotificationPresentationOptionSound);
}