Android中长连接的解决方案
Http请求的过程
- 通过运营商的DNS服务器解析目标域名的ip地址,保存到localhost文件中缓存
- 通过TCP三次握手与服务端建立链接
- 写入Http的请求头、请求体以及数据后接收数据
- 接收完一个请求的数据后,通过TCP四次挥手释放链接
如果还需要担心DNS劫持的话,则还需要加一层HttpDNS的过程。也就是通过Http协议将域名解析成对应的可信任的IP列表,通过ip直接访问。
长连接的优势
长连接通过Socket与服务端建立持久的链接,即使单个请求发送与接收后也不会释放链接。这样的好处有:
- 通过指定IP建立连接,减少DNS的查询时间
- 只用经历最初的一次TCP的三次握手,在真正请求时,则免去了建立连接的过程
- 在大量请求并发的时候,不会出现大量的Http链接断开重连的过程
- 服务端可以通过长连接进行推送,达到更加实时的效果
- TCP接收数据的滑动窗口也会一直保持
长连接的问题
虽然长连接的好处很多,但是在保持长连接稳定的过程中也会存在很多的问题:
- 网络切换的过程会导致长连接断开
- 进程被杀掉时候,也会导致长连接断开
- NAT超时,会导致长连接断开
- DHCP租期到了,会导致ip地址变化,导致长连接断开
所以,在应用中,就需要保护长连接的稳定,否则会导致很多信息收不到。
NAT:因为 IP v4 的 IP 量有限,运营商分配给手机终端的 IP 是运营商内网的 IP,手机要连接 Internet,就需要通过运营商的网关做一个网络地址转换(Network Address Translation,NAT)。简单的说运营商的网关需要维护一个外网 IP、端口到内网 IP、端口的对应关系,以确保内网的手机可以跟 Internet 的服务器通讯。GGSN(Gateway GPRS Support Node 网关GPRS支持结点)模块就实现了NAT功能。
因为大部分移动无线网络运营商都是为了减少网关的NAT映射表的负荷,所以如果发现链路中有一段时间没有数据通讯时,会删除其对应表,造成链路中断。
长连接的稳定性方案
为了从客户端保持长连接的稳定,有这些方案:
- 提升长连接进程的优先级,避免被系统杀死
- 因为Linux中的Low Memory Killer是通过每个进程的oomadj来判断是否清理该进程,所以可以通过提高进程优先级来降低被Kill的风险
- 进程保活,当进程被Kill时,可以重新启动:
- 通过接收系统广播
- 通过AlarmManager定时检测
- 通过Linux的fork子进程,当子进程被回收时,检测ppid是否为init进程,如果是的话,则回调Java层启动Service
- 通过Service的onStartCommand返回START_STICKY
- 网络切换时,自动重连
- 监听系统网络切换广播
- 监听屏幕亮灭的广播,主动批量拉取消息
- 定期发送心跳包,避免NAT超时
- 移动网络的NAT超时一般都在5min左右
- 心跳包需要在5min以内定时发送
长连接与耗电
在保证长连接的过程中,太频繁发送网络请求,并且当前设备状态不佳时,都需要动态的调整长连接的策略。并且在收到消息后,本地存储的时机都需要进行考虑。
- 前后台策略:区分进程前后台,调整心跳间隔时间
- NAT超时策略:根据运营商以及网络类型,调整心跳间隔
- 合并请求:将多个请求在同一时间/同一个包内进行合并,减少系统唤醒次数
- 设备策略:根据设备是否在充电、设备电量来制定长连接请求策略
- 消息策略:区分消息类型,制定优先级,确定哪些消息在特定情况下拉取
- Android特性:尽量拟合Doze以及JobScheduler的特性,批量以及特定情况下进行消息处理
长连接的容灾
当用户的长连接断了之后,如果不能重启,则需要将长连接进行降级策略,例如使用Http轮询策略。
长连接消息的本地存储
在长连接消息的本地存储中,通常都使用SQLite数据库,当然也有新型的ORM数据库例如ObjectBox。而通常长连接都需要考虑数据库的点有:
- 数据库的过期时间
- 数据库消息的数量
- 数据库消息的清理时机与策略
- 数据库的批量插入与删除效率
离线推送
对于有产品矩阵的App而言,可以通过产品矩阵来发送Push的通知从而拉起相应的产品。比如百度、腾讯、阿里、头条等
注意事项
在开发的过程中,需要注意:
- 消息的时序问题
- 数据库以及内存的更新顺序
- 消息表的分配
- 用户消息的区分