推送+后台语音播报功能实现

总结一下最近项目中由于语音播报功能中遇到的坑和解决方案。

功能需求

在用户产生交易记录后,后台发送推送消息给登录的用户,用户收到推送消息后,对消息内容进行语音播报。

功能实现

1. 推送功能

推送功能相对简单,项目中集成了极光推送,就极光推送中遇到的坑做个简单记录:

  1. 生成别名不成功:别名传入的参数必须确保是字符串类型,检查是否有类型错误。

  2. 什么时候调用[JPushService setBadge:x];和[JPushService resetBadge];?

    关于这一点,极光的官方文档和社区问答写的实在是不尽人意,让人不知所云。经验总结:

  • 本地消息数的展示:直接使用推送消息中的badge进行展示

    1
        [UIApplication sharedApplication] setApplicationBadgeNumber:badge];
  • 如果badge在推送中使用+1的方式,则在需要增减badge时,就需要调用下[JPushService setBadge:x],让JPush帮忙保存badge。比如用户通过点击消息进入app,这时候badge需要-1,那么调用[JPushService setBadge:badge-1]来保存到JPush服务器,本地展示调用

    1
    [UIApplication sharedApplication] setApplicationBadgeNumber:badge-1];
  • 配置JPush时,注意区分debug和release模式下的apsForProduction参数(发布的时候一定不要忘,否则上线后收不到推送),可以使用宏来配置,就不必每次发布的时候进行修改。

    1
    [JPUSHService setupWithOption:launchOptions appKey:JPushAppKey channel:@"release" apsForProduction:YES advertisingIdentifier:advertisingId];
  • 推送消息处理:收到推送消息后,所有的消息都会走application: didReceiveRemoteNotification: fetchCompletionHandler:方法,所以需要对消息进行的统一处理,放在这个方法中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {

    [JPUSHService handleRemoteNotification:userInfo];// Required, iOS 7 Support
    //回调
    completionHandler(UIBackgroundFetchResultNewData);
    NSDictionary *aps = [userInfo valueForKey:@"aps"];
    NSInteger badge = [[aps valueForKey:@"badge"] integerValue]; //badge数量
    NSString *content = [userInfo valueForKey:@"iosNotification"]; //推送显示的内容
    [JPUSHService setBadge:badge];//JPush保存badge
    [application setApplicationIconBadgeNumber:badge];//badge展示

    //播放声音
    if (content.length ) {
    NSLog(@"推送消息------->content=%@",content);
    [PPUtility broadcastMessage:content];
    }
    }
  • 点击消息的处理放在JPush的jpushNotificationCenter: didReceiveNotificationResponse: withCompletionHandler:方法中。比如点击消息后,跳转到相关页面并设置badge-1。

2. 一般语音播报功能实现:

一般语音播报功能实现起来很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//语音播报功能
+(void)broadcastMessage:(NSString *)message
{
AVSpeechSynthesizer *speechSynthesizer = [[AVSpeechSynthesizer alloc]init];
AVSpeechUtterance *utterance = [AVSpeechUtterance speechUtteranceWithString:message];
utterance.voice = [AVSpeechSynthesisVoice voiceWithLanguage:@"zh-CN"];
CGFloat rate = 0.5;
if (IOS_VERSION >= 9.0 && IOS_VERSION < 10.0) {
rate = 0.52;
}else if (IOS_VERSION >= 8.0 && IOS_VERSION < 9.0){
rate = 0.15;
}
utterance.rate = rate;
utterance.preUtteranceDelay = 0.0;

[speechSynthesizer speakUtterance:utterance];
}

这里有个坑需要注意:

由于苹果API的问题,在iOS8和iOS9上和iOS10上,语音播报的速度各不相同,正常情况下,0.5是默认语速,但iOS8上语速奇快,也是让人心醉。另外iOS8.1不支持语音播报(包括真机和模拟器),iOS8.2以上可以支持。

3. 如何实现后台语音

很多人可能会选择开启back-ground modes里的audio功能,这的确是个最简单的实现方式,只要开启这个选项(证书里也需要勾选audio),在处理推送消息时调用上面的方法就可以实现前台、后台状态下的语音播报。但是,拿去审核的时候会被苹果拒掉!!官方文档对于background modes下的audio功能是这样描述的:

1
2
The app plays audible content to the user or records audio while in the background. (This content includes streaming audio or video content using AirPlay.)
The user must grant permission for apps to use the microphone prior to the first use.

在被拒的邮件中苹果给出的解释是:audio功能只能用于需要持续播放音频或持续后台录音的app。但根据我们的项目需求,显然不符合条件,浪费了audio持续后台的功能。没办法,只好舍弃iOS8和iOS9的后台语音播报,改用notification extention框架对iOS10进行支持。

实现步骤

  1. File——New——Target 新建Notification Service Extension
  2. 在新生成的NotificationService.m文件的didReceiveNotificationRequest:withContentHandler:方法中实现语音播报功能:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];

//语音播报
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 10.0) {
AVSpeechUtterance *utterance = [AVSpeechUtterance speechUtteranceWithString:self.bestAttemptContent.body];//根据要播报的内容传入不同参数

utterance.voice = [AVSpeechSynthesisVoice voiceWithLanguage:@"zh-CN"];
//设置播报语速
utterance.rate = 0.5;

AVSpeechSynthesizer *synth = [[AVSpeechSynthesizer alloc] init];
[synth speakUtterance:utterance];
}

// Modify the notification content here...
// self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.title];

self.contentHandler(self.bestAttemptContent);
}

还有一个必不可少的操作是:在推送的aps中增加mutable-content参数为1。

这样收到的效果是:iOS10以后的手机,无论何时收到推送消息都能即时语音播报。

Notification Service Extension报错处理

  1. 添加Extension后,提示:

    extension证书报错

    即使证书设置完全相同,仍然报错。

    解决方法:在钥匙串中找到Apple Worldwide Developer Relations Certification Authority证书,修改始终信任为系统默认,重新编译即可。

  2. 最低版本支持iOS8.0的项目,添加了支持iOS10以上的extension之后,无法运行在iOS8的手机上:This app contains an app extension that specifies an extension point identifier that is not supported on this version of iOS for the value of the NSExtensionPointIdentifier key in its Info.plist. 报出下面的弹窗:

    Extension在ios8安装错误

    解决方法:

    修改target——NotificationService——general里面的deployment target为10.0。

    extension在ios8安装错误解决方法

总结

推送+语音播报功能实现了支持iOS8、9的前台语音播报和iOS10前后台语音播报推送消息。

检查点:

  1. JPush集成注意推送环境参数设置apsForProduciton、channel、别名为字符串类型,服务端aps是否设置了production环境等
  2. 推送消息的badge注意保存到JPush服务器,及时增减
  3. 语音播报注意报错处理,deployment target设置,语音播报速度在各iOS系统上的差异处理等