最近申请了支付宝的二维码收钱码,其中支付宝有这么一个功能,就是,别人扫描你的二维码给你转账之后,收到钱会有一条语音推送,”支付宝到账 1000万“之类的推送消息,不管你的支付宝app有没有被杀死。
只要你的远程推送开着,并且支付宝的"二维码收钱到账语音提醒",都打开着,就可以收到。
打开方式:支付宝点击右上角设置-通用-新消息通知,打开到账提醒即可。
image.png
对支付宝进行相关测试:
1、iOS 10以下的设备收到钱之后不管App是杀死还是压入后台状态都会播报”支付宝到账一笔”一句固定的语音
2、iOS 10以下的设备收到钱之后不管App是杀死还是压入后台状态,并且设备处于静音状态,只是会有一个推送弹框和手机振动
3、iOS 10以上的设备,收到钱之后,不管APP是杀死还是压入后台状态,不管是静音还是非静音状态,在收到转账的时候,会播报”支付宝到账 ***** 元”
4、iOS 10以上的设备,在收到语音播报的时候,按音量键是可以调节音量大小的
实现以上功能注意的点:
iOS 10以上和iOS10以下设备实现方式不一样iOS 10以上需要考虑的因素,设备是否被杀死状态,静音非静音状态,音量是否可以调节,是否可以播报随机对应的钱数
并且别人给你转多少钱就会播报到账多少钱。
iOS 10 之前系统实现方案:
能保证设备在杀死或者压入后台的状况下收到信息,应该是远程推送的功劳了。
iOS 10 之前系统,可以借助远程推送定制铃声的功能来实现,只要在本地添加一段提前录制好的语音,并且在推送内容的时候将sound字段,修改成语音的名称即可
下面来看一下iOS10以上系统的实现播报自定义语音的实现方式:
实现以上功能需借助iOS 10的 NotificationServiceExtension
首先了解下常规的远程推送逻辑
iOS 10以后添加上NotificationServiceExtension之后的推送流程
image.png
可以查看相关文档了解: NotificationServiceExtension
NotificationServiceExtension这个东西有啥作用呢?
主要是能够在展示推送内容之前先获取到相关的推送信息,可以更改或者替换相关的推送内容。
怎么实现呢?
不用自己创建UNNotificationServiceExtension类,使用Xcode提供的模板直接选择就可以。如果你的app收到远程推送的话就会加载扩展并且调用didReceiveNotificationRequest:withContentHandler:,而做这些的前提就是,远程推送的那一套配置<证书之类的>得做好,还有就是推送的字典中包含mutable-content并且它的值是1;
iOS 10系统之后远程推送播报语音的实现思路:
设备在收到远程推送的时候,进入Service Extension,将远程推送的信息拦截下来,将需要播报的文字信息通过相关方式翻译成语音,进行播报,播报完毕之后再展示弹框信息。
UNNotificationServiceExtension的功能就是可以拦截到远程推送的信息,然后当调用self.contentHandler(self.bestAttemptContent); 之后就会进行弹框显示了,如果进行了弹框显示,那么UNNotificationServiceExtension的使命意味着结束了。
当拦截到信息,到弹框最多有30秒的时间进行操作。
Your extension has a limited amount of time (no more than 30 seconds) to modify the content and execute the contentHandler
block. If you do not execute that block in a timely manner, the system calls your extension’s serviceExtensionTimeWillExpire method to give you one last chance to execute the block. If you do not, the system presents the notification’s original content to the user.
大致思路定下之后,下一步操作,怎么把文字翻译成语音进行播报
1、使用科大讯飞以及类似的三方库,将远程推送过来的文字,直接使用三方库播放出来
2、使用 AVSpeechSynthesisVoice 相关类将远程推送过来的文字直接转化成语音,进行播报
3、如果播报的是钱数的话,可以在本地将相关可能播报的语音片段录制好,然后根据推送过来的内容标识,对语音片段进行拼接,然后进行播放
如果对Notification Servivice Extension不是很熟悉的,建议先了解一下
iOS10 推送extension之 Service Extension你玩过了吗?
在介绍相关方式之前,先介绍一个测试工具
SmartPush
使用方式也很简单,运行起来,输入相关的推送数据和token,并且选择对应的推送证书,点击推送即可
image.png
正式进入主题
推送的内容是:
{
"aps":{
"alert":{
"title":"iOS 10 title",
"subtitle":"iOS 10 subtitle",
"body":"世上只有妈妈好,有妈的孩子像块宝。投进妈妈的怀抱,幸福哪里找。没妈的孩子像根草。大河向东流,天上的星星参北斗,嘿呀,咿儿呀,嘿 嘿 咿儿呀"
},
"my-attachment":"http://img01.taopic.com/160317/240440-16031FU23937.jpg",
"mutable-content":1,
"category":"myNotificationCategory1",
"badge":3
}
}
1、使用科大讯飞以及类似的三方库,将远程推送过来的文字,直接使用三方库播放出来
这个流量用多了应该收费,具体可查看科大讯飞官网
将推送过来的文字转化成语音播放,然后在播放完毕的回调中执行self.contentHandler(self.bestAttemptContent);
@interface NotificationService ()<IFlySpeechSynthesizerDelegate,AVAudioPlayerDelegate,AVSpeechSynthesizerDelegate>
{
AVSpeechSynthesizer *synthesizer;
}
@property (nonatomic, strong) IFlySpeechSynthesizer *iFlySpeechSynthesizer;
@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
@property (nonatomic, strong)AVAudioPlayer *myPlayer;
@property (nonatomic, strong) NSString *filePath;
// AVSpeechSynthesisVoice 播放完毕之后的回调block
@property (nonatomic, copy)PlayVoiceBlock finshBlock;
// 科大讯飞播放完毕之后的block回调
@property (nonatomic, copy)PlayVoiceBlock kedaFinshBlock;
// 语音合成完毕之后,使用 AVAudioPlayer 播放
@property (nonatomic, copy)PlayVoiceBlock aVAudioPlayerFinshBlock;
@end
@implementation NotificationService
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
// Modify the notification content here...
self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.title];
__weak __typeof(self)weakSelf = self;
/**************************************************************************/
// 方式1,直接使用科大讯飞播放,成功,但是刚开始的时候可能需要几秒的准备播放时间
[self playVoiceKeDaXunFeiWithMessage:self.bestAttemptContent.body withBlock:^{
weakSelf.contentHandler(weakSelf.bestAttemptContent);
}];
}
#pragma mark- 使用科大讯飞播放语音
- (void)playVoiceKeDaXunFeiWithMessage:(NSString *)message withBlock:(PlayVoiceBlock)finshBlock
{
if (finshBlock) {
self.kedaFinshBlock = finshBlock;
}
//创建语音配置,appid必须要传入,仅执行一次则可
NSString *initString = [[NSString alloc] initWithFormat:@"appid=%@",@"59db7ce2"];
//所有服务启动前,需要确保执行createUtility
[IFlySpeechUtility createUtility:initString];
/******************************************************/
//获取语音合成单例
_iFlySpeechSynthesizer = [IFlySpeechSynthesizer sharedInstance];
//设置协议委托对象
_iFlySpeechSynthesizer.delegate = self;
//设置合成参数
//设置在线工作方式
[_iFlySpeechSynthesizer setParameter:[IFlySpeechConstant TYPE_CLOUD]
forKey:[IFlySpeechConstant ENGINE_TYPE]];
//设置音量,取值范围 0~100
[_iFlySpeechSynthesizer setParameter:@"50"
forKey: [IFlySpeechConstant VOLUME]];
//发音人,默认为”xiaoyan”,可以设置的参数列表可参考“合成发音人列表”
[_iFlySpeechSynthesizer setParameter:@" xiaoyan "
forKey: [IFlySpeechConstant VOICE_NAME]];
//保存合成文件名,如不再需要,设置为nil或者为空表示取消,默认目录位于library/cache下
[_iFlySpeechSynthesizer setParameter:@" tts.pcm"
forKey: [IFlySpeechConstant TTS_AUDIO_PATH]];
//启动合成会话
[_iFlySpeechSynthesizer startSpeaking:message];
}
//IFlySpeechSynthesizerDelegate协议实现
//合成结束
- (void) onCompleted:(IFlySpeechError *) error {
NSLog(@"合成结束 error ===== %@",error);
self.kedaFinshBlock();
}
结果
满足以下两点要求(1)、iOS 10以上的设备,收到推送之后,不管APP是杀死还是压入后台状态,不管是静音还是非静音状态,在收到转账的时候,会播报”到账 ***** 元”
(2)、iOS 10以上的设备,在收到语音播报的时候,按音量键是可以调节音量大小的
坑点
说明:当前实现的是将push内容中的body
播放出来
1、如果你收到推送了但是添加了系统的铃声,也就是你在push的json中添加了"sound":"default"
那么就可能会影响推送声音的播放
2、推送有问题
3、播放的语音时长最好不要超过30秒
4、如果说你的远程推送还是走的iOS10之前的逻辑,那么请检查一下你的推送的json有没有【"mutable-content":1】
2、使用 AVSpeechSynthesisVoice 相关类将远程推送过来的文字直接转化成语音,进行播报
AVSpeechSynthesisVoice 可查看官方文档
流程:
将推送过来的文字转化成语音播放,然后在播放完毕的回调中执行,和科大讯飞类似,不过是苹果系统相关类
相关代码
@interface NotificationService ()<IFlySpeechSynthesizerDelegate,AVAudioPlayerDelegate,AVSpeechSynthesizerDelegate>
{
AVSpeechSynthesizer *synthesizer;
}
@property (nonatomic, strong) IFlySpeechSynthesizer *iFlySpeechSynthesizer;
@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
@property (nonatomic, strong)AVAudioPlayer *myPlayer;
@property (nonatomic, strong) NSString *filePath;
// AVSpeechSynthesisVoice 播放完毕之后的回调block
@property (nonatomic, copy)PlayVoiceBlock finshBlock;
// 科大讯飞播放完毕之后的block回调
@property (nonatomic, copy)PlayVoiceBlock kedaFinshBlock;
// 语音合成完毕之后,使用 AVAudioPlayer 播放
@property (nonatomic, copy)PlayVoiceBlock aVAudioPlayerFinshBlock;
@end
@implementation NotificationService
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
// Modify the notification content here...
self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.title];
__weak __typeof(self)weakSelf = self;
/**************************************************************************/
// 方式4,AVSpeechSynthesisVoice使用系统方法,文字转语音播报,成功
[self playVoiceWithAVSpeechSynthesisVoiceWithContent:self.bestAttemptContent.body fishBlock:^{
weakSelf.contentHandler(weakSelf.bestAttemptContent);
}];
}
#pragma mark- AVSpeechSynthesisVoice文字转语音进行播放,成功
- (void)playVoiceWithAVSpeechSynthesisVoiceWithContent:(NSString *)content fishBlock:(PlayVoiceBlock)finshBlock
{
if (content.length == 0) {
return;
}
if (finshBlock) {
self.finshBlock = finshBlock;
}
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setActive:YES error:nil];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
// 创建嗓音,指定嗓音不存在则返回nil
AVSpeechSynthesisVoice *voice = [AVSpeechSynthesisVoice voiceWithLanguage:@"zh-CN"];
// 创建语音合成器
synthesizer = [[AVSpeechSynthesizer alloc] init];
synthesizer.delegate = self;
// 实例化发声的对象
AVSpeechUtterance *utterance = [AVSpeechUtterance speechUtteranceWithString:content];
utterance.voice = voice;
utterance.rate = 0.5; // 语速
// 朗读的内容
[synthesizer speakUtterance:utterance];
}
- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didStartSpeechUtterance:(AVSpeechUtterance *)utterance
{
NSLog(@"开始");
}
- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didFinishSpeechUtterance:(AVSpeechUtterance *)utterance
{
self.finshBlock();
NSLog(@"结束");
}
- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didPauseSpeechUtterance:(AVSpeechUtterance *)utterance
{
}
- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didContinueSpeechUtterance:(AVSpeechUtterance *)utterance
{
}
- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didCancelSpeechUtterance:(AVSpeechUtterance *)utterance
{
}
坑点
说明:当前实现的是将push内容中的body
播放出来
1、如果你收到推送了但是添加了系统的铃声,也就是你在push的json中添加了"sound":"default"
那么就可能会影响推送声音的播放
2、播放的语音时长最好不要超过30秒
3、如果说你的远程推送还是走的iOS10之前的逻辑,那么请检查一下你的推送的json有没有【"mutable-content":1】
4、如果在手机静音的状态下听不到播报的语音
5、AVSpeechSynthesizer 的对象一定要设置成全局变量,不然代理不会执行。那么你播放完成的回调就不会起作用
添加设置
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setActive:YES error:nil];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
结果
满足以下两点要求
(1)、iOS 10以上的设备,收到推送之后,不管APP是杀死还是压入后台状态,不管是静音还是非静音状态,在收到转账的时候,会播报”到账 ***** 元”
(2)、iOS 10以上的设备,在收到语音播报的时候,按音量键是可以调节音量大小的
3、如果播报的是钱数的话,可以在本地将相关可能播报的语音片段录制好,然后根据推送过来的内容标识,对语音片段进行拼接,然后进行播放
流程:
如果播报的内容是相对固定的片段组合体,这里那支付宝举例。
比如提前先录好 以下可能播报的内容
*****到账、 0、 1、 2、 3、 4、 5、 6、 7、 8、 9、 十、 百、 千、 万、 十万、 百万、 千万、 亿、 元 等等
然后根据推送的内容进行相关语音文件的对应,然后拼接,拼接完毕之后生成一个语音文件,然后进行播放
相关代码:
@interface NotificationService ()<IFlySpeechSynthesizerDelegate,AVAudioPlayerDelegate,AVSpeechSynthesizerDelegate>
{
AVSpeechSynthesizer *synthesizer;
}
@property (nonatomic, strong) IFlySpeechSynthesizer *iFlySpeechSynthesizer;
@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
@property (nonatomic, strong)AVAudioPlayer *myPlayer;
@property (nonatomic, strong) NSString *filePath;
// AVSpeechSynthesisVoice 播放完毕之后的回调block
@property (nonatomic, copy)PlayVoiceBlock finshBlock;
// 科大讯飞播放完毕之后的block回调
@property (nonatomic, copy)PlayVoiceBlock kedaFinshBlock;
// 语音合成完毕之后,使用 AVAudioPlayer 播放
@property (nonatomic, copy)PlayVoiceBlock aVAudioPlayerFinshBlock;
@end
@implementation NotificationService
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
// Modify the notification content here...
self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.title];
__weak __typeof(self)weakSelf = self;
/*******************************推荐用法*******************************************/
// 方法3,语音合成,使用AVAudioPlayer播放,成功
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setActive:YES error:nil];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
[self hechengVoiceAVAudioPlayerWithFinshBlock:^{
weakSelf.contentHandler(weakSelf.bestAttemptContent);
}];
}
#pragma mark- 合成音频使用 AVAudioPlayer 播放
- (void)hechengVoiceAVAudioPlayerWithFinshBlock:(PlayVoiceBlock )block
{
if (block) {
self.aVAudioPlayerFinshBlock = block;
}
/************************合成音频并播放*****************************/
AVMutableComposition *composition = [AVMutableComposition composition];
NSArray *fileNameArray = @[@"daozhang",@"1",@"2",@"3",@"4",@"5",@"6"];
CMTime allTime = kCMTimeZero;
for (NSInteger i = 0; i < fileNameArray.count; i++) {
NSString *auidoPath = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"%@",fileNameArray[i]] ofType:@"m4a"];
AVURLAsset *audioAsset = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:auidoPath]];
// 音频轨道
AVMutableCompositionTrack *audioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:0];
// 音频素材轨道
AVAssetTrack *audioAssetTrack = [[audioAsset tracksWithMediaType:AVMediaTypeAudio] firstObject];
// 音频合并 - 插入音轨文件
[audioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, audioAsset.duration) ofTrack:audioAssetTrack atTime:allTime error:nil];
// 更新当前的位置
allTime = CMTimeAdd(allTime, audioAsset.duration);
}
// 合并后的文件导出 - `presetName`要和之后的`session.outputFileType`相对应。
AVAssetExportSession *session = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetAppleM4A];
NSString *outPutFilePath = [[self.filePath stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"xindong.m4a"];
if ([[NSFileManager defaultManager] fileExistsAtPath:outPutFilePath]) {
[[NSFileManager defaultManager] removeItemAtPath:outPutFilePath error:nil];
}
// 查看当前session支持的fileType类型
NSLog(@"---%@",[session supportedFileTypes]);
session.outputURL = [NSURL fileURLWithPath:outPutFilePath];
session.outputFileType = AVFileTypeAppleM4A; //与上述的`present`相对应
session.shouldOptimizeForNetworkUse = YES; //优化网络
[session exportAsynchronouslyWithCompletionHandler:^{
if (session.status == AVAssetExportSessionStatusCompleted) {
NSLog(@"合并成功----%@", outPutFilePath);
NSURL *url = [NSURL fileURLWithPath:outPutFilePath];
self.myPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
self.myPlayer.delegate = self;
[self.myPlayer play];
} else {
// 其他情况, 具体请看这里`AVAssetExportSessionStatus`.
// 播放失败
self.aVAudioPlayerFinshBlock();
}
}];
/************************合成音频并播放*****************************/
}
#pragma mark- AVAudioPlayerDelegate
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
if (self.aVAudioPlayerFinshBlock) {
self.aVAudioPlayerFinshBlock();
}
}
结果
(1)、iOS 10以上的设备,收到推送之后,不管APP是杀死还是压入后台状态,不管是静音还是非静音状态,在收到转账的时候,会播报”到账 ***** 元”
(2)、iOS 10以上的设备,在收到语音播报的时候,按音量键是可以调节音量大小的
坑点
(1)、注意上面的坑点
(2)、播放音频的时候有两种播放形式AudioServicesPlayAlertSoundWithCompletion和AVAudioPlayer。
建议使用AVAudioPlayer,因为AVAudioPlayer能满足不受设备静音不静音的影响,能根据音量调节声音的高低。
AudioServicesPlayAlertSoundWithCompletion局限性比较大,会受静音的影响,只会震动,并且无法调整音量的高低。
合成语音之后如果使用AudioServicesCreateSystemSoundID播放的话,有一定的局限性
http://www.hangge.com/blog/cache/detail_771.html
1,系统声音服务介绍:
系统声音服务提供了一个Api,用于播放不超过30秒的声音。它支持的文件格式有限,具体的说只有CAF、AIF和使用PCM或IMA/ADPCM数据的WAV文件。
但此函数没有提供操作声音和控制音量的功能,因此如果是要为多媒体或游戏创建专门声音,就不要使用系统声音服务。
2,系统声音服务支持如下三种类型:
(1)声音:立刻播放一个简单的声音文件。如果手机静音,则用户什么也听不见。
(2)提醒:播放一个声音文件,如果手机设为静音或震动,则通过震动提醒用户。
(3)震动:震动手机,而不考虑其他设置。
说明:
上面并没有实现 数字转对应音频文件名称数组的过程,直接实现的是合成音频的方法。
Extension的运行生命周期:
iOS对于扩展的支持已经由最初的6类到了如今iOS10的19类(相信随着iOS的发展扩展的覆盖面也会越来越广),当然不同类型的扩展其用途和用法均不尽相同,但是其工作原理和开发方式是类似的。下面列出扩展的几个共同点:
扩展依附于应用而不能单独发布和部署;
扩展和包含扩展的应用(containing app)生命周期是独立的,分别运行在两个不同的进程中;
扩展的运行依赖于宿主应用(或者叫载体应用 host app,而不是containing app)其生命周期由宿主应用确定;
对开发者而言扩展作为一个单独的target而存在;
扩展通常展现在系统UI或者其他应用中,运行应该尽可能的迅速而功能单一;
image.png
关于断点调试
如果想通过断点调试来了解或者检查推送的流程怎么搞呢?
方法一:
image.png然后在相关的target中打断点就可以了,但是貌似在Xcode 9.0 9.1上面选择service Extension不太管用,直接走iOS 10 之前的推送去了,不知道为啥。
image.png
调试 content Extension
image.png
方法二:
如果想同时调试各个target怎么办?
将项目运行起来,然后发送一条推送之后,激活Service Extension,如果有需要可以激活content Extension<下拉一下推送条查看就好,前提你的conten Extension可以使用>
然后去选择,这个时候不要stop掉程序,根据下图选择完毕之后,在相关的地方打上断点,再次推送。
image.png image.png
建议使用方法二,各个流程顺序更加直观
关于数据共享
如果有这么一个需求,在主工程中有这么一个按钮,让用户控制是否播放钱数,还是受到一条简单的到账通知。
思路可以是这样的,在主工程中根绝用户的操作添加一个标识,然后在Extension中获取这个标识,通过这个标识的状态来判断是否需要读取用户的到账通知。
这个就是关于主工程和Extension之间数据共享的问题了。
详细可参考:
经测试,可以很轻松实现
image.png image.png image.png image.png image.png image.png最后献上相关的Demo地址,如果你有更好的建议欢迎留言,如有不正,欢迎来喷。
可以直接用我的Demo进行调试,调试的时候注意修改下bundleId,然后用自己的开发者账号配置一下相关的push证书就可以了
image.png image.png如果最终修改我的项目还是不能收到推送,那么建议你重新生成一个新的项目然后重新配置相关证书即可。
如果有帮到你,记得点赞奥,如果你有更好的方案,欢迎交流沟通。
网友评论
1.创建了NotificationService 在iOS10以上的系统上 测试ok
跑到iOS10机器以下就会闪退
如果正式打包在iOS10以下 推送会不会闪退呢
是否是测试10以上的版本 跑NotificationService 10以下版本 正常跑app 测试吗?
2.我在NotificationService中处理数据并且播放了语音 但是之后想调用工程里的函数 刷新一个UI 怎么操作呢
跑到iOS10机器以下就会闪退
如果正式打包在iOS10以下 推送会不会闪退呢
是否是测试10以上的版本 跑NotificationService 10以下版本 正常跑app 测试吗?
你首先先找到闪退的原因,闪退的原因应该是iOS一下的设备执行了大于iOS10的方法,导致的崩溃,这个你代码适配一下就行
正式打包的话,肯定也会闪退啊,所以得适配好
推送的话,iOS10以下的就走之前的正常的推送
2.我在NotificationService中处理数据并且播放了语音 但是之后想调用工程里的函数 刷新一个UI 怎么操作呢
在NotificationService中执行方法,和你的主APP是两个进程。你可以点击推送之后,进入app,在appdelegate中相关推送代理回调中执行刷新UI的操作
通知栏显示的原因是因为 执行了 self.contentHandler(self.bestAttemptContent); 这句代码。播报玩好几秒通知栏才会显示的话,你可以看一下播报语音结束的回调啥时候结束就行。我这边用Demo方法4测试的是,读完语音会马上显示
2、而且播报完延迟好几秒才播报下一条(通知栏显示上一条播报的通知内容后才播报下一条)
播报玩整个一条的流程的结束是 通知栏消失的时候,这个时候才代表当前的一条推送结束,推送条出来到消失,大概是6秒的时间,所以才会出现这种情况
3、我把self.contentHandler(self.bestAttemptContent);没写在你的block回调里,收到通知通知栏都会及时显示,但是语音只播报第一条。
如果你是收到推送就直接显示的话<调用 self.contentHandler(self.bestAttemptContent);>,通知栏是会马上显示,但是就会造成一个问题,你的语音如果大于6秒就会播报不全,你的推送条消失的时候,也就意味着当前推送已经结束,语音播放也会被终止,这就是为什么先播放玩语音在显示推送条的原因,可以去体验一下支付宝的功能,也是这么做的
4、我不清楚你说的,我写的block是哪个block,是语音播放完毕的block还是self.contentHandler。如果你不主动调用self.contentHandler(self.bestAttemptContent);那么系统会在30S后自动调用serviceExtensionTimeWillExpire方法,调用其中的self.contentHandler(self.bestAttemptContent);显示推送条然后完成推送流程
5、使用方法4测试推送的内容你可以推送这个,会自动播放body中的内容
{
"aps":{
"alert":{
"title":"iOS 10 title",
"subtitle":"iOS 10 subtitle",
"body":"12345"
},
"my-attachment":"http://img01.taopic.com/160317/240440-16031FU23937.jpg";,
"mutable-content":1,
"category":"myNotificationCategory1",
"badge":3
}
}
1、extension的bundle id应该用什么?跟主target保持一致吗,还是用默认的主target bundle id加上extension后缀?
2、Run的时候是run的extension,那么打包应该打主target还是extension呢?
3、extension的deployment target版本应该设为多少,10.0以上吗?还是可以更低?尤其是如果主target的deployment target低于10.0呢?
问题比较多,因为没有找到相关的答案,官方文档暂时未发现相关的介绍。如果有相关的经验,麻烦指教。多谢!
extension的bundle id 这个你就用你创建出来的那个就可以,创建出来系统不就给你创建好了bundleId了吗
2、Run的时候是run的extension,那么打包应该打主target还是extension呢?
run的时候你可以run extension也可以run主项目,可以参考下面的调试部分的说明,打包的话你就正常打主项目就可以。可以打包完毕了之后,写个将deviceToken复制到粘贴板的功能,用相关的测试工具测试一下生产状态下的相关的功能
3、extension的deployment target版本应该设为多少,10.0以上吗?还是可以更低?尤其是如果主target的deployment target低于10.0呢?
service Extension和content Extension的最低支持版本都是iOS 10.0。也就是说上面的功能最低支持到iOS10.0。你写iOS 10.0以上的话,不就出现了你上面的情况了
我用的推送工具Easy APNs Provider构造的payload数据只有content-available,没有mutable-content。
另外我手动填写了mutable-content :1,APP在后台收到通知但不会走extension的回调方法,而是手机直接显示推送内容。
大佬指定迷津,多谢!
我的调试设备系统是iOS10.3.2, 创建的Notification Service Extension Target的默认Deployment Target是最新的iOS11,把这个版本改为低于设备系统的版本就行了。
分享一下,希望能有所帮助。