Android推送技术研究

观书散遗帙,探古穷至妙。这篇文章主要讲述Android推送技术研究相关的知识,希望能为你提供帮助。
前言近期研究android推送的实现, 研究了两天一夜, 有了一点收获, 写下来既为了分享, 也为了吐槽. 须要说明的是有些东西偏底层硬件和通信行业, 我对这些一窍不通, 仅仅能说说自己的理解.
为什么要研究Android推送技术?
主要还是毕业设计要做一个即时通信app, 我是不喜欢做什么社交app的, 也就象牙塔里的人想得出来, 说实话有这功夫还不如钻研一个小技术点, 把一个点研究透彻, 比搞个大而全, 还没用的东西好得多, 只是谁叫咱们是普通人, 没得选呢.
Android推送服务的几种实现方式现实生活中, 推送服务就像订杂志一样, 仅仅要留下你的地址, 杂志就能如期送到你手里, 能够觉得每一个人都有唯一的一个地址, 但在眼下的网络上, 这是办不到的, 由于不是每一个人都有一个唯一的地址, server想要给我们推送一条消息, 必须知道我们的地址, 但server不知道我们在哪.

说到推送服务, 我所知道的实现方案有例如以下几种:
轮询
client定期询问server有没有新的消息, 这样server不用管client的地址是什么, client来问, 直接告诉它即可.
这样的方案最简单, 对于一些不追求实时性的client来说, 非常适合, 仅仅须要把时间间隔设定成几个小时取一次, 就能非常方便的解决这个问题.
但对于即时通讯产品来说, 这样的方案全然不能用. 假设即时通讯软件在网络畅通的情况下发送的消息要求对方10s内就能收到, 假设用轮询, 那么client要每隔5s连一次server, 假设在移动端, 手机的电量和流量非常快就会被消耗殆尽.
SMS通知
这样的方案在移动端是有可能的, 让client拦截手机短信, server在有新消息时给用户的手机号发一条特殊的短信, client拦截短信后发现是正常短信就放行, 假设是特殊短信就连接server取消息.
运营商不会配合, 用户也不会放心, 这方案普通公司玩不起.
长连接
这大概是眼下情况下最佳的方案了, client主动和server建立TCP长连接之后, client定期向server发送心跳包, 有消息的时候, server直接通过这个已经建立好的TCP连接通知client.
XMPP, MQTT等不算推送技术
在网上搜索资料的时候, 常常看见XMPP协议实现的Android推送和MQTT协议实现的Android推送, 我个人觉得这两种说法都怪怪的, XMPP和MQTT二者都是协议, 虽然我不清楚严格来讲这俩协议工作在哪一层, 可是绝对是在传输层之上的, 姑且觉得他俩在TCP/IP五层模型的应用层吧, 闭口不提传输层的实现, 而是扯应用层, 这样的说法真是令我费解, 所以我个人觉得XMPP, MQTT等等不算推送技术.
关于这个XMPP, 我想非常多人都是參考Openfire和Smack那套东西, 我一年前尝试用aSmack和Openfire做IM, 只是那个时候什么都不懂, 做的东西非常烂, 唯一懂的就是Openfire这东西相当老了, 我看有一些开源的推送解决方式都是在这套东西的基础上改的, 想想这工作量, 挺可怕的.
细说TCP长连接与心跳长连接方案乍一听怪怪的, 什么是长连接? 定时发送心跳, 这和轮询有什么差别?
心跳是干什么的? 相同是定期和server沟通, 为什么长连接就比轮询更加优秀? 手机休眠了TCP连接不会断掉吗?
这是我在刚開始研究推送技术的时候的问题, 尽管有些还是没有非常准确的答案, 但了解的大概能够分享一下, 有什么错误欢迎指出.
什么是长连接
先说短连接, 短连接是通讯两方有数据交互时就建立一个连接, 数据发送完毕后。则断开此连接.

Android推送技术研究

文章图片


persistent connection

