在來聊這類需求的解決方案以前,我們仍是先來聊一聊這類需求的真實使用場景:語音播報。語音播報需求運用最爲普遍的應該是收銀對帳了,就相似於支付寶、微信、收錢吧等的收款語音提示同樣。在iOS 10 以前,蘋果沒有提供通知擴展類的時候,若是想要實現殺進程也能夠正常播報語音消息很難,從ios 10添加了這一個通知擴展類後,實現殺進程播報語音就相對簡單不少了。
這個Notification Service Extension 就是蘋果在 iOS 10的新系統中爲咱們添加的新特性,這個新特性就能幫助咱們用來解決殺死進程正常語音播報ios
蘋果官方解釋:UNNotificationServiceExtensiongit
新建完後,咱們的工程會多出一個文件夾,這裏示例Demo的Target命名爲 NotificationSE,文件夾中有NotificationService.h NotificationService.m 文件,這兩個文件就是後面咱們要用到的通知擴展類文件github
在沒有對NotificationService作任何修改時,咱們先來預覽下 .m 文件中都有哪些內容數組
從上面的截圖,咱們能夠看到,.m 文件其實很簡單,就 2 個函數,其實後面咱們對這個文件作邏輯處理,也是很簡單的。微信
AVSpeechSynthesizer
,AVSpeechSynthesisVoice
,AVSpeechUtterance
咱們先來看下一段語音播放代碼片斷:app
AVSpeechSynthesizer *av = [[AVSpeechSynthesizer alloc] init]; AVSpeechSynthesisVoice *voice = [AVSpeechSynthesisVoice voiceWithLanguage:@"zh-CN"]; AVSpeechUtterance *utterance = [[AVSpeechUtterance alloc] initWithString:@"我是測試文案"]; utterance.rate = 0.5; utterance.voice= voice; [av speakUtterance:utterance];
如今咱們將 NotificationService .m 文件作修改,使之支持語音播報。而且能支持多條通知同時過來的串行播報。完整文件以下:框架
// // NotificationService.m // NotificationSE // // Created by 劉光強 on 2018/9/17. // Copyright © 2018年 quangqiang. All rights reserved. // #import "NotificationService.h" #import <MediaPlayer/MediaPlayer.h> #import <AVFoundation/AVFoundation.h> @interface NotificationService ()<AVSpeechSynthesizerDelegate> @property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver); @property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent; @property (nonatomic, strong) AVSpeechSynthesisVoice *synthesisVoice; @property (nonatomic, strong) AVSpeechSynthesizer *synthesizer; @end @implementation NotificationService - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler { self.contentHandler = contentHandler; self.bestAttemptContent = [request.content mutableCopy]; // 這個info 內容就是通知信息攜帶的數據,後面咱們取語音播報的文案,通知欄的title,以及通知內容都是從這個info字段中獲取 NSDictionary *info = self.bestAttemptContent.userInfo; // 播報語音 [self playVoiceWithContent: info[@"content"]]; // 這行代碼須要註釋,當咱們想解決當同時推送了多條消息,這時咱們想多條消息一條一條的挨個播報,咱們就須要將此行代碼註釋 // self.contentHandler(self.bestAttemptContent); } - (void)playVoiceWithContent:(NSString *)content { AVSpeechUtterance *utterance = [AVSpeechUtterance speechUtteranceWithString:content]; utterance.rate = 0.5; utterance.voice = self.synthesisVoice; [self.synthesizer speakUtterance:utterance]; } // 新增語音播放代理函數,在語音播報完成的代理函數中,咱們添加下面的一行代碼 - (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didFinishSpeechUtterance:(AVSpeechUtterance *)utterance { self.contentHandler(self.bestAttemptContent); } - (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); } - (AVSpeechSynthesisVoice *)synthesisVoice { if (!_synthesisVoice) { _synthesisVoice = [AVSpeechSynthesisVoice voiceWithLanguage:@"zh-CN"]; } return _synthesisVoice; } - (AVSpeechSynthesizer *)synthesizer { if (!_synthesizer) { _synthesizer = [[AVSpeechSynthesizer alloc] init]; _synthesizer.delegate = self; } return _synthesizer; } @end
下面咱們來逐一對這個 .m 文件中的每個函數作下解釋函數
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {}
這個函數是通知擴展類的最爲核心的函數了,你能夠理解爲這個就是接受到蘋果APNS 通知的一個鉤子函數,每次當推送一條通知過來,都會執行到這個函數體內,因此說咱們的語音播報邏輯也是在這個鉤子函數中進行處理的。學習
- (void)playVoiceWithContent:(NSString *)content {}
這個函數很簡單了,就是咱們抽離出來的進行語音合成並播放出語音的函數,咱們傳遞一個語音文案做爲此函數的參數便可。測試
*- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didFinishSpeechUtterance:(AVSpeechUtterance *)utterance {}
這個函數就是咱們今天的主角了,咱們之因此可以實現當同時有多條通知同時推送,咱們還可以一條一條的串行逐條播放,主要的功能就歸功到這個函數了,這個函數是 AVSpeechSynthesizer
類的代理函數,就是一段語音播放完成後執行這個函數,每次當一條語音播放完成,都會被此函數勾住,咱們在函數體內實現咱們的處理邏輯。
- (void)serviceExtensionTimeWillExpire {}
此函數是擴展類自帶的一個函數,從這個函數解釋咱們能夠看出,這個函數是當擴展被系統終止以前,會調用到這個函數。
好了,.m文件的幾個關鍵的函數咱們都作了相應的解釋了,可能還有些小夥伴不是很明白,這些和解決通知串行逐一播報有什麼關係尼,下面我就來根據本身的經驗給你們作下詳細的解釋。
先來講下蘋果通知的通知欄問題
在蘋果通知中,當來一條通知時,咱們的手機會叮一下,而後手機通知欄彈出通知。這裏你們注意下,其實這個叮一下出來的通知欄也是有生命週期的。從通知欄被彈出來,到通知欄最終被收起,其實中間蘋果給了限制時間,大概就6秒左右的時長
說到6秒左右的時長,對於那些多條通知同時到達,須要串行來逐一播報,可是不少小夥伴們會遇到這樣一個問題:就是當同時來了多條通知,老是隻能播報2-3條,而後就語音中斷了,後面的通知不會播報了,遇到這些問題的小夥伴們有沒有注意到,其實只能播報2-3條,這個時間差其實就是6秒左右,也就是通知欄的生命週期時長。
出現上面的問題的緣由就是:當第一條通知來了,彈出通知欄,而後開始播報第一條語音,第一條播報完了,開始播報次日語音,可能當次日語音播報到一半了,可是這個時候,通知欄週期的時間到了,這時通知欄就會收起,注意:,當通知欄收起時,擴展類裏面的代碼就會終止執行,致使後面的語音播報終端。
上面說到當通知欄收起時,擴展類的代碼會終止執行,這裏又引出了另外一個注意點:就是咱們建立的這個擴展類也是有生命週期的,而且這個生命週期和通知欄的生命週期他們是有依賴關係的。即:當通知欄收起時,擴展類就會被系統終止,擴展內裏面的代碼也會終止執行,只有當下一個通知欄彈出來,擴展類就恢復功能
上面說到通知欄的出現和收起可以影響到擴展類的功能,那咱們是否是控制好通知欄的顯示和隱藏,就能解決多條串行問題尼?
是的,咱們只要控制好通知欄,就能夠解決上面的棘手問題,那麼問題又來了,咱們怎麼才能控制通知欄的顯示和隱藏尼?感受咱們平時使用蘋果的推送,歷來沒有關心過處理通知欄的顯示與隱藏,感受歷來沒有這樣用過,是的,對應普通的需求,咱們確實不須要關係通知欄顯示隱藏,感受這些蘋果系統本身已經處理好了,通知來了就顯示通知欄,等5秒左右,週期結束就隱藏通知欄。
其實啊,在擴展類裏面中,蘋果已經給咱們指出瞭如何控制通知欄的顯示和隱藏,核心就是這行代碼:self.contentHandler(self.bestAttemptContent);
,當咱們調用到這行代碼,就是用來彈出通知欄的,通知欄的隱藏不須要咱們來控制了,由於5秒左右的生命週期結束後,它會自動隱藏。
是否是對這樣代碼既熟悉有陌生啊,熟悉是由於你的擴展類文件中確實有這行代碼,陌生是由於你以前歷來都沒有用過這行代碼,不知道行代碼是用來幹啥的。
好了,既然self.contentHandler(self.bestAttemptContent);
這行核心代碼引用出來了,咱們就回到最開始的問題,在沒有作任何處理時,爲何當同時來多條通知是,語音播報就不能逐一播報尼,其實就是由於當每一條通知到達都會執行這個函數- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {}
,有沒有發現,這個函數體裏面 默認就是 執行了 self.contentHandler(self.bestAttemptContent);
這行代碼。
假設 一次性同時來了 10條 通知,就會一次性調用了 10次 didReceiveNotificationRequest
這個函數, 也就 執行了 10次 self.contentHandler(self.bestAttemptContent)
, 按照上面的說法,同時執行10次,不就是同時彈出10次的 通知欄嗎,這裏我調試時發現,當同時來10條通知時,通知欄並無同時彈出來10次,可能只彈出來1-2次。也就只能在這1-2次的時間長度中進行語音播報了。
上面解釋這麼多,那麼咱們到底該如何作尼,細心的同窗發現了,咱們上面 貼出來的 .m 代碼中,咱們新增了一個 AVSpeechSynthesizer
類的代理函數,就是語音播報完成的函數,咱們將 呼出通知欄的代碼 self.contentHandler(self.bestAttemptContent);
添加到這個代理函數中。意思就是:當第一條語音播放完成了,這時咱們呼出通知欄顯示播放的內容(通知欄的週期時間大概6秒左右),正好這時能夠播放第二條語音,等第二條語音播放完成了,呼出第二個通知的通知欄,繼續播放第三天語音,以此類推。
看到這裏,想必你們應該都理解了爲啥以前老是語音播報中斷的問題。
還有一個很重要的函數:- (void)serviceExtensionTimeWillExpire{}
,咱們上面只是提了下,具體他具體有什麼功能尼?
咱們發現serviceExtensionTimeWillExpire
函數中,也調用了 self.contentHandler(self.bestAttemptContent)
這行代碼,它爲啥也要調用這行代碼尼?
這是由於:當咱們在接受通知的鉤子函數中(didReceiveNotificationRequest
)沒有調用self.contentHandler(self.bestAttemptContent)
這行代碼,這時就會出現一個現象:就是通知收到了,可是沒有通知欄出現,這時蘋果就不容許了。蘋果規定,當一條通知達到後,若是在30秒內,尚未呼出通知欄,我就係統強制調用self.contentHandler(self.bestAttemptContent)
來呼出通知欄。 這時想必你們都知道 serviceExtensionTimeWillExpire
函數的用途了吧
這裏須要注意:當勾上上面的配置後,可能會致使蘋果審覈不經過,這裏咱們能夠在應用中添加一個語音播放的功能,並錄製視頻告知蘋果用途,可能會過審。
核心代碼以下
// 監聽通知函數中調用添加數據到隊列 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(nonnull void (^)(UIBackgroundFetchResult))completionHandler { [self addOperation: @"語音文案"]; } #pragma mark -隊列管理推送通知 - (void)addOperation:(NSString *)title { [[self mainQueue] addOperation:[self customOperation:title]]; } - (NSOperationQueue *)mainQueue { return [NSOperationQueue mainQueue]; } - (NSOperation *)customOperation:(NSString *)content { NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ AVSpeechUtterance *utterance = nil; @autoreleasepool { utterance = [AVSpeechUtterance speechUtteranceWithString:content]; utterance.rate = 0.5; } utterance.voice = self.voiceConfig; [self.synthConfig speakUtterance:utterance]; }]; return operation; } - (AVSpeechSynthesisVoice *)voiceConfig { if (_voiceConfig == nil) { _voiceConfig = [AVSpeechSynthesisVoice voiceWithLanguage:@"zh-CN"]; } return _voiceConfig; } - (AVSpeechSynthesizer *)synthConfig { if (_synthConfig == nil) { _synthConfig = [[AVSpeechSynthesizer alloc] init]; } return _synthConfig; }
speechSynthesizer:didFinishSpeechUtterance
語音播放完成的代理函數,可能有的小夥伴會遇到這個代理函數不執行的狀況,這時咱們須要將 AVSpeechSynthesizer
類的對象設置成全局屬性便可。content-avilable
字段的值,須要配置爲 1[https://github.com/guangqiang...]()
咱們公司以前作的掃碼支付需求,支付成功後播報支付金額,當時在開發這塊需求時,遇到了殺進程沒法進行語音播報的問題,後面引入了iOS10 的通知擴展類來解決殺進程問題。在使用擴展類時,也是遇到了很多的問題和大坑,這裏就逐一作了下總結,上面的講解也是填坑後的我的理解,若有錯誤之處,歡迎留言交流指出錯誤。