参考:
Android推送技术研究
Android实现推送方式解决方案
Android微信智能心跳方案
Android休眠问题探讨

Android推送服务的几种实现方式

一、推送方式基础知识:

  在移动互联网时代以前的手机,如果有事情发生需要通知用户,则会有一个窗口弹出,将告诉用户正在发生什么事情。可能是未接电话的提示,日历的提醒,或是一封新的彩信。推送功能最早是被用于Email中,用来提示我们新的信息。由于时代的发展和移动互联网的热潮,推送功能更加地普及,已经不再仅仅用在推送邮件了,更多地用在我们的APP中了。

  当我们开发需要和服务器交互的应用程序时,基本上都需要获取服务器端的数据,比如《地震应急通》就需要及时获取服务器上最新的地震信息。
  要获取服务器上不定时更新的信息,一般来说有两种方法:

  • 第一种是客户端使用Pull(拉)的方式,就是隔一段时间就去服务器上获取一下信息,看是否有更新的信息出现。

  • 第二种就是 服务器使用Push(推送)的方式,当服务器端有新信息了,则把最新的信息Push到客户端上。这样,客户端就能自动的接收到消息。 

      虽然Pull和Push两种方式都能实现获取服务器端更新信息的功能,但是明显来说Push方式比Pull方式更优越。因为Pull方式更费客户端的网络流量,更主要的是费电量,还需要我们的程序不停地去监测服务端的变化。  

在开发Android和iPhone应用程序时,我们往往需要从服务器不定的向手机客户端即时推送各种通知消息。我们只需要在Android或IPhone的通知栏处向下一拉,就展开了Notification Panel,可以集中一览各种各样通知消息。目前IOS平台上已经有了比较简单的和完美的推送通知解决方案,我会在以后详细介绍IPhone中的解决方案,可是Android平台上实现起来却相对比较麻烦。

二、三种常见的解决方案实现原理:

  1)轮询(Pull)方式:应用程序应当阶段性的与服务器进行连接并查询是否有新的消息到达,你必须自己实现与服务器之间的通信,例如消息排队等。而且你还要考虑轮询的频率,如果太慢可能导致某些消息的延迟,如果太快,则会大量消耗网络带宽和电池。
  
  但对于即时通讯产品来说, 这种方案完全不能用. 假设即时通讯软件在网络畅通的情况下发送的消息要求对方10s内就能收到, 如果用轮询, 那么客户端要每隔5s连一次服务器, 如果在移动端, 手机的电量和流量很快就会被消耗殆尽.

  2)SMS(Push)方式:在Android平台上,你可以通过拦截SMS消息并且解析消息内容来了解服务器的意图,并获取其显示内容进行处理。这个方案的好处是,可以实现完全的实时操作。但是问题是这个方案的成本相对比较高,我们需要向运营商缴纳相应的费用。

  3)长连接(Push)方式:应用程序和服务器保持一个长连接,服务器的消息可以直接通过这个链接push到应用程序。这个方案可以解决由轮询带来的性能问题,但是还是会消耗手机的电池。

  Android操作系统允许在低内存情况下杀死系统服务,所以我们的推送通知服务很有可能就被操作系统Kill掉了。 轮询(Pull)方式和SMS(Push)方式这两个方案也存在明显的不足。至于持久连接(Push)方案也有不足,不过我们可以通过良好的设计来弥补,以便于让该方案可以有效的工作。毕竟,我们要知道GMail,GTalk以及GoogleVoice都可以实现实时更新的。

在理论上使用SMS(Push)方式是最好的方法,这种方法手机客户端最省电(详情查看这里),但是碍于运营商高昂的费用,所以才去长连接(Push)的方式

三、长连接(Push)的三种方法:

3.1、GCM云端推送功能。

  在Android手机平台上,Google提供了GCM服务,2016 I/O大会发布了Firebase Cloud Messaging (FCM) 用来取代GCM。

  Google Cloud Messaging (GCM)是一个用来帮助开发者从服务器向Android应用程序发送数据的服务。GCM维护了一个与设备之间的长连接,该服务提供了一个简单的、轻量级的机制,允许服务器可以通知移动应用程序直接与服务器进行通信,以便于从服务器获取应用程序更新和用户数据。GCM服务负责处理诸如消息排队等事务并向运行于目标设备上的应用程序分发这些消息。

图片来自:http://blog.nkdroidsolutions.com/android-push-notification-with-image/

GCM的使用流程:

Step 1: Android device send sender id to GCM server for registering device

