iOS13微信收款到帐语音提醒开发总结

黑客技术前端

点击右侧关注,了解黑客的世界!程序员

Java开发进阶算法

点击右侧关注,掌握进阶之路!数据库

Python开发编程

点击右侧关注,探讨技术话题!安全

做者丨littleliang
连接:
微信

https://juejin.im/post/5e182f666fb9a0300f7171f0

网络

以前承诺过发一篇iOS13下微信收款到帐语音提醒的总结文章,一直拖着没有写。一方面是尚未上线,另外一方面是后面一直在搞红包项目,如今两个项目都顺利上线,终于能够停下来为你们总结一下具体的方案和开发中遇到的问题。数据结构

1、背景

随着苹果爸爸在WWDC2019发布了新的iOS13,两年前的这篇方案已经不能再使用了.多线程

微信iOS收款到帐语音提醒开发总结

http://littleliang.xyz/2018/09/05/How-To-Implement-WeChat-Speech/

具体的缘由是iOS13中(准确的说是使用XCode11编译)苹果爸爸再也不容许voip push应用在非voip电话的功能,若是须要使用pushkit的话须要接入callkit的接口,致使收到Voip Push会拉起一个接打电话的全屏界面,有在国区发布过应用的同窗应该知道拉起这个界面是不容许的。这篇文章总结了在iOS13下的语音播报迁移方案以及一些须要注意的问题,目前微信的7.0.10版本已经带上了这部分的特性。

2、技术方案

Notification Service Extension

新的方案是主要是利用了Notification Service Extension(如下简称NSE),当apns的payload上带上"mutable-content"的值为1时,就会进入NSE的代码中,咱们能够在这里这里去改变后台推送的通知内容,包括通知的铃声。与Voip方案最大的不一样之处是,NSE不能唤醒主应用,也不能访问主应用的文件空间,只能在Extension进程中处理相应的逻辑。

UNNotificationSound

在NSE中,能够经过给UNNotificationContent中的Sound属性赋值来达到在通知弹出时播放一段自定义音频的目的。

// The sound file to be played for the notification. The sound must be in the Library/Sounds folder of the app's data container or the Library/Sounds folder of an app group data container. If the file is not found in a container, the system will look in the app's bundle.

文档中明确描述了音频文件的存储路径,以及读取的优先级:

  1. 主应用中的Library/Sounds文件夹中

  2. AppGroups共享目录中的Library/Sounds文件夹中

  3. main bundle中

自定义铃声支持的声音格式包括,aiff、wav以及wav格式,铃声的长度必须小于30s,不然系统会播放默认的铃声。

并且因为是通知铃声,声音是默认跟静音开关的,不需跟之前同样再使用判断静音开关的黑魔法(黑魔法在不一样机型上偶尔会出现误判的状况)。

AppGroups

因为咱们是在NSE中自定义铃声,因此1和3这两个文件路径咱们是没法访问的。只能将合成好或者下载到语音音频文件存储到AppGroups下的Library/Sounds文件夹中,须要在Capablities中打开这个AppGroups的能力,便可经过NSFileManager

containerURLForSecurityApplicationGroupIdentifier:方法访问AppGroups的根目录。

语音合成

微信的收款到帐语音依赖了咱们自研的强大的离线语音合成库。apns的payload中携带了须要合成的文本内容,经过离线语音合成库生成wav音频文件后,将文件写到AppGroups的Library/Sounds文件夹下,最后更改UNNotificationSound属性便可使通知播报一段自定义的收款到帐语音。

若是一些小型的企业自己不具有有离线合成的能力(看了下市面上的几个比较厉害的离线合成服务都是须要收费的),则能够采用在线合成再经过http下载的方式,讯飞和微信都有提供免费的服务。这个方案的缺点是依赖后台和当前的网络环境,有可能会致使消息播报不及时的问题。若是出现30s内都没法如今成功,须要在serviceExtensionTimeWillExpire方法中进行处理,最好的兜底方案是播放一段默认的语音。

3、开发过程当中遇到的问题

消息播放队列

NSE方案有个问题是:当客户端短期内收到多条播报通知时,后面的通知会顶掉前面的通知,致使前面的通知播报不完整,这种状况对于商家来讲是比较困扰的。因此须要增长一个消息队列,将全部须要播报的通知都添加到队列中,当前面的消息播放完毕后,再播放后面的消息。音频的播放时间可让后台经过payload推送,若是是本身的合成的wav能够经过播放时间 =(音频大小 - 音频头)/ (采样频率 * 采样精度 * 通道数)进行计算。

多线程问题

要注意的是,NSE的代码逻辑并非在主线程执行的。苹果这样的设计是很是合理的,一方面避免了开发者在NSE因为代码设计有问题致使前台的其余应用界面的问题,另外一方面是主工程此时已被挂起或者已被kill掉,原本也不该该给主线程的执行时间给到NSE。

因此咱们在处理上面提到的消息播放队列,以及涉及到文件的读写上,须要给相应的代码逻辑加锁,不然会出现多线程问题。

消息去重

因为支付的消息相较于普通消息对可达性与实时性的要求更高,因此当初设计的时候使用了双通道来下降Voip的偶现的丢消息和延迟的问题。以前的Voip方案是客户端会收到两条消息同样的Voip消息,经过记录payload中的单号来对消息进行去重。可是在NSE中,客户端是没法作到主动去重的,根本缘由是NSE的设计理念只是为了修改NotificationContent的内容,而不能阻止通知弹出,这一点能够从超时处理方法的文档中看出:

If your didReceive(_:withContentHandler:) method takes to long to execute its completion block, the system calls this method on a separate thread to give you one last chance to execute the block. Use this method to execute the block as quickly as possible. Doing so might mean providing some fallback content. For example, if your extension is still downloading an image file with the intent of attaching it to the notification’s content, you might update the notification’s alert text to indicate that an image is being downloaded. If you fail to execute the completion block from the didReceive(_:withContentHandler:) method in time, the system displays the notification’s original content.

若是你30s内没调用handler方法,而且没有实现serviceExtensionTimeWillExpire方法,那么系统会帮你主动推送后台推给客户端的原内容。

这里的解决方案是让后台,让双通道触发的apns消息在requestheader上带上一样的apns-collapse-id,后面的通知就会覆盖前面的通知。可是这里还有个问题就是虽然用户看到的是一条消息,可是声音仍是会播两次。这里就能够经过记录已播放的消息单号,后面再重现重复的单号就讲sound设置为一段空白的音频就ok了。

3、总结

其实如今回头看,NSE是比Voip更优雅的一个方案,NSE方案的整体代码量也比Voip少了很多,为何当初没有选择这个方案呢?这里其实也有它的历史缘由,一方面是NSE是iOS10之后才出现的新Extension,作初版方案的时候也是iOS10刚发布,对其的了解程度也不够。另外一方面,微信当时也不具有离线合成语音的能力的,只能经过Cgi去拉在线合成语音,而微信的Extension当时也没有Cgi请求的能力。切换到NSE方案后,最好的一个体验是语音播报与静音开关能完美契合,另外一方面是使人诟病的消息延迟问题也有所改善。

 推荐↓↓↓ 

????16个技术公众号】都在这里!

涵盖:程序员大咖、源码共读、程序员共读、数据结构与算法、黑客技术和网络安全、大数据科技、编程前端、Java、Python、Web编程开发、Android、iOS开发、Linux、数据库研发、幽默程序员等。

万水千山老是情,点个 “在看” 行不行