长连接就是大家建立连接之后, 不主动断开. 两方互相发送数据, 发完了也不主动断开连接, 之后有须要发送的数据就继续通过这个连接发送.
TCP连接在默认的情况下就是所谓的长连接, 也就是说连接两方都不主动关闭连接, 这个连接就应该一直存在.
可是网络中的情况是复杂的, 这个连接可能会被切断. 比方client到server的链路由于故障断了, 或者server宕机了, 或者是你家网线被人剪了, 这些都是一些莫名其妙的导致连接被切断的因素, 还有几种比較特殊的:
NAT超时由于IPv4地址不足, 或者我们想通过无线路由器上网, 我们的设备可能会处在一个NAT设备的后面, 生活中最常见的NAT设备是家用路由器.
NAT设备会在IP封包通过设备时改动源/目的IP地址. 对于家用路由器来说, 使用的是网络地址port转换(NAPT), 它不仅改IP, 还改动TCP和UDP协议的port号, 这样就能让内网中的设备共用同一个外网IP. 举个样例, NAPT维护一个类似下表的NAT表
内网地址 外网地址
192.168.0.2:5566 120.132.92.21:9200
192.168.0.3:7788 120.132.92.21:9201
192.168.0.3:8888 120.132.92.21:9202
NAT设备会依据NAT表对出去和进来的数据做改动, 比方将192.168.0.3:8888发出去的封包改成120.132.92.21:9202, 外部就觉得他们是在和120.132.92.21:9202通信. 同一时候NAT设备会将120.132.92.21:9202收到的封包的IP和port改成192.168.0.3:8888, 再发给内网的主机, 这样内部和外部就能双向通信了, 但假设当中192.168.0.3:8888 == 120.132.92.21:9202这一映射由于某些原因被NAT设备淘汰了, 那么外部设备就无法直接与192.168.0.3:8888通信了.
我们的设备常常是处在NAT设备的后面, 比方在大学里的校园网, 查一下自己分配到的IP, 事实上是内网IP, 表明我们在NAT设备后面, 假设我们在寝室再接个路由器, 那么我们发出的数据包会多经过一次NAT.
国内移动无线网络运营商在链路上一段时间内没有数据通讯后, 会淘汰NAT表中的相应项, 造成链路中断.
网络状态切换手机网络和WIFI网络切换, 网络断开和连上等情况, 也会使长连接断开. 这里原因可能比較多, 但结果无非就是IP变了, 或者被系统通知连接断了.
DHCP的租期
眼下測试发现安卓系统对DHCP的处理有Bug, DHCP租期到了不会主动续约而且会继续使用过期IP, 这个问题会造成TCP长连接偶然的断连.
引自Android微信智能心跳方案
心跳包的作用
网上非常多文章介绍长连接的时候都说:
由于是长连接, 所以须要定期发送心跳包.
心跳包是用来通知serverclient当前状态.
提出这些说法的人事实上自己也是一知半解. 这些说法事实上都对, 可是没有答到点上. 就好像别人问: "你为什么要去食堂"?
这人回答: "检查自己还能不能找到食堂". 这个答案说不上错了, 可是事实上这人是去食堂吃饭的, 证明自己认得路仅仅是个附赠品.
明白一点, TCP长连接本质上不须要心跳包来维持, 大家能够试一试, 让两台电脑连上同一个wifi, 然后让当中一台做server, 还有一台用一个普通的没有设置KeepAlive的Socket连上server, 仅仅要两台电脑别断网, 路由器也别断电, DHCP正常续租, 就这么放着, 过几个小时再用当中一台电脑通过之前建立的TCP连接给还有一台发消息, 还有一台肯定能收到.
那为什么要有心跳包呢?
事实上主要是为了防止上面提到的NAT超时, 既然一些NAT设备推断是否淘汰NAT映射的根据是一定时间没有数据, 那么client就主动发一个数据.
当然, 假设不过为了防止NAT超时, 能够让server来发送心跳包给client, 不过这样做有个弊病就是, 万一连接断了, server就再也联系不上client了. 所以心跳包必须由client发送, client发现连接断了, 还能够尝试重连server.
所以心跳包的主要作用是防止NAT超时, 其次是探測连接是否断开.
链路断开, 没有写操作的TCP连接是感知不到的, 除非这个时候发送数据给server, 造成写超时, 否则TCP连接不会知道断开了. 主动kill掉一方的进程, 还有一方会关闭TCP连接, 是系统代进程给server发的FIN. TCP连接就是这样, 仅仅有明白的收到对方发来的关闭连接的消息(收到RST也会关闭, 大家都懂), 或者自己意识到发生了写超时, 否则它觉得连接还存在.
心跳包的时间间隔
既然心跳包的主要作用是防止NAT超时, 那么这个间隔就大有文章了.
发送心跳包势必要先唤醒设备, 然后才干发送, 假设唤醒设备过于频繁, 或者直接导致设备无法休眠, 会大量消耗电量, 并且移动网络下进行网络通信, 比在wifi下耗电得多. 所以这个心跳包的时间间隔应该尽量的长, 最理想的情况就是根本没有NAT超时, 比方刚才我说的两台在同一个wifi下的电脑, 全然不须要心跳包. 这也就是网上常说的长连接, 慢心跳.
现实是残酷的, 依据网上的一些说法, 中移动2/3G下, NAT超时时间为5分钟, 中国电信3G则大于28分钟, 理想的情况下, client应当以略小于NAT超时时间的间隔来发送心跳包.
wifi下, NAT超时时间都会比較长, 据说宽带的网关一般没有空暇释放机制, GCM有些时候在wifi下的心跳比在移动网络下的心跳要快, 可能是由于wifi下联网通信耗费的电量比移动网络下小.
关于怎样让心跳间隔逼近NAT超时的间隔, 同一时候自己主动适应NAT超时间隔的变化, 能够參看Android微信智能心跳方案.
心跳包和轮询的差别
心跳包和轮询看起来类似, 都是client主动联系server, 可是差别非常大.
  • 轮询是为了获取数据, 而心跳是为了保活TCP连接.
  • 轮询得越频繁, 获取数据就越及时, 心跳的频繁与否和数据是否及时没有直接关系
  • 轮询比心跳能耗更高, 由于一次轮询须要经过TCP三次握手, 四次挥手, 单次心跳不须要建立和拆除TCP连接.