Step 2: GCM Server generate registration id for android device

Step 3: Now our device send registration id to server through API

Step 4: Server save registration id in database for future use

Step 5: For requesting push, our server send request with registration id to GCM server

Step 6: GCM server send push notification to android device based on registration id.

GCM特点

a)Android2.2以下的手机不支持GCM,2.2到3.0需要安装Google Store并设置Google帐号,4.04及以上版本不需要设置帐号也能支持。
b)GCM只传递数据(可以传递小于4kb的数据),对这些数据的处理可以全部由开发者控制。
c)Android应用不需要运行就可以接收消息(通过Android广播)。
d)GCM不保证发送的消息的顺序,也不保证消息一定能够推送到手机。

GCM心跳策略以及存在的问题

a)用心跳保活长连接,心跳间隔为WIFI下15分钟,数据网络下28分钟。
b)Google可以改变所有Android设备的心跳间隔值(目前还未改变过)。
c)GCM由于心跳间隔固定,并且较长,所以在NAT aging-time设置较小的网络(如联通2G,或有些WIFI环境下)会导致TCP长连接在下一次心跳前被网关释放。造成Push延迟接收。

GCM的可用性及稳定性

目前测试发现GCM在国内可用性不高,原因有:
a) Android很多被手机厂商定制化,厂商可能会去掉GCM服务。
b) Android2.2到3.0之间需要安装Google Store并设置Google帐号。
c)由于国内2G和移动3G的NAT超时时间都小于GCM心跳时间(28分钟),TCP长连接必然无法保活,每次都要等28分钟心跳失败重连后才能收到Push。
d)某些运营商可能限制了5228端口,移动3G/2G下,发现几乎无法连接上GCM服务器,也就无法获得GCM通知,WhatsApp放后台10分钟后,经常很长时间都收不到Push消息。

在美国3G网络下抓包的24小时,GCM的连接极其稳定,24小时内GCM长连接未曾断过,
在台湾3G网络下抓包14个小时,GCM连接也只断过一次。
在中国电信3G下抓包,大部分时间GCM连接都比较稳定,只会因为偶尔的DHCP造成断连现象,由于频率很低(平均数小时才发生一次),对Push体验的影响不大。

GCM Server类型

GCM提供两种Server模型:
a)HTTP Server : 使用同步接口发送HTTP请求,一次请求可以发给最多1000个设备。
b)XMPP Server :使用异步接口发送请求,只支持对单个设备(或同一个用户的多个关联设备发送),发送请求并发数须小于1000,支持设备到云端Server发送数据。需要Google将我们的发送Server加入白名单。

3.2、自己建立长连接

见下面 第四点

3.3、使用第三方推送平台

第三方平台有商用的也有免费的,我们可以根据实现情况使用。
  
推送平台:极光、信鸽、一推 等

3.4 XMPP, MQTT等不算推送技术

XMPP是网络即时通信协议, MQTT是IBM开发的一个即时通讯协议。工作在应用层,并不负责底层的连接是如何保持的。并不关心是使用TCP连接的还是UDP连接的。

也可以使用HTTP协议进行数据传输。

如下图

Android推送、智能心跳解决方案、手机休眠对心跳的影响_第1张图片

更多关于XMPP查看这里:http://www.cnblogs.com/hanyonglu/archive/2012/03/04/2378956.html

更多关于MQTT查看这里:
https://github.com/mcxiaoke/mqtt

四、自己创建长连接

4.1、什么是长连接

先说短连接, 短连接是通讯双方有数据交互时就建立一个连接, 数据发送完成后,则断开此连接.

长连接就是大家建立连接之后, 不主动断开. 双方互相发送数据, 发完了也不主动断开连接, 之后有需要发送的数据就继续通过这个连接发送.

TCP连接在默认的情况下就是所谓的长连接, 也就是说连接双方都不主动关闭连接, 这个连接就应该一直存在。TCP连接其实是一种虚拟连接,连接的状态信息是在两端维持的。

4.2 影响TCP连接寿命的因素

在Android下,不管是GCM,还是微信,都是通过TCP长连接来进行Push消息的,TCP长连接存活,消息Push就及时,所以要对影响TCP连接寿命的因素进行研究。

4.2.1 NAT超时

大部分移动无线网络运营商都在链路一段时间没有数据通讯时,会淘汰 NAT 表中的对应项,造成链路中断(NAT超时的更多描述见附录A)。NAT超时是影响TCP连接寿命的一个重要因素(尤其是国内),所以客户端自动测算NAT超时时间,来动态调整心跳间隔,是一个重要的优化点。

