APP语音推送

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

  1. Device连接APNs服务器并携带设备序列号;
  2. 连接成功,APNs经过打包和处理产生device_token并返回给注册的Device;
  3. Device携带获取的device_token向我们自己的应用服务器注册;
  4. 完成需要被推送的Device在APNs服务器和我们自己的应用服务器注册。

推送的过程经过如下步骤:

  1. 首先,安装了具有推送功能的应用,我们的设备在有网络的情况下会连接苹果推送服务器,连接过程中,APNS会验证device_token,连接成功后维持一个长连接;
  2. Provider(我们自己的服务器)收到需要被推送的消息并结合被推送设备的device_token一起打包发送给APNS服务器;
  3. APNS服务器将推送信息推送给指定device_token的设备;
  4. 设备收到推送消息后通知我们的应用程序并显示和提示用户(声音、弹出框)。

我们需要对图1中1、3、4点,图2中的2、4点做相应开发。


应用内推送

应用内推送的几种方式:

  1. 客户端不断的查询服务器,检索新内容,称为pull或者轮询;
  2. 客户端和服务器之间维持一个TCP/IP长连接,服务器向客户端push。

实时性要求比较高的应用场景,目前都采用的第二种方式进行推送,iOS和Android系统层面的推送APNS、GCM实际也是用的第二种方式,手机设备提供一个系统服务和官方服务器进行长连接。

协议

目前主流的即时通讯协议有XMPP、MQTT,推送服务偏向采用更轻量的MQTT协议,下面是两种协议的区别:

  1. XMPP是基于可扩展标记语言(XML)的协议,协议成熟、强大,多应用于聊天软件中,缺点是XML冗余较高、费流量、费电,用在推送上过于复杂。
  2. MQTT是基于“发布/订阅”模式的消息传输协议,相对轻量,数据量小,适用于移动网络。

维护长连接需要心跳机制,客户端和服务器在建立长连接之后,每隔一段时间,客户端会发一个心跳给服务器,服务器给客户端一个心跳应答,这样就形成客户端服务器的一次完整的握手,这个握手是让双方都知道他们之间的连接是没有断开,客户端是在线的。如果超过一个时间的阈值,客户端没有收到服务器的应答,或者服务器没有收到客户端的心跳,那么对客户端来说则断开与服务器的连接重新建立一个连接,对服务器来说只要断开这个连接即可。

iOS MQTT代码示例(iOS github地址、Android github地址):

1. 使用Cocoapods导入MQTT
platform :ios, "7.0"

pod "MQTTClient"
pod 'MQTTClient/Websocket'
pod 'SocketRocket'

// 导入头文件
#import 
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 *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应用在后台、前台活跃的情况,都能实现动态语音播放;应用在被杀死的情况,则不能收到通知,普通的文本消息通知也不会收到。

你可能感兴趣的:(APP语音推送)