TCP唤醒Android
这部分内容我仅仅知道结论, 不知道详细的知识
大家有没有想过, 手机的短信功能和微信的功能差点儿相同, 为什么微信会比短信耗电这么多? 当然不是由于短信一条0.1元. 手机短信是通过什么获取推送的呢?
以下这段出处不明的话或许能够给大家启发
首先Android手机有两个处理器, 一个叫Application Processor(AP), 一个叫Baseband Processor(BP). AP是ARM架构的处理器,用于执行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进入休眠状态, 就会成为待机电池杀手.
全然不是必需操心AP休眠会导致收不到消息推送. 通讯协议栈运行于BP,一旦收到数据包, BP会将AP唤醒, 唤醒的时间足够AP运行代码完毕对收到的数据包的处理过程. 其他的如Connectivity事件触发时AP相同会被唤醒. 那么唯一的问题就是程序怎样运行向server发送心跳包的逻辑. 你显然不能靠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的, 我有点搞不清楚Radio怎么翻译. Google在Optimizing Downloads for Efficient Network Access中提到了一个叫Radio State Machine的东西, 我翻译成无线电波状态机, 也不知道正确的翻译是什么.
移动网络下, 每个TCP连接底层都应该是有RRC连接, 而RRC连接会唤醒基带, 基带会唤醒CPU处理TCP数据, 这是我个人的理解.
至于wifi下怎样工作, 我临时没有找到资料.
上面说了这么多, 事实上意思就是TCP数据包能唤醒手机. 至于UDP, 我不确定.
而推送中最重要的部分就是让手机尽量休眠, 仅仅有在server须要它处理数据时才唤醒它, 这正好符合我们的要求.
移动网络下的耗电
Google在Optimizing Downloads for Efficient Network Access中提到了一个叫Radio State Machine的东西.

