一茸歧、背景
2018年,受通信監(jiān)管要求丈挟,蘋果國區(qū)AppStore不允許app支持CallKit刁卜。
2019年,隨著iOS 13正式開放曙咽, Xcode11隨之發(fā)布蛔趴,蘋果已不再允許將PushKit應(yīng)用在非VoIP語音通話的場(chǎng)景上,開發(fā)者必須在接入CallKit的情況下才能使用PushKit例朱。也就是說孝情,通過 Xcode 11 編譯的 iOS 13 系統(tǒng) App,如果使用 PushKit 則必須依賴蘋果 CallKit 才能正常接收VoIP推送洒嗤。
結(jié)論:使用VoIP功能則無法在中國區(qū)App Store上架箫荡。
二、技術(shù)方案
VoIP
以微聊音視頻鈴聲播放為例渔隶,在之前的方案中羔挡,微聊音視頻消息會(huì)使用VoIP Push Notification,客戶端在被喚醒之后將獲得30s的后臺(tái)運(yùn)行時(shí)間间唉,在這30s內(nèi)绞灼,微聊被喚醒調(diào)起音視頻頁面,并播放定制鈴聲音頻呈野。
Notification Service Extension
新的方案是主要是利用了蘋果在iOS10中推出的Notification Service Extension(以下簡(jiǎn)稱NSE)低矮,當(dāng)apns的payload上帶上"mutable-content"的值為1時(shí),就會(huì)進(jìn)入NSE的代碼中际跪。與Voip方案最大的不同之處是商佛,NSE不能喚醒主應(yīng)用喉钢,也不能訪問主應(yīng)用的文件空間,只能在Extension進(jìn)程中處理相應(yīng)的邏輯良姆。在NSE中肠虽,開發(fā)者可以更改通知的內(nèi)容,利用離線合成或者從后臺(tái)下載的方式玛追,生成需要播報(bào)的內(nèi)容税课,通過自定義通知鈴聲的方式,達(dá)到語音播報(bào)提醒的目的痊剖。NSE方案也是蘋果在WWDC2019的Session707上推薦的解決方式韩玩。
UNNotificationSound
在NSE中,可以通過給UNNotificationContent中的Sound屬性賦值來達(dá)到在通知彈出時(shí)播放一段自定義音頻的目的陆馁。
// 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.
文檔中明確描述了音頻文件的存儲(chǔ)路徑找颓,以及讀取的優(yōu)先級(jí):
1.主應(yīng)用中的Library/Sounds文件夾中
2.AppGroups共享目錄中的Library/Sounds文件夾中
3.main bundle中
自定義鈴聲支持的聲音格式包括,aiff叮贩、wav以及caf格式击狮,鈴聲的長(zhǎng)度必須小于30s,否則系統(tǒng)會(huì)播放默認(rèn)的鈴聲益老。
鎖屏情況下彪蓬,鈴聲可以持續(xù)30s(與NES強(qiáng)制結(jié)束時(shí)間一致)在亮屏情況下鈴聲只能持續(xù)5s,是系統(tǒng)行為限制捺萌。
而且由于是通知鈴聲档冬,聲音是默認(rèn)跟靜音開關(guān)的。
AppGroups
由于我們是在NSE中自定義鈴聲桃纯,所以1和3這兩個(gè)文件路徑我們是無法訪問的酷誓。只能將合成好或者下載到語音音頻文件存儲(chǔ)到AppGroups下的Library/Sounds文件夾中,需要在Capablities中打開這個(gè)AppGroups的能力慈参,即可通過NSFileManager的containerURLForSecurityApplicationGroupIdentifier:方法訪問AppGroups的根目錄呛牲。
示例代碼:
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *containerUrl = [fileManager containerURLForSecurityApplicationGroupIdentifier:@"group.com.wchat.share"];
NSString *newPath = [containerUrl.path stringByAppendingPathComponent:@"Library/Sounds"];
// 清理本地緩存,刪除sounds文件夾(可選)
[fileManager removeItemAtPath:newPath error:nil];
if (![fileManager fileExistsAtPath:newPath]) {
[fileManager createDirectoryAtPath:newPath withIntermediateDirectories:NO attributes:nil error:nil];
}
NSURL *localURL = [NSURL fileURLWithPath:[newPath stringByAppendingPathComponent:[NSString stringWithFormat:@"/%@.%@", identifier, fileType]]];
[fileManager moveItemAtURL:temporaryFileLocation toURL:localURL error:&error];
//設(shè)置播發(fā)音頻 文件名sname.sfileType
self.bestAttemptContent.sound = [UNNotificationSound soundNamed:[NSString stringWithFormat:@"%@.%@", sname, sfileType]];
三驮配、開發(fā)過程中遇到的問題
消息播放隊(duì)列
NSE方案有個(gè)問題是:當(dāng)客戶端短時(shí)間內(nèi)收到多條播報(bào)通知時(shí)娘扩,后面的通知會(huì)頂?shù)羟懊娴耐ㄖ瑢?dǎo)致前面的通知播報(bào)不完整壮锻。所以需要增加一個(gè)消息隊(duì)列琐旁,將所有需要播報(bào)的通知都添加到隊(duì)列中,當(dāng)前面的消息播放完畢后猜绣,再播放后面的消息灰殴。
多線程問題
要注意的是,NSE的代碼邏輯并不是在主App工程中執(zhí)行的掰邢。一方面避免了開發(fā)者在NSE由于代碼設(shè)計(jì)失誤導(dǎo)致前臺(tái)的其他應(yīng)用界面卡住的問題牺陶,另一方面是主工程此時(shí)已被掛起或者已被kill掉伟阔,NSE本質(zhì)是push部分而不是調(diào)起主App。
所以我們?cè)谔幚砩厦嫣岬降南⒉シ抨?duì)列掰伸,以及涉及到文件讀寫的邏輯上皱炉,需要給相應(yīng)的代碼邏輯加鎖,否則會(huì)出現(xiàn)多線程問題狮鸭。
四合搅、NSE擴(kuò)展
iOS Notification Service Extension 共享空間數(shù)據(jù)互通測(cè)試
五、相關(guān)資料
Advances in App Background Execution - 2019
UNNotificationServiceExtension