拓展阅读:
微信收费事件背后被广泛忽略的技术细节
微信的大规模使用真的会过多占用信令,影响通讯稳定吗?

4.2.2、DHCP的租期

目前测试发现安卓系统对DHCP的处理有Bug,DHCP租期到了不会主动续约并且会继续使用过期IP,这个问题会造成TCP长连接偶然的断连。

关于DHCP租期的问题,原文的发布时Android 6.0还没有发布,不知道Android 6.0 还有没有这个bug(待测试)

4.2.3、网络状态变化

手机网络和WIFI网络切换、网络断开和连上等情况有网络状态的变化,也会使长连接变为无效连接,需要监听响应的网络状态变化事件,重新建立Push长连接。

4.3、心跳包的作用

https://www.zhihu.com/question/35013918

我认为心跳包的真正作用有两个:

4.3.1. 检测客户端和服务端的链接是否可用

你ssh连上服务器的情况下,拔掉网线,你任然检测不到网络断开了(没有FIN),这时候把网线插回去,敲两下键盘,终端一样有反应,链接还活着。因为tcp的链接是否有效,依赖链接两端的状态确定,你在你机器上拔掉网线,你是知道这件事情的,但是中间网络设备拔掉网线,或者出现什么问题,你完全无法得知链接是否还有效,依赖tcp本身的keepalive机制需要半个小时以上才能检测得出来。

4.3.2. 防止客户端和服务端之间的NAT表因超时而被清理

明确一点, TCP长连接本质上不需要心跳包来维持, 大家可以试一试, 让两台电脑连上同一个wifi, 然后让其中一台做服务器, 另一台用一个普通的没有设置KeepAlive的Socket连上服务器, 只要两台电脑别断网, 路由器也别断电, DHCP正常续租, 就这么放着, 过几个小时再用其中一台电脑通过之前建立的TCP连接给另一台发消息, 另一台肯定能收到.

那为什么要有心跳包呢? 其实主要是为了防止上面提到的NAT超时, 既然一些NAT设备判断是否淘汰NAT映射的依据是一定时间没有数据, 那么客户端就主动发一个数据,用来延续NAT映射的时长。

4.4、心跳范围选择

1、前后台区分处理:
为了保证微信收消息及时性的体验,当微信处于前台活跃状态时,使用固定心跳。
微信进入后台(或者前台关屏)时,先用几次最小心跳维持长链接。然后进入后台自适应心跳计算。这样做的目的是尽量选择用户不活跃的时间段,来减少心跳计算可能产生的消息不及时收取影响。

2、后台自适应心跳选择区间:
可根据自身产品的特点选择合适的心跳范围。

4.5 智能心跳算法描述

按网络类型区分计算:
因为每个网络的NAT时间可能不一致。所以需要区分计算,数据网络按subType做关键字,WIFI按WIFI名做关键字。
对稳定的网络,因为NAT老化(超时)时间的存在,在自适应计算态的时候,暂设计以下步骤在当前心跳区间逼近出最大可用的心跳。

a) 变量说明:

[MinHeart,MaxHeart]——心跳可选区间。
successHeart——当前成功心跳,初始为MinHeart
curHeart——当前心跳初始值为successHeart
heartStep——心跳增加步长
successStep——稳定期后的探测步长

b) 最大值探测步骤:

Android推送、智能心跳解决方案、手机休眠对心跳的影响_第2张图片

图4-1 自适应心跳计算流程

自适应心跳计算流程如图4-1所示,经过该流程,会找到必然使心跳失败的curHeart(或者MaxHeart),为了保险起见,我们选择比前一个成功值稍微小一点的值作为后台稳定期的心跳间隔。
影响手机网络测试的因素太多,为了尽量保证测试结果的可靠性,我们使用延迟心跳测试法:在我们重新建立TCP连接后,先使用 短心跳连续成功三次,我们才认为网络相对稳定,可以使用curHeart进行一次心跳测试。

图4-2显示了一次有效心跳测试过程。

图4-3显示了在没有达到稳定网络环境时,我们会一直使用固定短心跳直到满足三次连续短心跳成功。

原文中缺少了图4-2和图4-3,不过通过描述也能理解

使用延迟心跳测试的好处是,可以剔除偶然失败,和网络变化较大的情况(如地铁),使测试结果相对可靠(五次延迟测试确定结论)。同时在网络波动较大的情况,使用短心跳,保证收取消息相对及时。

c) 运行时的动态调整策略(已经按测算心跳稳定值后)

