iOS推送語(yǔ)音播報(bào)(類似支付寶收款提醒)

需求分析

實(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遍烦,我們可以在這里操作敛摘,在這里我用的是蘋果官方的AVSpeechSynthesizerAVSpeechUtterance來(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中我使用蘋果自帶的AVSpeechSynthesizerAVSpeechSynthesisVoiceAVSpeechUtterance來(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 fetchRemote 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)論里一起交流骗绕。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌钟鸵,老刑警劉巖种樱,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件以现,死亡現(xiàn)場(chǎng)離奇詭異记盒,居然都是意外死亡纪吮,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門萎胰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)碾盟,“玉大人,你說(shuō)我怎么就攤上這事技竟”龋” “怎么了?”我有些...
    開封第一講書人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵灵奖,是天一觀的道長(zhǎng)嚼沿。 經(jīng)常有香客問(wèn)我估盘,道長(zhǎng)瓷患,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任遣妥,我火速辦了婚禮擅编,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己爱态,他們只是感情好谭贪,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著锦担,像睡著了一般俭识。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上洞渔,一...
    開封第一講書人閱讀 49,821評(píng)論 1 290
  • 那天套媚,我揣著相機(jī)與錄音,去河邊找鬼磁椒。 笑死堤瘤,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的浆熔。 我是一名探鬼主播本辐,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼医增!你這毒婦竟也來(lái)了慎皱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤叶骨,失蹤者是張志新(化名)和其女友劉穎宝冕,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體邓萨,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡地梨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了缔恳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宝剖。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖歉甚,靈堂內(nèi)的尸體忽然破棺而出万细,到底是詐尸還是另有隱情,我是刑警寧澤纸泄,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布赖钞,位于F島的核電站,受9級(jí)特大地震影響聘裁,放射性物質(zhì)發(fā)生泄漏雪营。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一衡便、第九天 我趴在偏房一處隱蔽的房頂上張望献起。 院中可真熱鬧洋访,春花似錦、人聲如沸谴餐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)岂嗓。三九已至汁展,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間厌殉,已是汗流浹背善镰。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留年枕,地道東北人炫欺。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像熏兄,于是被迫代替她去往敵國(guó)和親品洛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容