APP語音推送
標(biāo)簽(空格分隔): 極光推送 語音推送 APNS
目前主流的推送解決方案分為兩種。
自己搭建推送平臺巴柿,對服務(wù)器并發(fā)處理能力、開發(fā)人員技術(shù)能力、不同網(wǎng)絡(luò)環(huán)境處理機制等要求較高杀餐,適用于資源豐富的團(tuán)隊。
采用第三方公司提供的推送服務(wù)朱巨,極光推送史翘、百度云、信鴿等冀续,這一方案成本相對較低。因為瑞錢包之前采用的是極光推送洪唐,下面著重講一下極光推送對語音推送的實現(xiàn)方式钻蹬。
極光推送
對于iOS系統(tǒng),百度云、信鴿在App活躍和非活躍狀態(tài)時,都是通過APNS推送注整;極光推送在App非活躍狀態(tài)是通過APNS推送借浊,活躍狀態(tài)的情況,JPush Server會通過sdk建立長連接,直接由JPush Server發(fā)送消息到App內(nèi)臂港。
對于Android系統(tǒng)筋遭,只支持應(yīng)用內(nèi)推送响驴,Push SDK是作為Android Service 運行在后臺的透且,從而創(chuàng)建并保持長連接,保持在線的能力踏施,接收服務(wù)器Push的消息石蔗。
iOS
從上圖可以看出,JPush iOS Push 包括 2 個部分畅形,APNs 推送,與 JPush 應(yīng)用內(nèi)消息诉探。
- 紅色部分是 APNs 推送日熬,JPush 代理開發(fā)者的應(yīng)用,向蘋果 APNs 服務(wù)器推送肾胯。由 APNs Server 推送到 iOS 設(shè)備上竖席。
- 藍(lán)色部分是 JPush 應(yīng)用內(nèi)推送部分耘纱,App 啟動時,內(nèi)嵌的 JPush SDK 會開啟長連接到 JPush Server毕荐,從而 JPush Server 可以推送消息到 App 里束析。
APNS
APP進(jìn)程被殺死的情況,使用APNS推送憎亚,語音播放分兩種情況:
- ios10.0+可以使用UNNotificationServiceExtension實現(xiàn)動態(tài)語音播放员寇。
- ios10.0之前的系統(tǒng),只能播放固定音頻第美。
JPush應(yīng)用內(nèi)推送
APP進(jìn)程沒有被殺死的情況蝶锋,可以實現(xiàn)動態(tài)語音播放,下面是iOS關(guān)鍵實現(xiàn)代碼什往。
1.使用系統(tǒng)方法或者第三方SDK實現(xiàn)語音播放
AVSpeechUtterance *utterance = [AVSpeechUtterance speechUtteranceWithString:@"成功集成語音播報"];
AVSpeechSynthesizer *synth = [[AVSpeechSynthesizer alloc] init];
[synth speakUtterance:utterance];
2.APP在后臺語音播放扳缕,在delelgate類didFinishLaunchingWithOptions、applicationWillResignActive方法實現(xiàn)以下代碼
NSError *error = NULL;
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback error:&error];
if(error) {
// Do some error handling
}
[session setActive:YES error:&error];
if (error) {
// Do some error handling
}
// 讓app支持接受遠(yuǎn)程控制事件
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
// 在AppDelegate定義屬性
@property (nonatomic, unsafe_unretained) UIBackgroundTaskIdentifier backgroundTaskIdentifier;
- (void)applicationWillResignActive:(UIApplication *)application;
- (void)applicationWillResignActive:(UIApplication *)application {
// 開啟后臺處理多媒體事件
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
AVAudioSession *session=[AVAudioSession sharedInstance];
[session setActive:YES error:nil];
// 后臺播放
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
// 這樣做别威,可以在按home鍵進(jìn)入后臺后 躯舔,播放一段時間,幾分鐘吧省古。但是不能持續(xù)播放網(wǎng)絡(luò)歌曲庸毫,若需要持續(xù)播放網(wǎng)絡(luò)歌曲,還需要申請后臺任務(wù)id衫樊,具體做法是:
_backgroundTaskIdentifier=[AppDelegate backgroundPlayerID:_backgroundTaskIdentifier];
// 其中的_bgTaskId是后臺任務(wù)UIBackgroundTaskIdentifier _bgTaskId;
}
//實現(xiàn)一下backgroundPlayerID:這個方法:
+(UIBackgroundTaskIdentifier)backgroundPlayerID:(UIBackgroundTaskIdentifier)backTaskId{
//設(shè)置并激活音頻會話類別
AVAudioSession *session=[AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
[session setActive:YES error:nil];
//允許應(yīng)用程序接收遠(yuǎn)程控制
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
//設(shè)置后臺任務(wù)ID
UIBackgroundTaskIdentifier newTaskId=UIBackgroundTaskInvalid;
newTaskId=[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
if(newTaskId!=UIBackgroundTaskInvalid&&backTaskId!=UIBackgroundTaskInvalid){
[[UIApplication sharedApplication] endBackgroundTask:backTaskId];
}
return newTaskId;
}
Anroid
JPush Android SDK 是作為 Android Service運行在后臺的飒赃,從而創(chuàng)建并保持長連接,保持App在線的能力科侈。
當(dāng)開發(fā)者想要及時地推送消息到達(dá) App 時载佳,只需要調(diào)用 JPush API 推送,或者使用其他方便的智能推送工具臀栈。
APP被完全殺死的情況下蔫慧,則不會受到消息,Android GCM不在國內(nèi)開放权薯。
相關(guān)代碼參考: JPush官方Github
搭建推送服務(wù)
- 應(yīng)用外iOS采用Apple官方APNS推送姑躲,Android不支持應(yīng)用外推送;
- 應(yīng)用內(nèi)Android自定義Android Service和Server實現(xiàn)長連接盟蚣,實時收到通知黍析,App殺掉的情況則收不到通知。iOS通過應(yīng)用內(nèi)和Server建立長連接屎开,實時接收通知阐枣。
自建推送服務(wù)要對DeviceToken做維護(hù),涉及到消息分類、消息分發(fā)蔼两、消息重發(fā)等甩鳄,長連接的穩(wěn)定性、心跳機制等技術(shù)點额划,相關(guān)優(yōu)缺點如下:
- 優(yōu)點:在APP和Server建立Push機制妙啃,不局限于推送場景、可擴(kuò)展到其它服務(wù)器主動Push的業(yè)務(wù)場景俊戳,不受第三方SDK版本升級引起的BUG影響揖赴,自由度高、擴(kuò)展性強品抽。
- 缺點:需要投入比較大的人力物力储笑,對技術(shù)人員架構(gòu)能力、服務(wù)器的并發(fā)處理能力圆恤、不同網(wǎng)絡(luò)環(huán)境處理機制突倍、穩(wěn)定性維護(hù)等方面要求較高。
應(yīng)用外推送
應(yīng)用外推送目前只適用于iOS系統(tǒng)盆昙,采用官方提供的APNS羽历;Android系統(tǒng)由于GCM服務(wù)在國內(nèi)不開放,所以Android應(yīng)用在完全被殺死的情況下淡喜,接收不到通知秕磷。
APNS
- Device連接APNs服務(wù)器并攜帶設(shè)備序列號;
- 連接成功炼团,APNs經(jīng)過打包和處理產(chǎn)生device_token并返回給注冊的Device澎嚣;
- Device攜帶獲取的device_token向我們自己的應(yīng)用服務(wù)器注冊;
- 完成需要被推送的Device在APNs服務(wù)器和我們自己的應(yīng)用服務(wù)器注冊瘟芝。
推送的過程經(jīng)過如下步驟:
- 首先易桃,安裝了具有推送功能的應(yīng)用,我們的設(shè)備在有網(wǎng)絡(luò)的情況下會連接蘋果推送服務(wù)器锌俱,連接過程中晤郑,APNS會驗證device_token,連接成功后維持一個長連接贸宏;
- Provider(我們自己的服務(wù)器)收到需要被推送的消息并結(jié)合被推送設(shè)備的device_token一起打包發(fā)送給APNS服務(wù)器造寝;
- APNS服務(wù)器將推送信息推送給指定device_token的設(shè)備;
- 設(shè)備收到推送消息后通知我們的應(yīng)用程序并顯示和提示用戶(聲音吭练、彈出框)诫龙。
我們需要對圖1中1、3线脚、4點赐稽,圖2中的2叫榕、4點做相應(yīng)開發(fā)浑侥。
應(yīng)用內(nèi)推送
應(yīng)用內(nèi)推送的幾種方式:
- 客戶端不斷的查詢服務(wù)器姊舵,檢索新內(nèi)容,稱為pull或者輪詢寓落;
- 客戶端和服務(wù)器之間維持一個TCP/IP長連接括丁,服務(wù)器向客戶端push。
實時性要求比較高的應(yīng)用場景伶选,目前都采用的第二種方式進(jìn)行推送史飞,iOS和Android系統(tǒng)層面的推送APNS、GCM實際也是用的第二種方式仰税,手機設(shè)備提供一個系統(tǒng)服務(wù)和官方服務(wù)器進(jìn)行長連接构资。
協(xié)議
目前主流的即時通訊協(xié)議有XMPP、MQTT陨簇,推送服務(wù)偏向采用更輕量的MQTT協(xié)議吐绵,下面是兩種協(xié)議的區(qū)別:
- XMPP是基于可擴(kuò)展標(biāo)記語言(XML)的協(xié)議,協(xié)議成熟河绽、強大己单,多應(yīng)用于聊天軟件中,缺點是XML冗余較高耙饰、費流量纹笼、費電,用在推送上過于復(fù)雜苟跪。
- MQTT是基于“發(fā)布/訂閱”模式的消息傳輸協(xié)議廷痘,相對輕量,數(shù)據(jù)量小件已,適用于移動網(wǎng)絡(luò)笋额。
維護(hù)長連接需要心跳機制,客戶端和服務(wù)器在建立長連接之后拨齐,每隔一段時間鳞陨,客戶端會發(fā)一個心跳給服務(wù)器,服務(wù)器給客戶端一個心跳應(yīng)答瞻惋,這樣就形成客戶端服務(wù)器的一次完整的握手厦滤,這個握手是讓雙方都知道他們之間的連接是沒有斷開,客戶端是在線的歼狼。如果超過一個時間的閾值掏导,客戶端沒有收到服務(wù)器的應(yīng)答,或者服務(wù)器沒有收到客戶端的心跳羽峰,那么對客戶端來說則斷開與服務(wù)器的連接重新建立一個連接趟咆,對服務(wù)器來說只要斷開這個連接即可添瓷。
iOS MQTT代碼示例(iOS github地址、Android github地址):
1. 使用Cocoapods導(dǎo)入MQTT
platform :ios, "7.0"
pod "MQTTClient"
pod 'MQTTClient/Websocket'
pod 'SocketRocket'
// 導(dǎo)入頭文件
#import <MQTTClient/MQTTClient.h>
2. 建立連接
// 創(chuàng)建一個傳輸對象
MQTTCFSocketTransport *transport = [[MQTTCFSocketTransport alloc] init];
// IP
transport.host = TFHOST;
// 端口
transport.port = TFPORT;
// 會話
MQTTSession *session = [[MQTTSession alloc] init];
self.session = session;
session.transport = transport;
session.delegate = self;
// 設(shè)置終端ID(可以根據(jù)后臺的詳細(xì)詳情進(jìn)行設(shè)置)
session.clientId = uid;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 會話鏈接并設(shè)置超時時間
[session connectAndWaitTimeout:30];
dispatch_async(dispatch_get_main_queue(), ^{
// 訂閱主題, qosLevel是一個枚舉值,指的是消息的發(fā)布質(zhì)量
// 注意:訂閱主題不能放到子線程進(jìn)行,否則block不會回調(diào)
[session subscribeToTopic:topic atLevel:MQTTQosLevelAtMostOnce subscribeHandler:^(NSError *error, NSArray<NSNumber *> *gQoss) {
if (error) {
TFLog(@"連接失敗 = %@", error.localizedDescription);
}else{
TFLog(@"鏈接成功 = %@", gQoss);
}
}];
});
});
3. 接收消息
- (void)newMessage:(MQTTSession *)session
data:(NSData *)data
onTopic:(NSString *)topic
qos:(MQTTQosLevel)qos
retained:(BOOL)retained
mid:(unsigned int)mid {
// this is one of the delegate callbacks
}
4. 常用操作
- (void)disconnect{
if (self.session) {
[self.session disconnect];
}
}
- (void)close{
if (self.session) {
[self.session close];
}
}
- (void)closeWithDisconnect{
if (self.session) {
[self.session closeWithDisconnectHandler:^(NSError *error) {
if (error) {
TFLog(@"close error = %@", error.localizedDescription);
}else{
TFLog(@"close session");
}
}];
}
}
- (void)publishAndWaitData:(NSData *)data{
if (self.session) {
[self.session publishAndWaitData:data onTopic:topic retain:NO qos:MQTTQosLevelAtMostOnce];
}
}
- (void)publishAndWaitDic:(NSDictionary *)dic{
NSError *error = nil;
NSData *data = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:&error];
[self publishAndWaitData:data];
}
總結(jié)
iOS應(yīng)用在被殺死和在后臺的情況值纱,iOS 10.0+的版本可以實現(xiàn)動態(tài)語音播放鳞贷,ios 10.0之前的版本只能播放固定音頻文件;應(yīng)用處于前臺活躍的情況虐唠,則都能實現(xiàn)動態(tài)語音播放搀愧。
Android應(yīng)用在后臺、前臺活躍的情況疆偿,都能實現(xiàn)動態(tài)語音播放咱筛;應(yīng)用在被殺死的情況,則不能收到通知杆故,普通的文本消息通知也不會收到迅箩。