iOS12.1之后语音播报问题解决,以及对Notification Service Extension的一些探索

  最近很多人表示升级iOS12.1后原有的播报程序无法正常运行,试了很多方法始终不行。之前需求太忙了,现在终于有时间沉下心来看一下这方面的问题,最后终于找到一个比较合适的解决办法。
  其实代码很简单,在这里主要总结一下整个问题解决的思路。没有耐心的小朋友可以直接拉到最后,有解决方法。

一、原因分析:为什么会无法播报

  我们之前采用的方法是,在Extension中拿到需要播报的内容,然后用合成语音的工具将文字转换成语音,再在Extension中进行播放。但进入12.1后我们打断点,是可以看到对应的错误提示,无法在后台进行audio的播放。
  明显的苹果在12.1上对Extension做了更多的完善和限制。原来基本上可以等同于一个完整的app,现在做的更像是一个挂载的包。虽然这些限制给我们造成了bug,但是这样做还是有好处的,毕竟原来没有限制的Extension是可以做很多你想象不到的事儿的,算是苹果对之前出的功能做了完善和补救。
  很多同学一开始抱着等待苹果修复这个问题的心态,在等,觉得这是苹果更新版本的bug。我只能说这些同学太天真了。从文档中可以看出,serviceExtension一开始诞生的目的,是为了给开发者提供一个改变服务器推送给iphone通知内容的程序,比如说对一些敏感内容进行解密,或者根据客户端具体状态改变通知展示内容。你在这里面偷摸的进行后台音频播放,这本身其实就是一个比较鸡贼的做法。
Notification Service Extension官方文档

二、方案制定

  既然已经定位到了问题出现的原因,接下来就是着手解决了。由于是之前没有遇到过的问题,说实话需要对整个推送过程重新梳理,看看有什么切入点来解决整个问题。毕竟不能说苹果不允许这个功能就不做了。


思路脑图

实现语音播报功能主要就是有以下三个途径

  • Notification Service Extension
  • 程序进入后台时保活播放
  • 利用VoIP唤醒app,执行代码进行播报

后台播放就不聊了,很早之前就研究过这种方案,能播,但是很不稳定,而且程序被杀死的情况下是无法播报的。
VoIP是可以实现功能,但是首先是审核的问题,如果你没有VoIP的功能,你是很难通过苹果的审核的,毕竟随意唤醒程序这种功能,苹果的给予还是很谨慎的。再有就是整个推送后台,如果换VoIP就需要整个重构。为了保证语音播报的准时送达,及时播报,我们也做了很多努力,我们整个推送架构现在已经相当稳定,整个重构对业务影响是巨大的。

经过分析后,解决问题的关键还是在Notification Service Extension上。因为没有方向,没有前人经验可以借鉴,只能所有可能的方式都试一下。一条路一条路试是很痛苦的,因为你不知道什么时候就撞到了南墙,然后继续迷失方向。但是根据经验,大方向基本是有的,就看哪条路能通了,而且有时候通向真理的路很有可能就是隐藏在林荫间的小路,初极狭,才通人,过后才霍然开朗。

三、着手解决

1.在plist里增加后台播放音频特性

既然你丫不让我后台播放,那我就强行允许自己能后台播放。


后台模式列表

我们知道苹果对于app的生命周期管控是很严格的,这也是为什么iOS的性能要比安卓好很多。你想要在后台运行,可以,但必须上报,必须说明你哪些功能要后台运行,如果我不认可,还不给你过(苹果爸爸就是这么牛逼)。
但是当你找到Extension的功能列表时会惊讶的发现,根本就没有勾选的地方。说白了,就是这些功能,苹果一个都不给你在扩展里用。
不给我选我自己手改总可以了吧,找到扩展的info.plist打开源码,添加后台音频播放。

<key>UIBackgroundModes</key>
    <array>
        <string>audio</string> 
    </array>

运行,走你!这时你会发现,我曹,解决了。一切恢复了正常。我真牛逼。
然后你兴奋的告诉产品,问题解决了,赶紧发个版本,把这个问题修复。然后你就会发现,打包上传appStore时候报错。。说这个字段是非法的,无法添加的。
好吧,你检查我的包,我就写在代码里,让你检测不出来。我就尝试在代码里动态的往info.plist里加代码。很可惜,也不行。在代码里,是无法对info.plist进行任何修改,你有读取的权限,却没有写入的权限。
好吧,这条路放弃!

2.通过Service Extension唤醒主app进行播报

