iOS小技能:队列管理推送通知,解决收款到账并发语音播报问题。

简介: 需求:收款到账语音提醒功能NSE是比Voip更优雅的解决方案,完成迁移后,总体代码量也比Voip方案少了不少。

引言

需求:收款到账语音提醒功能

NSE是比Voip更优雅的解决方案,完成迁移后,总体代码量也比Voip方案少了不少。
https://kunnan.blog.csdn.net/article/details/103702284

遇到的问题:短时间内收到多条播报通知时,后面的通知会顶掉前面的通知,导致前面的通知播报不完整。

解决方式:增加一个消息队列,将所有需要播报的通知都添加到队列中,当前面的消息播放完毕后,再播放后面的消息。

音频的播放时间可以让后台通过payload推送,如果是自己离线合成的音频可以通过自己计算: 播放时间 =(音频大小 - 音频头)/ (采样频率 * 采样精度 * 通道数)

其他相关问题:由于服务端原因,客户端会收到两条消息一样
解决方案:消息去重

  1. 双通道触发的apns消息在requestheader上带上同样的apns-collapse-id,后面的通知就会覆盖前面的通知。

在这里插入图片描述
https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html#//apple_ref/doc/uid/TP40008194-CH11-SW1

  1. 通过记录已播放的消息单号,后面再重现重复的单号就讲sound设置为一段空白的音频。

I 消息播放队列的实现

1.1 方式一: 使用递归管理消息推送

  1. AudioTool.sharedPlayer单利对象心中一个数组属性,用来存储待处理的消息
/**
待处理的消息
*/
@property (nonatomic, strong) NSMutableArray *userInfos;
/**
是否正在处理消息
*/
@property (nonatomic, assign) BOOL isProcessing;

- (NSMutableArray *)userInfos{
    
    if(_userInfos == nil){
        _userInfos = [NSMutableArray array];
        
        
    }
    
    return _userInfos;
}

  1. NotificationService 收到消息时,往待处理的消息集合中添加,并调用递归方法处理消息
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    self.contentHandler = contentHandler;
    UNMutableNotificationContent *bestAttemptContent = [request.content mutableCopy];
    
    
    NSLog(@"NotificationService_%@: dict->%@", NSStringFromClass([self class]), bestAttemptContent.userInfo);
    
    bestAttemptContent.sound = nil;
    
    [AudioTool.sharedPlayer.userInfos addObject: bestAttemptContent];
    // 调用递归方法处理消息
     [self processNotificationContent];
   
}
  1. 递归方法的实现:判断退出条件及是否正在处理
- (void)processNotificationContent{
    
    if(AudioTool.sharedPlayer.userInfos.count<1){// 递归的退出条件
        return ;
    }
    
    if(AudioTool.sharedPlayer.isProcessing){//是否正在处理消息播报
        
        return ;

    }
    
    
    self.bestAttemptContent = AudioTool.sharedPlayer.userInfos.firstObject;
    AudioTool.sharedPlayer.isProcessing = YES;

    // 调用语音播报方法
                    [[AudioTool sharedPlayer] playPushInfo:weakSelf.bestAttemptContent.userInfo backModes:YES completed:^(BOOL success) {
                        
                        
                        
                        __strong typeof(weakSelf) strongSelf = weakSelf;
                        if (strongSelf) {
                            
                            NSMutableDictionary *dict = [strongSelf.bestAttemptContent.userInfo mutableCopy] ;
                                [dict setObject:[NSNumber numberWithBool:YES] forKey:@"hasHandled"] ;
                            
                            strongSelf.bestAttemptContent.userInfo = dict;
                            NSLog(@"当前方法是: %@",NSStringFromSelector(_cmd));

                            
                            strongSelf.contentHandler(strongSelf.bestAttemptContent);
                            
                            [AudioTool.sharedPlayer.userInfos removeObject:strongSelf.bestAttemptContent];
                            YJAudioTool.sharedPlayer.isProcessing = NO;
                            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                                [strongSelf processNotificationContent];// 循环调用自己

                            });

                        }
                    }];

}

1.2 方式二:NSOperationQueue

一个NSOperation对象可以通过调用start方法来执行任务,默认是同步执行的。也可以将NSOperation添加到一个NSOperationQueue(操作队列)中去执行,而且是异步执行的。

Operations的执行顺序:先满足依赖关系,然后再根据优先级从所有准备好的操作中选择优先级最高的那个执行。

————————————————
版权声明:本文为CSDN博主「iOS逆向」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请>附上原文出处链接及本声明。
原文链接: https://blog.csdn.net/z929118967/article/details/74298904
- (void)addOperation:(NSString *)title {
    [[NSOperationQueue mainQueue] addOperation:[self customOperation:title]];
}


- (NSOperation *)customOperation:(NSString *)content {
        __weak __typeof__(self) weakSelf = self;

    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        
        [weakSelf pushNotificationWith:content];
    }];
    return operation;
}

1.3 备用方案

当扩展处理消息推送的时间超过苹果规定的时候,在serviceExtensionTimeWillExpire处理。

- (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);//苹果规定,当一条通知达到后,如果在30秒内,还没有呼出通知栏,我就系统强制调用self.contentHandler(self.bestAttemptContent) 来呼出通知栏
}

II 语音播报

2.1 iOS前台可使用系统API进行语音播报