NAT超时值算出来后,在维持心跳的过程中的策略

  • 无网络、网络时好时坏、偶然失败、NAT超时变小:在后台稳定期发生心跳发生失败后,我们使用延迟心跳测试法测试五次。如果有一次成功,则保持当前心跳值不变;如果五次测试全失败,重新计算合理心跳值。该过程如图4-4所示,有一点需要注意,每个新建的长连接需要先用短心跳成功维持3次后才用successHeart进行心跳。

Android推送、智能心跳解决方案、手机休眠对心跳的影响_第3张图片
图4-4

  • NAT超时变大:以周为周期,每周三将后台稳定态调至自适应计算态,使用心跳延迟法往后探测心跳间隔。
  • successHeart是NAT超时临界值:因为我们现在选择的是一个比successHeart稍小的值作为稳定值,所以在计算过程中可以避开临界值。当运营商在我们后台稳定期将NAT超时调整为我们当前计算值,那么由于我们每周会去向下探索,所以下一周探测时也可以及时调整正确。

4.6 冗余Sync和心跳

在用户的一些主动操作以及联网状态改变时,增加冗余Sync和心跳,确保及时收到消息。
1、当用户点亮屏幕的时候,做一次心跳。
2、当微信切换到前台时,做一次Sync。
3、联网时重建信令TCP,做一次Sync

4.7、微信使用的推送方案:

微信Push的优化主要有几个优化点:
a) 公共Push通道
b) 使用GCM Push作为辅助通道
c) 自适应心跳间隔优化

公共Push通道:
由于GCM在国内的可靠性很低,现在国内Android上的Push基本上是各自为政,很多软件都自己实现Push。导致手机被经常性的唤醒,耗电耗流量严重。
市面上已经有很多第三方的公共推送服务,大家可以选择一个适合自己应用的推送服务。腾讯也有信鸽和维纳斯组件,大家在选择方案的时候可以对比下。
最终因为我们国内外使用一套方案,并且是辅助公道,所以我们选择使用GCM。

使用GCM Push作为辅助通道:
当前使用GCM的成本不大,可以使用GCM作为辅助通道来增加新消息的及时性。
使用GCM作为辅助通道,在支持GCM的设备上微信上传自己的注册GCM ID给微信Server。
微信Server在发现长连接失效的情况下,可以使用GCM 作为辅助通道通知客户端有新消息,客户端收到push通知后做一次sync。
只利用GCM来激活微信,不传递消息的具体数据,要控制给同一设备发送GCM通知的时间间隔(如五分钟)。

5、Android休眠带来的影响

Android设备上解决耗电的一个策略就是休眠,手机在锁屏之后一段时间手机就会休眠,那个时候,无论是屏幕,CPU还是其他模块都会停止工作,这样导致了2个问题:

1.一些通讯软件的心跳包中断,导致掉线

2.若采用UDP连接的情况下,服务器过来的数据包不一定实时。

我们来讲讲如何解决以上的两个问题。

Android手机有两个处理器

  • Application Processor(AP)
  • Baseband Processor(BP)。

AP是ARM架构的处理处理器,用于运行Linux+Android体系;
BP用于运行及时操纵体系(RTOS),通信协议栈运行于BP的RTOS之上。非通话时候,BP的能耗基本在5mA以下,而AP只要处于非休眠状况,能耗至少在50mA以上,履行图形运算时会更高。别的LCD工作时功耗在100mA左右,WIFI也在100mA左右。一般手机待机时,AP、LCD、WIFI均进入休眠状况,这时Android中应用法度的代码也会停止运行。

Android为了确保一些关键代码的正确运行,供给了Wake Lock的API,使得应用有权限经由过程代码阻拦AP进入休眠状况(iOS、WP7都没这种器材)。若是不懂得Android设计者的意图而滥用Wake Lock API,为了自身代码在后台的正常工作而长时候阻拦AP进入休眠状况,结果就相当严重了,手机的电量就犀利哗啦的被用完了。

如果AP休眠了,手机不是接收不到消息了吗?

完全不用担心,通信协议栈运行于BP,一旦收到数据包,BP会将AP唤醒,唤醒的时间足够AP完成对BP收到协议的处理,但是有一点需要大家注意的是,假如你处理协议包的时间很长的话,那么请加上wakelock,完成之后再释放掉。

如果AP休眠了,程序如何执行向服务器发送心跳包的逻辑

