android推送的方式存在几种,包括轮询,长连接等方式,由于其他的方式用户体验并不是特别好(耗电或者是消耗资源),所以现在大部分的推送都使用的是socket长连接的方式。

#集成Android的推送服务

现在的第三方可推送的平台特别多,以前用过的就是极光推送,友盟的,好像mob也有一个,具体的集成方法这里就不介绍了,需要的话请自行到对应官网去查看对应API。当然如果项目有需要的话,也完全可以自己实现推送服务,这个时候就要注意处理心跳包了(具体的请见下文)。这篇文章主要探索Android推送底层的一些处理及细节。

#长连接

##运作方式##
客户端在与服务器建立了TCP连接之后,客户端定时向服务器发送心跳包确认,有消息的时候,服务器直接通过这个已经建立好的TCP连接通知客户端。
而正所谓长连接,就是大家建立连接之后不主动断开,双方互相发送数据,发完了也不主动断开连接,之后有需要发送的数据就继续通过这个连接发送。

##TCP的三次握手##
既然总结到了TCP,就顺带总结一下TCP的三次握手和四次挥手吧。

这是TCP的三次握手,也就是通过这三次握手,让客户端与服务器建立一个连接,该连接理论上是永远不会断的,但其底层存在一个保活协议(默认为2小时),如果到了指定的时间检测到客户端死了,服务器就会关闭掉与该客户端的连接。也由于保活协议默认的时间太长(2个小时),也就诞生出了心跳包的概念,用于定时检测客户端是否还是活着的,如果死了就立刻关闭掉当前客户端的连接,节省服务器的带宽资源。

首先,客户端会发送一个包给服务器,服务器接收到了之后就会把当前服务器的一个状态码+1(用于区分,之后会讲到),之后服务器发送SYN包给客户端,此时服务器的状态改变,准备建立连接。客户端接收到服务器发过来的SYN包时,向服务器发送一个确认包,表明我知道了,当服务器接受到这个包的时候,就会检测当前的状态码(第一次+1的那个状态码)是否对应,因为有可能在第一次握手的时候存在一个超时的情况,客户端的包迟迟没有发到服务器(例如网络阻塞),而之前没有发到服务器的包却有可能在本次操作的时候发到了,所以就需要检测状态码是否对应,用于区分发过来的包到底是不是客户端第二次发来的包。最后就进入到连接建立的状态。

TCP的四次挥手
-
四次挥手是用于客户端与服务器断开连接的

1、客户端的应用简称先向其TCP发出连接释放报文段,并停止再发送数据,主动关闭TCP连接,客户端把连接释放报文段首部的种植控制为置为1,然后进入到终止等待1状态,等待服务器的确认
2、服务器收到连接释放报文段后即发出确认,之后进入到关闭等待状态。而TCP服务器进程这个时候就会通知高层的应用进程,从而让客户端到服务器这个方向的连接被释放掉,这个时候的TCP连接处于一个半关闭状态(即客户端没有数据要发送了,但是服务器如果要发送数据,客户端仍然要接受,也就是服务器到客户端的连接未关闭),客户端在收到来自服务器的确认之后,就进入终止等待2状态,等待服务器发出的连接释放报文段。
3、如果服务器已经没有了要向客户端发送的数据,其应用进程就会通知TCP释放连接,之后服务器进入最后确认状态,等待客户端的确认
4、客户端在收到服务器的连接释放报文段后,就必须对此发出确认,之后进入到时间等待状态,此时的TCP连接仍然没有被释放掉,必须要经过时间等待计时器设置的时间2MSL后,客户端才进入到关闭状态。MSL被称为最长报文段寿命,可根据工程的实际情况来进行设定(RFC建议设置为2分钟但对于现在的网络来说可能过长),而经过了2MSL(以4分钟为例)之后,就进入到了关闭状态,才能开始建立下一个新的连接。

之所以要经过2MSL的时间才能关闭是因为以下两个原因:

  • 可以保证客户端发送的最后一个报文段能够到达服务器,最后的这个报文段有可能丢失,因此服务器在没收到的情况下会超时重传一次第三步的报文,而客户端就又会收到这个报文段,之后再重传,同时重新等待2MSL
  • 用于防止上一个失效的链接请求报文段出现在本连接中。客户端在发送完最后一个报文段后,经过时间2MSL,就可以让本连接持续的时间内所产生的所有报文段都从网络中消失,这样就可以使下一个新的连接不会出现这种旧的连接请求报文段。