既然扩展你不让播了,我告诉我大哥让他播总可以了吧。既然扩展是另一个程序,我通过openURL让住程序播放。听起来像是个不错的方案。
但写了之后又是各种报错,无法在扩展里用这个类,用那个类,编译都不给过!好吧,这条路放弃!

3.使扩展进入“前台”

既然是后台播放不允许,那我如果在前台你总不能不让我播吧。尝试着寻找一下扩展的生命周期,结果发现依然是无从下手。AppDelegate里面的生命周期的方法,写到扩展里面,根本就不走,所以就更谈不上改变了。
结论就是扩展,只是苹果暴露给开发者的一个执行代码的方法,比之前我认知的一个完整app,在开发者来看还是有很大区别的。

4.修改通知的UNNotificationSound

这时,之前的尝试基本都白费了,陷入了困局。每当到这种时候我就会静下心来,去看苹果官方的文档,看看有没有什么灵感或启发。这时我看到了一个属性。

@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;

给我们暴露出来的修改推送内容的属性其实是接收到的UNMutableNotificationContent,既然他的title、subTitle可以修改,那可不可以修改他的声音呢。
UNMutableNotificationContent有一个UNNotificationSoundName的sound属性,这个属性就是通知来的时候手机发出的声音,我们可以事先在app的bundle里、或者在Library/Sounds路径下,预置好对应的音频文件就可以播放。

// The name of a sound file to be played for the notification. The sound file must be contained in the app’s bundle or in the Library/Sounds folder of the app’s data container. If files exist in both locations then the file in the app’s data container will be preferred.
+ (instancetype)soundNamed:(UNNotificationSoundName)name __WATCHOS_PROHIBITED;

这样就又有了一个思路

  • 在推送扩展里将要播放的音频文件合成之后,存储到Library/Sounds的目录下,然后再修改推送的声音为该文件,就可以播放了。

由于我们之前就是用的合成本地的mp3形式播放的,所有合成和存储是没有难度的。但当我按这样方式执行了之后,却发现依旧不能播。
原因是扩展和主程序是两个不互通的bundle,你在扩展里存进去了,但是推送到达住app之后,他找的确是自己的路径底下的文件。
尝试了很多方法,什么文件共享,修改路径等等等等都不行。
难道这条路也要放弃?

就在这时我灵光一闪,那既然这两个是不同的app那我直接在扩展里,发送本地通知,这样我其实是相当于给扩展发一个通知,这下总该能找到对应的文件了吧。这样就又有了一个思路

  • 扩展在收到通知之后 -> 合成音频 -> 存储到扩展的对应路径 -> 扩展自己给自己发一个本地通知那个通知的sound设置成合成文件

自信满满修改代码,走你。结果还是没播,但是由于我偷懒没有改存储的文件名字,意外的发现,我之前存在主程序里的文件被播放了!
等等,为什么我在扩展里发本地通知,却发到了主程序里??难道是我的错觉?
我又在主程序里面放了几个单独的音频文件,发现了原来在扩展里面发本地通知,最后的接收方是主程序!!
这时我已经胸有成竹了,因为我知道这个问题已经被我解决了!

四、最终实现

最终的程序很快就被敲出来了。方法如下

  • 将你想要播放的音频拆分,放到主程序的包里
  • 利用Service Extension,在收到服务端的推送的时候,按照顺序发送本地通知
  • 本地通知的sound就是对应的音频拆分

这个方案和12.1之前的播报效果基本一模一样,而且无需后台改动,可以说是现在播报的最完美替代方案。
但是这个方案也有缺陷,不能完美的动态播放,只能是一些比较套路的文案,相对的动态播放,但这已经解决了我们业务遇到的问题,所以还是比较完美的方案。

最后谢谢大家观看,如果我的文章解决了你的问题,帮忙点个喜欢哦~
有错误和问题也欢迎指正,大家一起交流进步~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,022评论 4 62
  • 文件大师局域网传输/播放功能使用帮助(Mac电脑用户) 使用局域网账户密码登录,可以访问电脑上所有文件 怎么设置共...
    iOS开发者_独立阅读 12,077评论 0 1
  • 喜欢的小伙伴点个赞哦。 注意事项:css模块化,只针对类选择器、Id选择器生效;不会将标签选择器模块化。模块化之后...
    Eternal丶星空阅读 935评论 0 0
  • 你好呀,星星 原来你也是一个人呀 你为什么住在那么远的地方 那个深沉的令人窒息的角落 其实,我从小就想去看你 却又...
    者者行阅读 369评论 1 2