美文网首页iOS开发经验收集iOS进阶知识iOS 坑的集中营
iOS 模仿支付宝支付到账推送,播报钱数

iOS 模仿支付宝支付到账推送,播报钱数

作者: 踩坑小分队 | 来源:发表于2017-10-11 16:19 被阅读1571次

    最近申请了支付宝的二维码收钱码,其中支付宝有这么一个功能,就是,别人扫描你的二维码给你转账之后,收到钱会有一条语音推送,”支付宝到账 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
    首先了解下常规的远程推送逻辑

    image.png

    iOS 10以后添加上NotificationServiceExtension之后的推送流程


    image.png

    可以查看相关文档了解: NotificationServiceExtension

    NotificationServiceExtension这个东西有啥作用呢?

    主要是能够在展示推送内容之前先获取到相关的推送信息,可以更改或者替换相关的推送内容。

    怎么实现呢?
    不用自己创建UNNotificationServiceExtension类,使用Xcode提供的模板直接选择就可以。如果你的app收到远程推送的话就会加载扩展并且调用didReceiveNotificationRequest:withContentHandler:,而做这些的前提就是,远程推送的那一套配置<证书之类的>得做好,还有就是推送的字典中包含mutable-content并且它的值是1;

    image.png

    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.

    image.png

    大致思路定下之后,下一步操作,怎么把文字翻译成语音进行播报

    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、推送有问题

    image.png

    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)、播放音频的时候有两种播放形式AudioServicesPlayAlertSoundWithCompletionAVAudioPlayer
    建议使用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之间数据共享的问题了。
    详细可参考:

    Extension 与主app共享数据

    经测试,可以很轻松实现

    image.png image.png image.png image.png image.png image.png

    最后献上相关的Demo地址,如果你有更好的建议欢迎留言,如有不正,欢迎来喷。

    可以直接用我的Demo进行调试,调试的时候注意修改下bundleId,然后用自己的开发者账号配置一下相关的push证书就可以了

    image.png image.png

    如果最终修改我的项目还是不能收到推送,那么建议你重新生成一个新的项目然后重新配置相关证书即可。
    如果有帮到你,记得点赞奥,如果你有更好的方案,欢迎交流沟通。

    相关文章

      网友评论

      • PGOne爱吃饺子:大佬 你好 ,播放语言必须要用service扩展么,不用扩展的话实现不了么
        踩坑小分队:@PGOne爱吃饺子 博客里面写了三种方法
      • 12bfadcbeb60:大神又来请教一个问题了,上生产的时候NotificationService的target需要配置描述文件吗?因为我是手动配置的证书签名,我是否需要单独去给NotificationService的bundleID配置一个描述文件关联一下生产证书
        12bfadcbeb60:@踩坑小分队 谢谢回复,最后我也是选择了自动管理
        踩坑小分队:@LEE木子李 不好意思,最近没关注简书,我一般打包都是选择自动的打包方式,具体生成啥证书你不用关心,Xcode 会帮你搞定
      • 4cee4b4f0365:大神,我有几个问题,1.我需要重新为那个新生成的target 创建证书吗?2.didReceiveNotificationRequest这个方法一直不被调用,是什么原因?
        Homey313:@being_best :smile: 我也已经解决
        4cee4b4f0365:@nsjhuk 解决了
        Homey313:请问问题解决了么,我有相同问题:joy:
      • 190CM:大神 我有两个问题
        1.创建了NotificationService 在iOS10以上的系统上 测试ok
        跑到iOS10机器以下就会闪退
        如果正式打包在iOS10以下 推送会不会闪退呢
        是否是测试10以上的版本 跑NotificationService 10以下版本 正常跑app 测试吗?
        2.我在NotificationService中处理数据并且播放了语音 但是之后想调用工程里的函数 刷新一个UI 怎么操作呢
        希达like:@踩坑小分队 我很奇怪 10楼的问题 serviceExtension不是iOS10以上的机子才能进去它的回调方法吗 为啥iOS10以下的机子会出现推送闪退 望解答
        希达like:我很奇怪 10楼的问题 serviceExtension不是iOS10以上的机子才能进去它的回调方法吗 为啥iOS10以下的机子会出现推送闪退 望解答@踩坑小分队 @190CM
        踩坑小分队:1.创建了NotificationService 在iOS10以上的系统上 测试ok 
        跑到iOS10机器以下就会闪退 
        如果正式打包在iOS10以下 推送会不会闪退呢
        是否是测试10以上的版本 跑NotificationService 10以下版本 正常跑app 测试吗?

        你首先先找到闪退的原因,闪退的原因应该是iOS一下的设备执行了大于iOS10的方法,导致的崩溃,这个你代码适配一下就行
        正式打包的话,肯定也会闪退啊,所以得适配好

        推送的话,iOS10以下的就走之前的正常的推送

        2.我在NotificationService中处理数据并且播放了语音 但是之后想调用工程里的函数 刷新一个UI 怎么操作呢

        在NotificationService中执行方法,和你的主APP是两个进程。你可以点击推送之后,进入app,在appdelegate中相关推送代理回调中执行刷新UI的操作
      • 12bfadcbeb60:请教一个问题,连续推送好几条消息,AVSpeechSynthesisVoice使用系统方法,文字转语音播报,成功语音播报为什么先播报完后好几秒通知栏显示才通知,而且播报完延迟好几秒才播报下一条(通知栏显示上一条播报的通知内容后才播报下一条),我把self.contentHandler(self.bestAttemptContent);没写在你的block回调里,收到通知通知栏都会及时显示,但是语音只播报第一条。您写的block好像没回掉,感觉像是走的serviceExtensionTimeWillExpire方法里的30秒之后自动显示通知,然后才进行下一条语音播报。才有的延迟的现象
        12bfadcbeb60:@踩坑小分队 谢谢楼主,解决了。你的文章里写的确实会延迟,我下了楼主的demo测试下您写的block确实及时回调了,按照文章写的确实没及时回调有延迟走的是serviceExtensionTimeWillExpire,所以上边问时候才把self.contentHandler(self.bestAttemptContent);拿出来没放在您的回掉函数里。仔细对比下文章和demo是语音合成器初始化的原因
        踩坑小分队:1、成功语音播报为什么先播报完后好几秒通知栏显示才通知
        通知栏显示的原因是因为 执行了 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

        }
        }
        12bfadcbeb60:楼主有没有好办法解决下?还是说只有我这样情况,楼主碰到过吗?:blush:
      • 欢欢1206:和用什么推送应该没关系吧?比如说第三方推送,极光等
        踩坑小分队:@欢欢1206 只要保证推送的json 格式,符合上面的说的要求就可以
      • wanglei1702:再请教些问题,
        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呢?

        问题比较多,因为没有找到相关的答案,官方文档暂时未发现相关的介绍。如果有相关的经验,麻烦指教。多谢!
        踩坑小分队:1、extension的bundle id应该用什么?跟主target保持一致吗,还是用默认的主target bundle id加上extension后缀?
        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以上的话,不就出现了你上面的情况了
      • wanglei1702:请问payload里面的mutable-content是什么,跟content-available有什么区别?
        我用的推送工具Easy APNs Provider构造的payload数据只有content-available,没有mutable-content。
        另外我手动填写了mutable-content :1,APP在后台收到通知但不会走extension的回调方法,而是手机直接显示推送内容。
        大佬指定迷津,多谢!
        踩坑小分队:@wanglei1702 :+1: :+1:
        wanglei1702:找到原因了。
        我的调试设备系统是iOS10.3.2, 创建的Notification Service Extension Target的默认Deployment Target是最新的iOS11,把这个版本改为低于设备系统的版本就行了。
        分享一下,希望能有所帮助。
      • saman0:厉害,不过楼主,用讯飞播放语音是成功了的。
        踩坑小分队:@踩坑小分队 你说的这个场景,感觉苹果做的有点类似队列的东西了,你已经推出去了,你也不知道一共到几条,估计是一条条的排队了。就像你连着发了10条推送,那么推送也是一条不拉的过去,铃声也是会一条条的播报的,你可以把 [ "mutable-content":1,]干掉,添加上【 "sound":"default"】试试
        踩坑小分队:@saman0 我试了一下,只能是一条条的播放,这样感觉也没有什么不妥啊,比如你播报钱数,那么肯定是希望一笔不拉的都播报吧。
        saman0:@踩坑小分队 问一下楼主,语音播放只能一条一条的播放吗?假如有连续的20条推送过来,我们可以只播放第一条或者最后一条吗?
      • 祭司123:请问APP处于后台或者被kill的情况下如何处理呢
        踩坑小分队:@祭司123 这个说白了就是远程推送后做的功能,你跑一把就知道了。远程推送你说的上面这两种情况都是不受影响的吧
      • 殷汇长15382366628:非常感谢,感谢分享!
      • soullink2011:大神,大腿伸出来我抱一下先:+1:

      本文标题:iOS 模仿支付宝支付到账推送,播报钱数

      本文链接:https://www.haomeiwen.com/subject/hpkwyxtx.html