连接中断的情况
-
由于现在我们使用的是手机,因此我们的网络状态在很多时候都不是稳定的,就很有可能会导致连接被切断(切断后就需要再次建立连接),比如说NAT设备超时(路由器就是一个NAT设备),网络状态切换,DHCP的租期等等。

心跳包
-
这里推荐一篇文章,Android微信智能心跳方案

心跳包,顾名思义,就是用于判断当前的客户端是否还有心跳(也就是是否还活着),如果没有心跳了,服务器就会关闭掉这个连接,用于节省带宽。

在一个正常的情况下,服务器会面对很多个连接。而一旦这个连接建立之后,是不会主动断开的,就会一直占据到服务器的资源,而很有可能过了一段时间之后有的连接就已经失效了,就可以释放掉该连接,而之后如果客户端需要再连接的时候重新建立连接就好了。

而这里有个点是很重要的,如果客户端心跳间隔是固定的,那么服务器就会在连接闲置超过这个时间还没收到心跳时,可以认为对方掉线,关闭连接。如果客户端心跳会动态改变,例如在微信的心跳方案中的话,就应该在服务器设置一个最大值,超过最大值如果还没收到就认为对方掉线。

同时服务器通过TCP连接主动给客户端发消息出现写超时的时候,也可以直接认为对方掉线了。

而之所以在这一小段中提到Android微信智能心跳方案,是因为对于整个心跳包来说,理论是比较简单的,而重要的是这个心跳时间间隔的设定。
发送心跳包势必要先唤醒设备, 然后才能发送, 如果唤醒设备过于频繁, 或者直接导致设备无法休眠, 会大量消耗电量, 而且移动网络下进行网络通信, 比在wifi下耗电得多. 所以这个心跳包的时间间隔应该尽量的长, 最理想的情况就是根本没有NAT超时, 这也就是网上常说的长连接, 慢心跳.

而超时的这个情况肯定是不能避免的了,这个时候就可以参看一下上面的那篇心跳方案,它是关于如何让心跳间隔逼近NAT超时的间隔,同时自动适应NAT超时间隔的变化。

设备休眠
-

首先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进入休眠状态。但如果不领会Android设计者的意图而滥用Wake Lock API,为了自身程序在后台的正常工作而长时间阻止AP进入休眠状态,就会成为待机电池杀手。比如前段时间的某应用,比如现在仍然干着这事的某应用。

网上有说使用AlarmManager,因为AlarmManager 是Android 系统封装的用于管理 RTC 的模块,RTC (Real Time Clock) 是一个独立的硬件时钟,可以在 CPU 休眠时正常运行,在预设的时间到达时,通过中断唤醒 CPU。

尤其需要注意的一点就是:如果不进行特别的设置,Android会在一定时间后屏幕变暗,在屏幕变暗后约几分钟,CPU也会休眠,大多数的程序都会停止运行,从而节省电量。而CPU休眠的话就存在一个很严重的问题了,很有可能你设置的一些程序会由于CPU休眠而无法执行,例如Timer和TimerTask,这个时候就可以使用系统的AlarmService来执行轮询。因为虽然系统让机器休眠,节省电量,但并不是完全的关机,系统有一部分优先级很高的程序还是在执行的,比如闹钟,利用AlarmService可以定时启动自己的程序,让cpu启动,执行完毕再休眠。

而对于Socket来说,同样也存在一个很严重的问题,当屏幕关了几分钟之后连接会被断开,但实际上网络又是连通的,这是因为CPU休眠了,此时就可以设置一个PARTIAL_WAKE_LOCK来保持CPU不休眠。(这是从网上找的一种解决方案,可能会有更优)

最后再补充一句,小米的MIUI系统对于原生的系统改动太大,很多地方可能需要根据该系统的情况来设定指定的方案,说多了都是泪~

参考
-
android设备休眠
Android微信智能心跳方案
Android推送技术研究
Android实现推送方式解决方案