Android推送技术研究

文章图片


mobile radio state machine
说的应该就是基带的工作状态, 在Radio Standby下差点儿不耗电, 可是一旦有须要处理的事情, 比方手机里某个app要訪问网络(从上一节能够猜測: 收到RRC指令也会导致唤醒), 就会进入到Radio Full Power中, 由Standby转为Full Power这一唤醒过程非常耗电, Full Power下基带空暇后5s进入Radio Low Power, 假设又空暇12s才进入Standby. 基本的意思就是不要频繁的唤醒基带去请求网络, 由于仅仅要一唤醒, 就至少会让基带在Full Power下工作5s, 在Low Power下工作12s, 并且唤醒过程非常耗电. 所以在移动网络下, 心跳须要尽量的慢才好, 只是以当前这样的情况, 想慢下来差点儿不可能.
只是这也带来另外一个问题, 假如手机里有10个应用, 每一个应用都发送心跳包, 每一个应用的server都可能唤醒手机, 那手机还休不休眠了?
实际实现遇到的问题了解完了我就開始动手做demo, server使用Apache的Mina, client也用这个
Mina
这个框架挺好用, 就是遇到些非常奇怪的事情, 我两天前看的, 所以也可能是我自己的问题.
一个是Android端发一个汉字给server, serverfilter崩溃, 发超过一个汉字, clientfilter崩溃, 写个IoFilter做一下编解码就好了. 另外User Guide里面的代码也有错误. 第二个是iosessionConfig的写超时设置了全然不起作用.
小米手机的奇妙Socket
后来又发现client仅仅要在后台超过一定时间, 对socket的写操作就会变得很诡异, 表现为socket把数据吞了, 告知应用数据已经被对方接收, 可是server什么都没收到, 并且server发送的消息client也收不到. 仅仅要让app进到前台, 之前消失的数据会一股脑发给server, client会收到server重传的消息.
我開始还以为是Android的休眠机制把wifi断了, 我把各种WifiLock,  WakeLock都持有了, 还是出这样的情况. 后来无意间发现小米针对每一个app都有个后台执行时同意联网的开关, 我把它打开了, 果然好了一阵子, 后来又開始反复之前的情况, 我还以为是Mina的IO线程被kill了还是怎么, 用DDMS看了线程信息没问题. 不放心, 又用纯Socket实现了client, 还是有问题, 再在之前的基础上加上1分钟的心跳, 还是有问题.
小米手机的奇妙bug
这次真是我运气好, 我又看了一眼后台执行时同意联网的开关, 发现demo app的这个开关刚刚还被我打开了, 这下又关上了, 我怀疑是小米的这个功能有bug, 我是记得有小米员工提到这东西有server下发白名单的, 我觉得是server下发数据把我的修改给覆盖了, 我把几个app的后台联网关了, 重新启动手机之后, 他们又开了.
最后我改了个10s的心跳间隔, 在心跳的时候, 把后台同意联网关掉, 复现了那个奇妙的socket行为, 大概确定是MIUI的bug.
睡了一觉起来, MIUI的project师联系了我, 确认是bug. 顺便提醒一下用小米做測试机的开发人员和用户, 这个bug的暂时解决方式是: 用神隐模式里的自己定义配置, 把自己想改的设置好即可.
想起一年前什么都不懂就跑去小米面试就好笑, 我这水平全然就是坑人, 然而没想到这次被小米坑了.
我心中最佳的推送技术RRC那套东西, 你懂的, 基带Standby模式下也保持着的连接.
參考网址Understanding the Android “Radio State Machine” for better battery life
Android微信智能心跳方案
微信收费事件背后被广泛忽略的技术细节
android设备休眠
Optimizing Downloads for Efficient Network Access
关于socket长连接的心跳包
Network address translation
C/C++网络编程中的TCP保活
TCP/IP,http,socket,长连接,短连接――小结。
Android实现推送方式解决方式
【Android推送技术研究】转自【http://www.jianshu.com/p/584707554ed7】


    推荐阅读