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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容