你显然不能靠AP来做心跳计时. Android提供的Alarm Manager就是来解决这个问题的. Alarm应该是BP计时(或其它某个带石英钟的芯片,不太确定,但绝对不是AP), 触发时唤醒AP执行程序代码.
那么Wake Lock API有啥用呢? 比如心跳包从请求到应答, 比如断线重连重新登陆这些关键逻辑的执行过程, 就需要Wake Lock来保护. 而一旦一个关键逻辑执行成功, 就应该立即释放掉Wake Lock了. 两次心跳请求间隔5到10分钟, 基本不会怎么耗电. 除非网络不稳定. 频繁断线重连, 那种情况办法不多.

上面所说的通信协议, 我猜应该是无线资源控制协议(Radio Resource Control), RRC应该工作在OSI参考模型中的第三层网络层, 而TCP, UDP工作在第四层传输层, 上文说的BP, 应该就是手机中的基带, 也有叫Radio的,
Google在Optimizing Downloads for Efficient Network Access 中提到了一个叫Radio State Machine的东西

TCP长连接是可以将AP唤醒,但是UDP数据包并不会唤醒。

具体的原因可能是因为底层对于TCP长连接的数据过来,会产生AP中断来唤醒AP,但是UDP不会。。。这么做也是有道理的,因为TCP长链接是客户端自身验证过的服务器,也就是数据来源可靠。。。若UDP也会唤醒,那完全可以进行UDP数据包工具,这样一来,被攻击的手机至少耗电量将会大幅度上升。

附录A——NAT超时介绍

因为 IP v4 的 IP 量有限,运营商分配给手机终端的 IP 是运营商内网的 IP,手机要连接 Internet,就需要通过运营商的网关做一个网络地址转换(Network Address Translation,NAT)。简单的说运营商的网关需要维护一个外网 IP、端口到内网 IP、端口的对应关系,以确保内网的手机可以跟 Internet 的服务器通讯。

大部分移动无线网络运营商都在链路一段时间没有数据通讯时,会淘汰 NAT 表中的对应项,造成链路中断。下表列出一些已测试过的网络的NAT超时时间(更多数据由于测试条件所限没有测到):

地区/网络 NAT超时时间
中国移动3G和2G 5分钟
中国联通2G 5分钟
中国电信3G 大于28分钟
美国3G 大于28分钟
台湾3G 大于28分钟

GGSN(Gateway GPRS Support Node 网关GPRS支持结点)模块就实现了NAT功能。
因为大部分移动无线网络运营商都是为了减少网关的NAT映射表的负荷,所以如果发现链路中有一段时间没有数据通讯时,会删除其对应表,造成链路中断。

长连接心跳间隔必须要小于NAT超时时间(aging-time),如果超过aging-time不做心跳,TCP长连接链路就会中断,Server就无法发送Push给手机,只能等到客户端下次心跳失败后,重建连接才能取到消息。

附录B——安卓DHCP的租期(lease time)问题

目前测试发现安卓系统对DHCP的处理有Bug:
1、 DHCP租期到了不会主动续约并且会继续使用过期IP,详细描述见http://www.net.princeton.edu/android/android-stops-renewing-lease-keeps-using-IP-address-11236.html。这个问题导致的问题表象是,在超过租期的某个时间点(没有规律)会导致IP过期,老的TCP连接不能正常收发数据。并且系统没有网络变化事件,只有等应用判断主动建立新的TCP连接才引起安卓设备重新向DHCP Server申请IP租用。
2、 未到租期的一半时间,安卓设备重新向DHCP Server申请IP租用。从目前测试结果来看,这种现象恢复的比较快。
3、 移动2G/3G,联通2G没有抓到DHCP。
4、 美国3G下抓取24小时,没有抓到DHCP。

更多相关文章

  1. Android 网络请求库Retrofit简单使用
  2. Android 网络操作常用的两个类
  3. Android使用HttpURLConnection访问网络
  4. android关闭或开启移动网络数据(关闭后,设备不可以上网,但可以打电
  5. Android开发之与服务器(jsp)发送、接受JSON数据
  6. Android中网络通信方式的简单汇总(HttpURLConnection、HttpClient

随机推荐

  1. Android引导程序开发要点
  2. 【资源汇总分享】Android开发资源汇总之
  3. Android activity-alias 多入口配置
  4. android logcat用法总结
  5. 【Android】实现登录、注册、数据库操作(
  6. android中的ANR
  7. 简单学习Android TextView
  8. android软键盘的隐藏以及Edittext的焦点
  9. 状态开关按钮ToggleButton和开关switch的
  10. LeadTools Android 入门教学——运行第一