APP语音推送
标签(空格分隔): 极光推送 语音推送 APNS
目前主流的推送解决方案分为两种。
自己搭建推送平台,对服务器并发处理能力、开发人员技术能力、不同网络环境处理机制等要求较高,适用于资源丰富的团队。
采用第三方公司提供的推送服务,极光推送、百度云、信鸽等,这一方案成本相对较低。因为瑞钱包之前采用的是极光推送,下面着重讲一下极光推送对语音推送的实现方式。
极光推送
对于iOS系统,百度云、信鸽在App活跃和非活跃状态时,都是通过APNS推送;极光推送在App非活跃状态是通过APNS推送,活跃状态的情况,JPush Server会通过sdk建立长连接,直接由JPush Server发送消息到App内。
对于Android系统,只支持应用内推送,Push SDK是作为Android Service 运行在后台的,从而创建并保持长连接,保持在线的能力,接收服务器Push的消息。
iOS
从上图可以看出,JPush iOS Push 包括 2 个部分,APNs 推送,与 JPush 应用内消息。
- 红色部分是 APNs 推送,JPush 代理开发者的应用,向苹果 APNs 服务器推送。由 APNs Server 推送到 iOS 设备上。
- 蓝色部分是 JPush 应用内推送部分,App 启动时,内嵌的 JPush SDK 会开启长连接到 JPush Server,从而 JPush Server 可以推送消息到 App 里。
APNS
APP进程被杀死的情况,使用APNS推送,语音播放分两种情况:
- ios10.0+可以使用UNNotificationServiceExtension实现动态语音播放。
- ios10.0之前的系统,只能播放固定音频。
JPush应用内推送
APP进程没有被杀死的情况,可以实现动态语音播放,下面是iOS关键实现代码。
1.使用系统方法或者第三方SDK实现语音播放
AVSpeechUtterance *utterance = [AVSpeechUtterance speechUtteranceWithString:@"成功集成语音播报"];
AVSpeechSynthesizer *synth = [[AVSpeechSynthesizer alloc] init];
[synth speakUtterance:utterance];
2.APP在后台语音播放,在delelgate类didFinishLaunchingWithOptions、applicationWillResignActive方法实现以下代码
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支持接受远程控制事件
[[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键进入后台后 ,播放一段时间,几分钟吧。但是不能持续播放网络歌曲,若需要持续播放网络歌曲,还需要申请后台任务id,具体做法是:
_backgroundTaskIdentifier=[AppDelegate backgroundPlayerID:_backgroundTaskIdentifier];
// 其中的_bgTaskId是后台任务UIBackgroundTaskIdentifier _bgTaskId;
}
//实现一下backgroundPlayerID:这个方法:
+(UIBackgroundTaskIdentifier)backgroundPlayerID:(UIBackgroundTaskIdentifier)backTaskId{
//设置并激活音频会话类别
AVAudioSession *session=[AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
[session setActive:YES error:nil];
//允许应用程序接收远程控制
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
//设置后台任务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运行在后台的,从而创建并保持长连接,保持App在线的能力。
当开发者想要及时地推送消息到达 App 时,只需要调用 JPush API 推送,或者使用其他方便的智能推送工具。
APP被完全杀死的情况下,则不会受到消息,Android GCM不在国内开放。
相关代码参考: JPush官方Github
搭建推送服务
- 应用外iOS采用Apple官方APNS推送,Android不支持应用外推送;
- 应用内Android自定义Android Service和Server实现长连接,实时收到通知,App杀掉的情况则收不到通知。iOS通过应用内和Server建立长连接,实时接收通知。
自建推送服务要对DeviceToken做维护,涉及到消息分类、消息分发、消息重发等,长连接的稳定性、心跳机制等技术点,相关优缺点如下:
- 优点:在APP和Server建立Push机制,不局限于推送场景、可扩展到其它服务器主动Push的业务场景,不受第三方SDK版本升级引起的BUG影响,自由度高、扩展性强。
- 缺点:需要投入比较大的人力物力,对技术人员架构能力、服务器的并发处理能力、不同网络环境处理机制、稳定性维护等方面要求较高。
应用外推送
应用外推送目前只适用于iOS系统,采用官方提供的APNS;Android系统由于GCM服务在国内不开放,所以Android应用在完全被杀死的情况下,接收不到通知。
APNS
- Device连接APNs服务器并携带设备序列号;
- 连接成功,APNs经过打包和处理产生device_token并返回给注册的Device;
- Device携带获取的device_token向我们自己的应用服务器注册;
- 完成需要被推送的Device在APNs服务器和我们自己的应用服务器注册。
推送的过程经过如下步骤:
- 首先,安装了具有推送功能的应用,我们的设备在有网络的情况下会连接苹果推送服务器,连接过程中,APNS会验证device_token,连接成功后维持一个长连接;
- Provider(我们自己的服务器)收到需要被推送的消息并结合被推送设备的device_token一起打包发送给APNS服务器;
- APNS服务器将推送信息推送给指定device_token的设备;
- 设备收到推送消息后通知我们的应用程序并显示和提示用户(声音、弹出框)。
我们需要对图1中1、3、4点,图2中的2、4点做相应开发。
应用内推送
应用内推送的几种方式:
- 客户端不断的查询服务器,检索新内容,称为pull或者轮询;
- 客户端和服务器之间维持一个TCP/IP长连接,服务器向客户端push。
实时性要求比较高的应用场景,目前都采用的第二种方式进行推送,iOS和Android系统层面的推送APNS、GCM实际也是用的第二种方式,手机设备提供一个系统服务和官方服务器进行长连接。
协议
目前主流的即时通讯协议有XMPP、MQTT,推送服务偏向采用更轻量的MQTT协议,下面是两种协议的区别:
- XMPP是基于可扩展标记语言(XML)的协议,协议成熟、强大,多应用于聊天软件中,缺点是XML冗余较高、费流量、费电,用在推送上过于复杂。
- MQTT是基于“发布/订阅”模式的消息传输协议,相对轻量,数据量小,适用于移动网络。
维护长连接需要心跳机制,客户端和服务器在建立长连接之后,每隔一段时间,客户端会发一个心跳给服务器,服务器给客户端一个心跳应答,这样就形成客户端服务器的一次完整的握手,这个握手是让双方都知道他们之间的连接是没有断开,客户端是在线的。如果超过一个时间的阈值,客户端没有收到服务器的应答,或者服务器没有收到客户端的心跳,那么对客户端来说则断开与服务器的连接重新建立一个连接,对服务器来说只要断开这个连接即可。
iOS MQTT代码示例(iOS github地址、Android github地址):
1. 使用Cocoapods导入MQTT
platform :ios, "7.0"
pod "MQTTClient"
pod 'MQTTClient/Websocket'
pod 'SocketRocket'
// 导入头文件
#import <MQTTClient/MQTTClient.h>
2. 建立连接
// 创建一个传输对象
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;
// 设置终端ID(可以根据后台的详细详情进行设置)
session.clientId = uid;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 会话链接并设置超时时间
[session connectAndWaitTimeout:30];
dispatch_async(dispatch_get_main_queue(), ^{
// 订阅主题, qosLevel是一个枚举值,指的是消息的发布质量
// 注意:订阅主题不能放到子线程进行,否则block不会回调
[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];
}
总结
iOS应用在被杀死和在后台的情况,iOS 10.0+的版本可以实现动态语音播放,ios 10.0之前的版本只能播放固定音频文件;应用处于前台活跃的情况,则都能实现动态语音播放。
Android应用在后台、前台活跃的情况,都能实现动态语音播放;应用在被杀死的情况,则不能收到通知,普通的文本消息通知也不会收到。