需求分析
實(shí)現(xiàn)類似支付寶微信收款后的語(yǔ)音播報(bào)如:支付寶到賬xx元。要求是APP在前臺(tái)運(yùn)行、鎖屏赚楚、殺死進(jìn)程后都會(huì)有語(yǔ)音播報(bào)。那想到的解決方案就是利用推送了骗卜。
功能實(shí)現(xiàn)思路分析
上面說(shuō)了宠页,要使用推送,也就是APNs寇仓,這里我使用了極光推送举户,接下來(lái)就是實(shí)現(xiàn)手機(jī)接收到通知之后播報(bào)語(yǔ)音了,關(guān)于這個(gè)功能的實(shí)現(xiàn)在iOS10以后蘋果新增了“推送拓展”UNNotificationServiceExtension
遍烦,我們可以在這里操作敛摘,在這里我用的是蘋果官方的AVSpeechSynthesizer
和AVSpeechUtterance
來(lái)將接收到的推送內(nèi)容轉(zhuǎn)換成語(yǔ)音播報(bào),其中在這里乳愉,iOS12.1以后兄淫,不允許在UNNotificationServiceExtension
中播放語(yǔ)音了,我也查找過(guò)很多資料蔓姚,最終實(shí)現(xiàn)了一個(gè)比較折中的方法捕虽,下面會(huì)詳細(xì)說(shuō)。
功能實(shí)現(xiàn)
一坡脐、極光推送
關(guān)于極光推送的證書申請(qǐng)啥的就不講了泄私,官方文檔上寫的很清楚了,這里只說(shuō)將極光推送SDK集成到項(xiàng)目之后了备闲。
1晌端、集成極光推送SDK
在項(xiàng)目中的Podfile文件中添加pod 'JPush'
,然后pod install
恬砂,等待pod完成咧纠。
2、在AppDelegate.m中編寫推送功能代碼
(其實(shí)極光推送的文檔里也有)泻骤。
(1漆羔、在項(xiàng)目中引入所需頭文件:
// 引入 JPush 功能所需頭文件
#import "JPUSHService.h"
// iOS10 注冊(cè) APNs 所需頭文件
#ifdef NSFoundationVersionNumber_iOS_9_x_Max
#import <UserNotifications/UserNotifications.h>
#endif
(2梧奢、設(shè)置代理
@interface AppDelegate ()<JPUSHRegisterDelegate>
(3、在- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
方法中配置推送相關(guān)配置
初始化APNs:
- (void)initAPNS {
//Required
//notice: 3.0.0 及以后版本注冊(cè)可以這樣寫演痒,也可以繼續(xù)用之前的注冊(cè)方式
JPUSHRegisterEntity * entity = [[JPUSHRegisterEntity alloc] init];
if (@available(iOS 12.0, *)) {
entity.types = JPAuthorizationOptionAlert|JPAuthorizationOptionBadge|JPAuthorizationOptionSound|UNAuthorizationOptionProvidesAppNotificationSettings;
//應(yīng)用內(nèi)顯示通知設(shè)置的按鈕
} else {
entity.types = JPAuthorizationOptionAlert|JPAuthorizationOptionBadge|JPAuthorizationOptionSound;
}
if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0) {
// 可以添加自定義 categories
// NSSet<UNNotificationCategory *> *categories for iOS10 or later
// NSSet<UIUserNotificationCategory *> *categories for iOS8 and iOS9
}
[JPUSHService registerForRemoteNotificationConfig:entity delegate:self];
}
初始化JPUSH:
#pragma mark 初始化jpush
- (void)initJpushWithOptions:(NSDictionary *)launchOptions {
// Optional
// 獲取 IDFA
// 如需使用 IDFA 功能請(qǐng)?zhí)砑哟舜a并在初始化方法的 advertisingIdentifier 參數(shù)中填寫對(duì)應(yīng)值
// NSString *advertisingId = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
// Required
// init Push
// notice: 2.1.5 版本的 SDK 新增的注冊(cè)方法亲轨,改成可上報(bào) IDFA,如果沒有使用 IDFA 直接傳 nil
// 如需繼續(xù)使用 pushConfig.plist 文件聲明 appKey 等配置內(nèi)容鸟顺,請(qǐng)依舊使用 [JPUSHService setupWithOption:launchOptions] 方式初始化惦蚊。
NSString *appKey = @"你申請(qǐng)的推送AppKey";
[JPUSHService setupWithOption:launchOptions appKey:appKey
channel:@"0"
apsForProduction:NO
advertisingIdentifier:nil];
}
/*!
* @abstract 啟動(dòng)SDK
*
* @param launchingOption 啟動(dòng)參數(shù).
* @param appKey 一個(gè)JPush 應(yīng)用必須的,唯一的標(biāo)識(shí). 請(qǐng)參考 JPush 相關(guān)說(shuō)明文檔來(lái)獲取這個(gè)標(biāo)識(shí).
* @param channel 發(fā)布渠道. 可選.
* @param isProduction 是否生產(chǎn)環(huán)境. 如果為開發(fā)狀態(tài),設(shè)置為 NO; 如果為生產(chǎn)狀態(tài),應(yīng)改為 YES.
* App 證書環(huán)境取決于profile provision的配置,此處建議與證書環(huán)境保持一致.
* @param advertisingIdentifier 廣告標(biāo)識(shí)符(IDFA) 如果不需要使用IDFA讯嫂,傳nil.
*
* @discussion 提供SDK啟動(dòng)必須的參數(shù), 來(lái)啟動(dòng) SDK.
* 此接口必須在 App 啟動(dòng)時(shí)調(diào)用, 否則 JPush SDK 將無(wú)法正常工作.
*/
(4养筒、實(shí)現(xiàn)APNs的代理方法:
#pragma mark 注冊(cè) APNs 成功并上報(bào) DeviceToken
- (void)application:(UIApplication *)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
/// Required - 注冊(cè) DeviceToken
NSString *token = [[[[deviceToken description] stringByReplacingOccurrencesOfString:@"<" withString:@""] stringByReplacingOccurrencesOfString:@">" withString:@""] stringByReplacingOccurrencesOfString:@" " withString:@""];
NSLog(@"device token is %@", token);
[JPUSHService registerDeviceToken:deviceToken];
}
#pragma mark 實(shí)現(xiàn)注冊(cè) APNs 失敗接口
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
//Optional
NSLog(@"did Fail To Register For Remote Notifications With Error: %@", error);
}
#pragma mark 添加處理 APNs 通知回調(diào)方法
//這個(gè)方法是用來(lái)出來(lái)在收到推送通知,并且在還沒有展示出通知具體內(nèi)容時(shí)調(diào)用的端姚,可以在這里處理一些邏輯,比如說(shuō)APP在活躍狀態(tài)中設(shè)置不出現(xiàn)彈框和badge挤悉,只有聲音提示渐裸,或者說(shuō)APP在Active狀態(tài)下直接跳轉(zhuǎn)制定界面。
// iOS 10 Support
- (void)jpushNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(NSInteger))completionHandler {
// Required
NSDictionary * userInfo = notification.request.content.userInfo;
if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
[JPUSHService handleRemoteNotification:userInfo];
}
//驗(yàn)證別名
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSString *userID = [userDefaults objectForKey:prefix_userId];
NSString *localAlias = [NSString stringWithFormat:@"shop_id_%@",userID];
[JPUSHService getAlias:^(NSInteger iResCode, NSString *iAlias, NSInteger seq) {
NSLog(@"極光推送請(qǐng)求到的別名:iResCode=%ld,iAlias=%@,seq=%ld", iResCode, iAlias, seq);
if ([localAlias isEqualToString:iAlias]) {
if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) {
//活躍狀態(tài)
// completionHandler(UNNotificationPresentationOptionBadge); // 需要執(zhí)行這個(gè)方法装悲,選擇是否提醒用戶昏鹃,有 Badge、Sound诀诊、Alert 三種類型可以選擇設(shè)置
//重置角標(biāo)
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
[JPUSHService resetBadge];
if ([[UIDevice currentDevice].systemVersion doubleValue] >= 12.1) {
//如果是iOS12.1 有語(yǔ)音提示
completionHandler(UNNotificationPresentationOptionSound);
}
} else {
completionHandler(UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionSound|UNNotificationPresentationOptionAlert); // 需要執(zhí)行這個(gè)方法洞渤,選擇是否提醒用戶,有 Badge属瓣、Sound载迄、Alert 三種類型可以選擇設(shè)置
}
}
} seq:1];
}
//在iOS10 及以上系統(tǒng),收到通知后抡蛙,點(diǎn)擊通知框护昧,進(jìn)行的邏輯頁(yè)面跳轉(zhuǎn)(比如:跳轉(zhuǎn)到指定頁(yè)面)
// iOS 10 Support
- (void)jpushNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler {
// Required
NSDictionary * userInfo = response.notification.request.content.userInfo;
if([response.notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
[JPUSHService handleRemoteNotification:userInfo];
}
//設(shè)置角標(biāo)
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
[JPUSHService resetBadge];
//驗(yàn)證別名
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSString *userID = [userDefaults objectForKey:prefix_userId];
NSString *localAlias = [NSString stringWithFormat:@"shop_id_%@",userID];
[JPUSHService getAlias:^(NSInteger iResCode, NSString *iAlias, NSInteger seq) {
NSLog(@"極光推送請(qǐng)求到的別名ssss:iResCode=%ld,iAlias=%@,seq=%ld", iResCode, iAlias, seq);
//驗(yàn)證別名成功
if ([localAlias isEqualToString:iAlias]) {
//點(diǎn)擊跳轉(zhuǎn)頁(yè)面
}
} seq:1];
completionHandler(); // 系統(tǒng)要求執(zhí)行這個(gè)方法
}
//系統(tǒng)版本小于10.0 跳轉(zhuǎn)制定頁(yè)面
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
// Required, iOS 7 Support
[JPUSHService handleRemoteNotification:userInfo];
//設(shè)置角標(biāo)
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
[JPUSHService resetBadge];
//驗(yàn)證別名
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSString *userID = [userDefaults objectForKey:prefix_userId];
NSString *localAlias = [NSString stringWithFormat:@"shop_id_%@",userID];
[JPUSHService getAlias:^(NSInteger iResCode, NSString *iAlias, NSInteger seq) {
NSLog(@"極光推送請(qǐng)求到的別名ssss:iResCode=%ld,iAlias=%@,seq=%ld", iResCode, iAlias, seq);
//驗(yàn)證別名成功
if ([localAlias isEqualToString:iAlias]) {
//跳轉(zhuǎn)指定頁(yè)面
}
} seq:1];
completionHandler(UIBackgroundFetchResultNewData);
}
注:另外,我們是根據(jù)別名來(lái)進(jìn)行推送的粗截,別名是用戶名惋耙,所以需要在登錄的時(shí)候需要注冊(cè)別名
[JPUSHService setAlias:alias completion:^(NSInteger iResCode, NSString *iAlias, NSInteger seq) {
NSLog(@"極光推送設(shè)置別名:iResCode = %ld, alias = %@, seq = %ld", iResCode,iAlias, seq);
} seq:1];
在注銷登錄的時(shí)候注銷推送別名
[JPUSHService deleteAlias:^(NSInteger iResCode, NSString *iAlias, NSInteger seq) {
NSLog(@"極光推送清除別名:iResCode = %ld, alias = %@, seq = %ld", iResCode,iAlias, seq);
} seq:1];
(5、在UNNotificationServiceExtension
推送拓展中操作
這個(gè)UNNotificationServiceExtension
使用xcode自帶模板進(jìn)行創(chuàng)建熊昌,創(chuàng)建出來(lái)是一個(gè)新的target绽榛,具體流程可以參考這個(gè)博客https://blog.csdn.net/BUG_delete/article/details/80408661
在新建的UNNotificationServiceExtension
中我使用蘋果自帶的AVSpeechSynthesizer
、AVSpeechSynthesisVoice
和AVSpeechUtterance
來(lái)實(shí)現(xiàn)語(yǔ)音播報(bào)婿屹,當(dāng)然也可以使用訊飛或者百度等第三方SDK來(lái)實(shí)現(xiàn)灭美。
文件中默認(rèn)實(shí)現(xiàn)了方法
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
//用來(lái)展示通知彈框
self.contentHandler(self.bestAttemptContent);
}
這個(gè)方法用來(lái)接收通知推送,我們可以在這里面進(jìn)行處理昂利。
首先說(shuō)在iOS12.1之前語(yǔ)音播報(bào)方法:
#pragma mark iOS12.1以下 播放語(yǔ)音
- (void)playApsVoice {
//內(nèi)容是通知信息攜帶的數(shù)據(jù)
NSDictionary *info = self.bestAttemptContent.userInfo;
NSDictionary *contentDic = [info objectForKey:@"aps"];
//播放語(yǔ)音
[self playVoiceWithContent:contentDic[@"alert"]];
}
- (void)playVoiceWithContent:(NSString *)content {
AVSpeechSynthesizer * synthsizer = [[AVSpeechSynthesizer alloc] init];
synthsizer.delegate = self;
AVSpeechUtterance * utterance = [[AVSpeechUtterance alloc] initWithString:content];//需要轉(zhuǎn)換的文本
utterance.voice = [AVSpeechSynthesisVoice voiceWithLanguage:@"zh-CN"];//國(guó)家語(yǔ)言
utterance.rate = 0.5f;//聲速
utterance.volume = 1;
[synthsizer speakUtterance:utterance];
}
//新增語(yǔ)音播放代理 語(yǔ)音播放完成的代理函數(shù)中添加播完彈框功能
- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didFinishSpeechUtterance:(AVSpeechUtterance *)utterance {
self.contentHandler(self.bestAttemptContent);
}
接下來(lái)對(duì)這個(gè).m文件的每個(gè)函數(shù)逐一分析:
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {}
這個(gè)函數(shù)是通知拓展類的最為核心的函數(shù)了冲粤,你可以理解為這個(gè)就是接受到蘋果APNs通知的一個(gè)hock函數(shù)美莫,每次當(dāng)推送一條通知過(guò)來(lái),都會(huì)執(zhí)行到這個(gè)函數(shù)體內(nèi)梯捕,所以說(shuō)我們的語(yǔ)音播報(bào)邏輯也是在這個(gè)函數(shù)中進(jìn)行處理的厢呵。
- (void)playApsVoice{}
這個(gè)函數(shù)就是用來(lái)獲取通知信息攜帶的需要播放的語(yǔ)音的字段做處理進(jìn)行播放。
- (void)playVoiceWithContent:(NSString *)content {}
用來(lái)播放語(yǔ)音
- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didFinishSpeechUtterance:(AVSpeechUtterance *)utterance {}
之所以能夠?qū)崿F(xiàn)當(dāng)同時(shí)又多條通知同時(shí)推送傀顾,我們還能一條條串行逐條播放襟铭,主要的功能就是這個(gè)函數(shù),這個(gè)是AVSpeechSynthesizer
類的代理函數(shù)短曾,就是一段語(yǔ)音播放完成后執(zhí)行這個(gè)函數(shù)寒砖,每次當(dāng)一條語(yǔ)音播放完成,都會(huì)被此函數(shù)勾住嫉拐,我們?cè)诤瘮?shù)體內(nèi)實(shí)現(xiàn)我們的處理邏輯哩都。
- (void)serviceExtensionTimeWillExpire {}
這個(gè)函數(shù)時(shí)拓展類自帶的函數(shù),這個(gè)函數(shù)時(shí)當(dāng)拓展類被系統(tǒng)終止之前婉徘,調(diào)用這個(gè)函數(shù)漠嵌。被強(qiáng)行彈框。
蘋果通知的通知欄問(wèn)題
在蘋果通知中盖呼,當(dāng)來(lái)一條通知時(shí)儒鹿,我們的手機(jī)會(huì)叮一下,然后手機(jī)通知欄彈出通知几晤。這里大家注意下约炎,其實(shí)這個(gè)叮一下出來(lái)的通知欄也是有生命周期的。從通知欄被彈出來(lái)蟹瘾,到通知欄最終被收起圾浅,其實(shí)中間蘋果給了限制時(shí)間,大概就6秒左右的時(shí)長(zhǎng)憾朴。
說(shuō)到6秒左右的時(shí)長(zhǎng)贱傀,對(duì)于那些多條通知同時(shí)到達(dá),需要串行來(lái)逐一播報(bào)伊脓,但是很多小伙伴們會(huì)遇到這樣一個(gè)問(wèn)題:就是當(dāng)同時(shí)來(lái)了多條通知府寒,總是只能播報(bào)2-3條,然后就語(yǔ)音中斷了报腔,后面的通知不會(huì)播報(bào)了株搔,遇到這些問(wèn)題的小伙伴們有沒有注意到,其實(shí)只能播報(bào)2-3條纯蛾,這個(gè)時(shí)間差其實(shí)就是6秒左右纤房,也就是通知欄的生命周期時(shí)長(zhǎng)。
出現(xiàn)上面的問(wèn)題的原因就是:當(dāng)?shù)谝粭l通知來(lái)了翻诉,彈出通知欄炮姨,然后開始播報(bào)第一條語(yǔ)音捌刮,第一條播報(bào)完了,開始播報(bào)第二條語(yǔ)音舒岸,可能當(dāng)?shù)诙l語(yǔ)音播報(bào)到一半了绅作,但是這個(gè)時(shí)候,通知欄周期的時(shí)間到了,這時(shí)通知欄就會(huì)收起,注意:耳胎,當(dāng)通知欄收起時(shí),擴(kuò)展類里面的代碼就會(huì)終止執(zhí)行淮腾,導(dǎo)致后面的語(yǔ)音播報(bào)終端。
上面說(shuō)到當(dāng)通知欄收起時(shí),擴(kuò)展類的代碼會(huì)終止執(zhí)行,這里又引出了另一個(gè)注意點(diǎn):就是我們創(chuàng)建的這個(gè)擴(kuò)展類也是有生命周期的岂贩,并且這個(gè)生命周期和通知欄的生命周期他們是有依賴關(guān)系的。即:當(dāng)通知欄收起時(shí)巷波,擴(kuò)展類就會(huì)被系統(tǒng)終止萎津,擴(kuò)展內(nèi)里面的代碼也會(huì)終止執(zhí)行,只有當(dāng)下一個(gè)通知欄彈出來(lái)褥紫,擴(kuò)展類就恢復(fù)功能。
上面說(shuō)到通知欄的出現(xiàn)和收起能夠影響到擴(kuò)展類的功能瞪慧,那我們是不是控制好通知欄的顯示和隱藏髓考,就能解決多條串行問(wèn)題尼?
是的弃酌,我們只要控制好通知欄氨菇,就可以解決上面的棘手問(wèn)題,那么問(wèn)題又來(lái)了妓湘,我們?cè)趺床拍芸刂仆ㄖ獧诘娘@示和隱藏尼查蓉?感覺我們平時(shí)使用蘋果的推送,從來(lái)沒有關(guān)心過(guò)處理通知欄的顯示與隱藏榜贴,感覺從來(lái)沒有這樣用過(guò)豌研,是的,對(duì)應(yīng)普通的需求唬党,我們確實(shí)不需要關(guān)系通知欄顯示隱藏鹃共,感覺這些蘋果系統(tǒng)自己已經(jīng)處理好了,通知來(lái)了就顯示通知欄驶拱,等5秒左右霜浴,周期結(jié)束就隱藏通知欄。
其實(shí)啊蓝纲,在擴(kuò)展類里面中阴孟,蘋果已經(jīng)給我們指出了如何控制通知欄的顯示和隱藏晌纫,核心就是這行代碼:self.contentHandler(self.bestAttemptContent);,當(dāng)我們調(diào)用到這行代碼永丝,就是用來(lái)彈出通知欄的锹漱,通知欄的隱藏不需要我們來(lái)控制了,因?yàn)?秒左右的生命周期結(jié)束后类溢,它會(huì)自動(dòng)隱藏凌蔬。
是不是對(duì)這樣代碼既熟悉有陌生啊,熟悉是因?yàn)槟愕臄U(kuò)展類文件中確實(shí)有這行代碼闯冷,陌生是因?yàn)槟阒皬膩?lái)都沒有用過(guò)這行代碼砂心,不知道行代碼是用來(lái)干啥的。
好了蛇耀,既然self.contentHandler(self.bestAttemptContent); 這行核心代碼引用出來(lái)了辩诞,我們就回到最開始的問(wèn)題,在沒有做任何處理時(shí)纺涤,為什么當(dāng)同時(shí)來(lái)多條通知是译暂,語(yǔ)音播報(bào)就不能逐一播報(bào)尼,其實(shí)就是因?yàn)楫?dāng)每一條通知到達(dá)都會(huì)執(zhí)行這個(gè)函數(shù)- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {}撩炊,有沒有發(fā)現(xiàn)外永,這個(gè)函數(shù)體里面 默認(rèn)就是 執(zhí)行了 self.contentHandler(self.bestAttemptContent); 這行代碼。
假設(shè) 一次性同時(shí)來(lái)了 10條 通知拧咳,就會(huì)一次性調(diào)用了 10次 didReceiveNotificationRequest這個(gè)函數(shù)伯顶, 也就 執(zhí)行了 10次 self.contentHandler(self.bestAttemptContent), 按照上面的說(shuō)法骆膝,同時(shí)執(zhí)行10次祭衩,不就是同時(shí)彈出10次的 通知欄嗎,這里我調(diào)試時(shí)發(fā)現(xiàn)阅签,當(dāng)同時(shí)來(lái)10條通知時(shí)掐暮,通知欄并沒有同時(shí)彈出來(lái)10次,可能只彈出來(lái)1-2次政钟。也就只能在這1-2次的時(shí)間長(zhǎng)度中進(jìn)行語(yǔ)音播報(bào)了路克。
上面解釋這么多,那么我們到底該如何做尼养交,細(xì)心的同學(xué)發(fā)現(xiàn)了衷戈,我們上面 貼出來(lái)的 .m 代碼中,我們新增了一個(gè) AVSpeechSynthesizer 類的代理函數(shù)层坠,就是語(yǔ)音播報(bào)完成的函數(shù)殖妇,我們將 呼出通知欄的代碼 self.contentHandler(self.bestAttemptContent); 添加到這個(gè)代理函數(shù)中。意思就是:當(dāng)?shù)谝粭l語(yǔ)音播放完成了破花,這時(shí)我們呼出通知欄顯示播放的內(nèi)容(通知欄的周期時(shí)間大概6秒左右)谦趣,正好這時(shí)可以播放第二條語(yǔ)音疲吸,等第二條語(yǔ)音播放完成了,呼出第二個(gè)通知的通知欄前鹅,繼續(xù)播放第三天語(yǔ)音摘悴,以此類推。
看到這里舰绘,想必大家應(yīng)該都理解了為啥之前總是語(yǔ)音播報(bào)中斷的問(wèn)題蹂喻。
還有一個(gè)很重要的函數(shù):- (void)serviceExtensionTimeWillExpire{},我們上面只是提了下捂寿,具體他具體有什么功能尼?
我們發(fā)現(xiàn)serviceExtensionTimeWillExpire函數(shù)中口四,也調(diào)用了 self.contentHandler(self.bestAttemptContent) 這行代碼,它為啥也要調(diào)用這行代碼尼秦陋?
這是因?yàn)椋寒?dāng)我們?cè)诮邮芡ㄖ你^子函數(shù)中(didReceiveNotificationRequest)沒有調(diào)用self.contentHandler(self.bestAttemptContent) 這行代碼蔓彩,這時(shí)就會(huì)出現(xiàn)一個(gè)現(xiàn)象:就是通知收到了,但是沒有通知欄出現(xiàn)驳概,這時(shí)蘋果就不允許了赤嚼。蘋果規(guī)定,當(dāng)一條通知達(dá)到后顺又,如果在30秒內(nèi)更卒,還沒有呼出通知欄,我就系統(tǒng)強(qiáng)制調(diào)用self.contentHandler(self.bestAttemptContent) 來(lái)呼出通知欄稚照。 這時(shí)想必大家都知道 serviceExtensionTimeWillExpire 函數(shù)的用途了吧
此段解釋源自:https://blog.csdn.net/qq_23414675/article/details/82751049
關(guān)于iOS12.1及以上系統(tǒng)推送語(yǔ)音播報(bào)失效的問(wèn)題:
官方給出的說(shuō)明蹂空,之前給出這個(gè)拓展推送主要是為了豐富推送的UI樣式,推送信息加密之類的锐锣,結(jié)果卻被用做推送語(yǔ)音播報(bào)腌闯,所以就發(fā)了這個(gè)聲明绳瘟,在12.1之后雕憔,在這個(gè)推送擴(kuò)展里面AVAudioPlayer就失效了。
解決方法:這里我的處理可能不是最理想的解決方法糖声,因?yàn)槲以趇OS12.1及以上采用了播放固定錄制好的語(yǔ)音斤彼,并不能靈活播放推送消息了。
既然我們可以修改推送內(nèi)容的title蘸泻、subtitle和body琉苇,那么由此類推,同樣的話悦施,我們也可以修改推送的sound
在推送拓展target中拖入音頻文件:然后進(jìn)行如下設(shè)置:
self.bestAttemptContent.sound = [UNNotificationSound soundNamed:@"shoukuanAuido.wav"];
self.contentHandler(self.bestAttemptContent);
注意:
在項(xiàng)目target-Capabilities-Background Modes中要記得勾選Background fetch
和Remote notifications
這樣設(shè)置才可以正常接收推送并扇。并且在設(shè)置推送的時(shí)候,一定要帶上這個(gè)字段:"mutable -content" 抡诞,只有將該字段設(shè)置為1穷蛹,才可以正常實(shí)現(xiàn)功能。
另一種語(yǔ)音播放方式:https://blog.csdn.net/BUG_delete/article/details/80408661
因?yàn)橹皼]有做過(guò)此類功能,也是借鑒了很多大牛的解決方案,每個(gè)借鑒都有帶的鏈接馁龟,如果有侵權(quán)請(qǐng)聯(lián)系我刪除却音。目前就總結(jié)這么多句灌,有更好的想法希望可以在評(píng)論里一起交流骗绕。