//语音合成
+ (void)Voicebroadcast:(NSString *)str
{
    AVSpeechSynthesizer * speechSynthesizer = [[AVSpeechSynthesizer alloc] init];
    AVSpeechUtterance *utterance = [AVSpeechUtterance speechUtteranceWithString:[NSString stringWithFormat:@"%@",str]];
    
    AVSpeechSynthesisVoice *voiceType = [AVSpeechSynthesisVoice voiceWithLanguage:@"zh-CN"];
    utterance.voice = voiceType;
    //设置语速
    utterance.rate *= 0.9;
    //设置音量
    utterance.volume = 1;
    
    [speechSynthesizer speakUtterance:utterance];
}

2.2 本地拼接音频+Service Extension

  1. 付费方案:iOS App处于后台/被杀死的状态仍可进行语言播报的实现方案( 离线合成+Service Extension):https://kunnan.blog.csdn.net/article/details/103702284
  2. 免费方案:(本地拼接音频+Service Extension)https://download.csdn.net/download/u011018979/84197453

2.3 uni 原生插件(支持iOS Extension)离线推送语音播报

https://blog.csdn.net/z929118967/article/details/127536579?spm=1001.2014.3001.5501

实现原理:

  1. 获取GroupIdentifier,用于保存本地拼接音频。
从info.plist 获取配置信息GroupIdentifier,这样扩展插件可灵活适用于不同app。
+(NSString*)getAppGroupID{
    NSDictionary *infoDic=[[NSBundle mainBundle] infoDictionary];
    NSString*  kAppGroupID =  [infoDic valueForKey:@"KNGroupIdentifier"];
    
    return kAppGroupID;
}

————————————————
版权声明:本文为CSDN博主「iOS逆向」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/z929118967/article/details/127536579
  1. 将音频文件写到AppGroups的Library/Sounds文件夹下,最后更改UNNotificationSound属性即可使通知播报一段自定义的收款到账语音。
https://kunnan.blog.csdn.net/article/details/103702284

see also

更多服务和咨询请关注#公号:iOS逆向

目录
相关文章
|
11月前
|
iOS开发 开发者
📝 App备案与iOS云管理式证书 ,公钥及证书SHA-1指纹的获取方法
在iOS应用程序开发过程中,进行App备案并获取公钥及证书SHA-1指纹是至关重要的步骤。本文将介绍如何通过appuploader工具获取iOS云管理式证书 Distribution Managed 公钥及证书SHA-1指纹,帮助开发者更好地理解和应用该过程。
|
3月前
|
语音技术 开发工具 图形学
Unity与IOS⭐一、百度语音IOS版Demo调试方法
Unity与IOS⭐一、百度语音IOS版Demo调试方法
|
3月前
|
语音技术 开发工具 图形学
Unity与IOS⭐二、Unity接入IOS版百度语音:语音唤醒
Unity与IOS⭐二、Unity接入IOS版百度语音:语音唤醒
Unity与IOS⭐二、Unity接入IOS版百度语音:语音唤醒
|
6月前
|
Android开发 iOS开发 开发者
App备案-iOS云管理式证书 Distribution Managed 公钥及证书SHA-1指纹的获取方法
App备案-iOS云管理式证书 Distribution Managed 公钥及证书SHA-1指纹的获取方法
412 0
|
4月前
|
调度 Swift Android开发
苹果iOS新手开发之Swift中的并发任务和消息机制
Swift的消息机制类似Android的Handler,实现任务调度有三种方式: 1. **Grand Central Dispatch (GCD)**:使用`DispatchQueue`在主线程或后台线程执行任务。 2. **OperationQueue**:提供高级接口管理`Operation`对象。 3. **RunLoop**:处理事件如输入源、计时器,类似Android的`Looper`和`Handler`。 **示例**: - GCD:在不同线程执行代码块。 - OperationQueue:创建操作并执行。 - RunLoop:用Timer添加到RunLoop中。
99 2
|
Android开发 iOS开发 开发者
App备案-iOS云管理式证书 Distribution Managed 公钥及证书SHA-1指纹的获取方法
,在appuploder直接复制IOS信息;如果还没有创建证书,请上传正确的P12苹果证书后,系统会自动解析出对应的签名和公钥信息; ——APP备案的原理是基于原有的工信部域名备案系统,如果已经有了域名备案,无需新增备案主体;只需要在之前的域名备案系统里面,新增APP信息,收集的APP信息主要包括APP包名和签名及公钥这3项;——APP备案是属于行政常规主体信息预存,和域名一样,自行决定是否备案。目前国内安卓应用商店是全面要求APP备案的,如果没有APP备案是不能通过审核发布到各大应用商店。——如看了教程,还不清楚怎么获取APP包名、安卓签名、苹果sha1签名、公钥等信息,请联系我们在线客服,
|
iOS开发
iOS多线程之NSOperationQueue-依赖、并发数、优先级、自定义Operation等最全的使用总结
iOS多线程之NSOperationQueue-依赖、并发数、优先级、自定义Operation等最全的使用总结
472 0
|
6月前
|
监控 API Swift
用Swift开发iOS平台上的上网行为管理监控软件
在当今数字化时代,随着智能手机的普及,人们对于网络的依赖日益增加。然而,对于一些特定场景,如家庭、学校或者企业,对于iOS设备上的网络行为进行管理和监控显得尤为重要。为了满足这一需求,我们可以利用Swift语言开发一款iOS平台上的上网行为管理监控软件。
274 2
|
6月前
|
iOS开发 开发者
App备案与iOS云管理式证书 ,公钥及证书SHA-1指纹的获取方法
App备案与iOS云管理式证书 ,公钥及证书SHA-1指纹的获取方法
517 0
|
6月前
|
Android开发 iOS开发 开发者
App备案-iOS云管理式证书 Distribution Managed 公钥及证书SHA-1指纹的获取方法
App备案-iOS云管理式证书 Distribution Managed 公钥及证书SHA-1指纹的获取方法
202 0