iOS13微信收款到帳語音提醒開發總結

以前承諾過發一篇iOS13下微信收款到帳語音提醒的總結文章,一直拖着沒有寫。一方面是尚未上線,另外一方面是後面一直在搞紅包項目,如今兩個項目都順利上線,終於能夠停下來爲你們總結一下具體的方案和開發中遇到的問題。微信

1、背景

隨着蘋果爸爸在WWDC2019發佈了新的iOS13,兩年前的這篇微信iOS收款到帳語音提醒開發總結方案已經不能再使用了,具體的緣由是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進程中處理相應的邏輯。app

UNNotificationSound

在NSE中,能夠經過給UNNotificationContent中的Sound屬性賦值來達到在通知彈出時播放一段自定義音頻的目的。ui

// 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.this

文檔中明確描述了音頻文件的存儲路徑,以及讀取的優先級:設計

  1. 主應用中的Library/Sounds文件夾中
  2. AppGroups共享目錄中的Library/Sounds文件夾中
  3. main bundle中

自定義鈴聲支持的聲音格式包括,aiff、wav以及wav格式,鈴聲的長度必須小於30s,不然系統會播放默認的鈴聲。code

並且因爲是通知鈴聲,聲音是默認跟靜音開關的,不需跟之前同樣再使用判斷靜音開關的黑魔法(黑魔法在不一樣機型上偶爾會出現誤判的狀況)。接口

AppGroups

因爲咱們是在NSE中自定義鈴聲,因此1和3這兩個文件路徑咱們是沒法訪問的。只能將合成好或者下載到語音音頻文件存儲到AppGroups下的Library/Sounds文件夾中,須要在Capablities中打開這個AppGroups的能力,便可經過NSFileManagercontainerURLForSecurityApplicationGroupIdentifier:方法訪問AppGroups的根目錄。隊列

語音合成

微信的收款到帳語音依賴了咱們自研的強大的離線語音合成庫。apns的payload中攜帶了須要合成的文本內容,經過離線語音合成庫生成wav音頻文件後,將文件寫到AppGroups的Library/Sounds文件夾下,最後更改UNNotificationSound屬性便可使通知播報一段自定義的收款到帳語音。進程

若是一些小型的企業自己不具有有離線合成的能力(看了下市面上的幾個比較厲害的離線合成服務都是須要收費的),則能夠採用在線合成再經過http下載的方式,訊飛和微信都有提供免費的服務。這個方案的缺點是依賴後臺和當前的網絡環境,有可能會致使消息播報不及時的問題。若是出現30s內都沒法如今成功,須要在serviceExtensionTimeWillExpire方法中進行處理,最好的兜底方案是播放一段默認的語音。

3、開發過程當中遇到的問題

消息播放隊列

NSE方案有個問題是:當客戶端短期內收到多條播報通知時,後面的通知會頂掉前面的通知,致使前面的通知播報不完整,這種狀況對於商家來講是比較困擾的。因此須要增長一個消息隊列,將全部須要播報的通知都添加到隊列中,當前面的消息播放完畢後,再播放後面的消息。音頻的播放時間可讓後臺經過payload推送,若是是本身的合成的wav能夠經過播放時間 =(音頻大小 - 音頻頭)/ (採樣頻率 * 採樣精度 * 通道數)進行計算。

消息去重

因爲支付的消息相較於普通消息對可達性與實時性的要求更高,因此當初設計的時候使用了雙通道來下降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方案後,最好的一個體驗是語音播報與靜音開關能完美契合,另外一方面是使人詬病的消息延遲問題也有所改善。

相關文章
相關標籤/搜索