Android性能优化之网络优化

网络 通信技术
在移动互联网的快速发展环境下,手机用户日益对网络的使用或体验有着更深度的诉求,因此应用中的网络体验已经显得由此重要。

[[414087]]

前言小计

在移动互联网的快速发展环境下,手机用户日益对网络的使用或体验有着更深度的诉求,因此应用中的网络体验已经显得由此重要;

网络影响:

1.最直观的就是用户交互体验;

2.流量的流失;

3.电量的消耗;

上篇文章介绍了网络请求过程中dns,本文主要针对Android上网络优化

图片

一、网络数据缓存优化

提供一种将数据存储到本地的思想,实现减少服务器的请求负荷、加快请求速度、无网也能显示内容;

(1)对于常访问的数据或首页数据,尽量缓存在本地,加载时优先加载本地数据,然后在请求网络数据,更新页面并更新缓存;

(2)在网络丢失或者网络差需要保存数据时, 网数据保存在本地,并且把发出的请求添加到队列中,当网络恢复的时候再及时发出;

(3) 强制缓存:在缓存数据未失效的情况下,可以直接使用缓存数据,由两个字段Expires和Cache-Control用于标明失效规则;

(4)对比缓存:表示需要和服务端进行相关信息的对比,由服务器决定是使用缓存还是最新内容,如果服务器判定使用缓存,返回响应吗304,判定使用最新内容,则返回响应码200和最新数据;

(5)okhttp上的缓存设置

进行数据缓存,我们可以在返回上加上过期时间,避免重新获取。这种做法节约了流量,且大幅提高数据访问的速度,增强了用户体验。在OKHTTP与Volley等一些网络框架中都有很好的实践;

下面进行OKHTTP,在无网络的情况下使用cache进行缓存

  1. public class NoNetInterceptor implements Interceptor { 
  2.     @Override 
  3.     public Response intercept(Chain chain) throws IOException { 
  4.         Request request = chain.request(); 
  5.         Request.Builder builder = request.newBuilder(); 
  6.         if(!Utils.isNetworkConnected(PerformanceApp.getApplication())){ 
  7.             builder.cacheControl(CacheControl.FORCE_CACHE); 
  8.         } 
  9.         return chain.proceed(builder.build()); 
  10.     } 
  11.  static { 
  12.         OkHttpClient.Builder client = new OkHttpClient.Builder(); 
  13.         HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); 
  14.         logging.setLevel(HttpLoggingInterceptor.Level.BODY); 
  15.         Cache cache = new Cache(PerformanceApp.getApplication().getCacheDir(),10*1024*1024); 
  16.         client. 
  17.                 cache(cache). 
  18.                 eventListenerFactory(OkHttpEventListener.FACTORY). 
  19.                 dns(OkHttpDNS.getIns(PerformanceApp.getApplication())). 
  20.                 addInterceptor(new NoNetInterceptor()). 
  21.                 addInterceptor(logging); 
  22.         final Retrofit RETROFIT = new Retrofit.Builder() 
  23.                 .baseUrl(HTTP_SPORTSNBA_QQ_COM) 
  24.                 .addConverterFactory(FastJsonConverterFactory.create()) 
  25.                 .client(client.build()) 
  26.                 .build(); 
  27.         API_SERVICE = RETROFIT.create(APIService.class); 
  28.     } 

二、dns优化和httpdns

1、dns解析流程

图片
  • 在App内用域名发送请求都要经过DNS解析出ip,然后再根据ip去拿对应的资源,这个过程中,如果LocalDNS中存在这个域名对应的ip,就会直接返回这个ip,类似于App内做缓存;
  • 如果不存在,才会去权威DNS查询改访问哪个ip,然后查询到的ip会在LocalDNS中做缓存。也就是说,如果我们要访问新浪http://api.weibo.cn,如果LocalDNS里面有该域名对应的ip,就直接返回了ip了。
图片

2、问题分析

  • 一个新用户使用来访问api.weibo.cn,由于localDNS缓存的存在,不会去查询新浪的权威DNS,这样返回的ip是联通这个运营商的ip,从而会使得用户出现访问变慢等状况;
  • 缓存还会导致一点就是,当权威DNS将域名与ip的映射发生改变之后,由于LocalDNS缓存没有及时改变,用户就会访问到错误的服务器,或者直接访问不到资源;
  • 很多三四级运营商会把运营解析指向他们的缓存服务器上,并把网页里面的广告替换成他们自己的,或者内嵌他们自己的广告(之前做的APP出现过这样的情况,投诉之后会好上一段时间,但是过段时间又会出现广告);
  • 竟然DNS解析存在问题,那有没有一种调度精准、成本低廉、配置方便的基于域名的流量调度系统呢?
  • HttpDNS基于Http协议和域名解析的流量调度解决方案,可以在很大程度上防止上面的问题出现;

3、HttpDNS 概念

  • 客户端直接访问HttpDNS接口,获取业务在域名配置管理系统上配置的访问延迟最优的IP;
  • 客户端向获取到的IP后就向直接往此IP发送业务协议请求。以Http请求为例,通过在header中指定host字段,向HttpDNS返回的IP发送标准的Http请求即可;
  • 采用HttpDNS来解析域名,就绕过了三四级运营商解析域名会出现的问题,在HttpDNS返回了正确的ip之后,我们是直接采用ip去进行http请求,只需要关注通信内容的安全即可;

4、HTTPDNS 工作模式

  • 在客户端的 SDK 里动态请求服务端,获取 HTTPDNS 服务器的 IP 列表,缓存到本地。随着不断地解析域名,SDK 也会在本地缓存 DNS 域名解析的结果;
  • 当手机应用要访问一个地址的时候,首先看是否有本地的缓存,如果有就直接返回。这个缓存和本地 DNS 的缓存不一样的是,这个是手机应用自己做的,而非整个运营商统一做的。如何更新、何时更新,手机应用的客户端可以和服务器协调来做这件事情;
  • 如果本地没有,就需要请求 HTTPDNS 的服务器,在本地 HTTPDNS 服务器的IP 列表中,选择一个发出 HTTP 的请求,会返回一个要访问的网站的 IP 列表;

请求的方式是这样的。

  1. http://106.2.xxx.xxx/d?dn=c.m.163.com 
  2. {"dns":[{"host":"c.m.163.com","ips":["223.252.199.12"],"ttl":300,"http2":0}],"client":{"ip":"106.2.81.50","line":269692944}} 

手机客户端自然知道手机在哪个运营商、哪个地址。由于是直接的 HTTP 通信,HTTPDNS 服务器能够准确知道这些信息,因而可以做精准的全局负载均衡;

5、HttpDNS缓存设计

  • HTTPDNS 的缓存设计策略也是咱们做应用架构中常用的缓存设计模式,也即分为客户端、缓存、数据源三层,分别对应 SDK 客户端、本地缓存、HTTPDNS 服务器;
  • App内维护一个Serve IP List。把每次App从HttpDNS取到的ip存储进入该数组,并设置权重,理论上来说从HttpDns解析下来的ip权重是最大的。这个List可以在App启动的时候,进行更新,同时取出本地缓存的Serve IP List的权重最大的ip进行数据的初始化操作(如果第一次启动,没有该List的话,就使用LocalDNS进行解析);
  • Serve IP List里面的权重设置机制,很明显的一点就是从DNS解析出来的ip具有最大的权重,每次从List里面取ip应该要取权重最大的ip。列表中的ip也是需要可以动态更新配置的,根据连接或者服务的成功失败来进行动态调整,这样即使DNS解析失败,用户在一段时间后也会取到合适的ip进行访问;
  • 对ip进行数据统计。在所有app内统计每个ip进行请求所需平均时间、最长时间、最短时间、请求成功次数、失败次数,需要注意的是,要区分网络环境进行统计,Wifi、4G、3G,对在不同的网络环境下数据优秀的ip进行存储,下发到App里面使用起来。这样每次启动App时可以对收集起来的ip根据不同的网络环境进行测速,选择最好的ip进行请求。需要注意的是,在网络环境切换的时候,必须要重新进行速度测试。做到这一步,可以节约DNS解析时间,以及劫持的问题;
  • SDK 中的缓存会严格按照缓存过期时间,如果缓存没有命中,或者已经过期,而且客户端不允许使用过期的记录,则会发起一次解析,保障记录是更新的;

6、HTTPDNS调度设计

  • 在客户端,可以知道手机是哪个国家、哪个运营商、哪个省,甚至哪个市,HTTPDNS服务端可以根据这些信息,选择最佳的服务节点返回;
  • 如果有多个节点,还会考虑错误率、请求时间、服务器压力、网络状况等,进行综合选择,而非仅仅考虑地理位置。当有一个节点宕机或者性能下降的时候,可以尽快进行切换;
  • 要做到这一点,需要客户端使用 HTTPDNS 返回的 IP 访问业务应用。客户端的 SDK 会收集网络请求数据,如错误率、请求时间等网络请求质量数据,并发送到统计后台,进行分析、聚合,以此查看不同的 IP 的服务质量;
  • 在服务端,应用可以通过调用 HTTPDNS 的管理接口,配置不同服务质量的优先级、权重。HTTPDNS 会根据这些策略综合地理位置和线路状况算出一个排序,优先访问当前那些优质的、时延低的 IP 地址;
  • HTTPDNS 通过智能调度之后返回的结果,也会缓存在客户端。为了不让缓存使得调度失真,客户端可以根据不同的移动网络运营商 WIFI 的 SSID 来分维度缓存。不同的运营商或者 WIFI 解析出来的结果会不同;

7、OKHttp 接入 HTTPDNS

OkHttp 中使用 HTTPDNS,有两种方式:

通过拦截器,在发送请求之前,将域名替换为 IP 地址;

通过 OkHttp 提供的 .dns() 接口,配置 HTTPDNS;

①拦截器接入

拦截器是 OkHttp 中,非常强大的一种机制,它可以在请求和响应之间,做一些我们的定制操作;

在 OkHttp 中,可以通过实现 Interceptor 接口,来定制一个拦截器。使用时,只需要在 OkHttpClient.Builder 中,调用 addInterceptor() 方法来注册此拦截器即可;

  1. class HTTPDNSInterceptor : Interceptor{ 
  2.     override fun intercept(chain: Interceptor.Chain): Response { 
  3.         val originRequest = chain.request() 
  4.         val httpUrl = originRequest.url() 
  5.         val url = httpUrl.toString() 
  6.         val host = httpUrl.host() 
  7.         val hostIP = HttpDNS.getIpByHost(host) 
  8.         val builder = originRequest.newBuilder() 
  9.         if(hostIP!=null){ 
  10.             builder.url(HttpDNS.getIpUrl(url,host,hostIP)) 
  11.             builder.header("host",hostIP) 
  12.         } 
  13.         val newRequest = builder.build() 
  14.         val newResponse = chain.proceed(newRequest) 
  15.         return newResponse 
  16.     } 

在拦截器中,使用 HttpDNS 这个帮助类,通过 getIpByHost() 将 Host 转为对应的 IP;

②OKHttp 标准 API 接入

OkHttp 其实本身已经暴露了一个 Dns 接口,默认的实现是使用系统的 InetAddress 类,发送 UDP 请求进行 DNS 解析;

我们只需要实现 OkHttp 的 Dns 接口,即可获得 HTTPDNS 的支持。

在我们实现的 Dns 接口实现类中,解析 DNS 的方式,换成 HTTPDNS,将解析结果返回;

  1. class HttpDns : Dns { 
  2.     override fun lookup(hostname: String): List<InetAddress> { 
  3.         val ip = HttpDnsHelper.getIpByHost(hostname) 
  4.         if (TextUtils.isEmpty(ip)) { 
  5.             //返回自己解析的地址列表 
  6.             return InetAddress.getAllByName(ip).toList()  
  7.         } else { 
  8.             // 解析失败,使用系统解析 
  9.             return Dns.SYSTEM.lookup(hostname) 
  10.         } 
  11.     } 
  12. mOkHttpClient = httpBuilder 
  13.         .dns(HttpDns()) 
  14.         .build(); 

三、数据传输优化

客户端与服务端经常进行着频繁的数据传输,而数据传输又影响着用户体验,提出合理的优化建议

1、传统的传输方案

在开始的时候,采用的是xml传输,这就要使用到Serializable/Parcelable序列化以及反序列化,其传输速度之慢,基本已经被遗弃,后来又出现了JSON序列化传输,其常用工具就是GSON和fastjson,但随着时代的进步,json也体现出了局限性json的局限性主要体现在其是基于字符串的传输,在转换的时候会生成大量JsonObject,然后转化为字符串,送进流里面,然后传输,在服务端也要从流中取出,然后反序列化,一大堆繁琐的过程,其也渐渐不适合当今传输数据的要求;

2、新的数据传输方式

  • 现在有如下选择可以用Protocal Buffers:强大,灵活,但是对内存的消耗会比较大,并不是移动终端上的最佳选择;
  • Nano-Proto-Buffers:基于Protocal,为移动终端做了特殊的优化,代码执行效率更高,内存使用效率更佳;
  • FlatBuffers:这个开源库最开始是由Google研发的,专注于提供更优秀的性能;

如下图:

FlatBuffers几乎从空间和时间复杂度上完胜其他技术

FlatBuffers是一个开源的跨平台数据序列化库,可以应用到几乎任何语言(C++,C#,Go,Java,JavaScript,PHP,Python),最开始是Google为游戏或者其他对性能要求很高的应用开发的;

FlatBuffer的优点

FlatBuffer相对于其他序列化技术,例如XML,JSON,Protocol Buffers等,有哪些优势呢?官方文档的说法如下:

  • 直接读取序列化数据,而不需要解析(Parsing)或者解包(Unpacking):FlatBuffer把数据层级结构保存在一个扁平化的二进制缓存(一维数组)中,同时能够保持直接获取里面的结构化数据,而不需要解析,并且还能保证数据结构变化的前后向兼容;
  • 高效的内存使用和速度:FlatBuffer 使用过程中,不需要额外的内存,几乎接近原始数据在内存中的大小;
  • 灵活:数据能够前后向兼容,并且能够灵活控制你的数据结构
  • 很少的代码侵入性:使用少量的自动生成的代码即可实现;
  • 强数据类性,易于使用,跨平台,几乎语言无关;
  • JSON是Android中很常用的数据序列化技术,但却很消耗内存,而FlatBuffer正好解决了这个问题,性能还更好了;

3、对于 Post 请求,Body 是用 Gzip 压缩的,也就是请求的时候带上 Gzip 请求头,服务端返回的时候也加上 Gzip 压缩,这样数据流就是被压缩过的。

四、网络其他优化

1、网络分级请求

(1)将网络分成移动网络、宽带网络、强网络、弱网络,不同的网络环境对请求进行不同的处理,例如在移动网络下需要进行下载任务时,停止或提示用户,在弱网络下对图片的请求的区分等;

(2) 网络状态可以由TelephonyManager.getNetworkType()方法获取到;

(3)对下载和上传文件采用断点续传功能,不浪费用户之前耗费的时间和流量;

2、流量使用优化

(1)局部更新 、分页加载;

(2) 数据加载采用增量,有更新数据时才请求新数据,合并客户端旧数据;

(3)尽量避免客户端轮询,采用服务端推送方式;

3、请求数据优化

(1)合并请求,可以将多个请求合并成一个接口请求

(2)压缩请求数据

(3)精简数据格式,只取需要的数据字段

4、连接优化

  • 启用keep-alive,okhttp中已默认打开,但需要服务器支持;
  • 使用http2,使用keep-alive来缓存,通过http2来复用请求连接;
  • 复用连接池可以减少多个网络请求下的连接建立的消耗,而且OkHttp已经默认帮我们实现了这些功能;
  • 复用连接的前提是同ip通道,如果每个请求都发送给不同的ip,那么连接池也复用不了;
  • 非Http2的使用完连接后(就是Http请求完成后),我们需要手动 关闭RealCall,否则下层代码就要通过触发GC回收来帮我们检查和关闭;
  • 切换不同的域名,当一个连接失败的时候用另一个域名进行连接;

总结

1、传统的 DNS 有很多问题,例如解析慢、更新不及时。因为缓存、转发、NAT问题导致客户端误会自己所在的位置和运营商,从而影响流量的调度;

2、HTTPDNS 通过客户端 SDK 和服务端,通过 HTTP 直接调用解析 DNS 的方式,绕过了传统 DNS 的这些缺点,实现了智能的调度;

3、关于连接优化还有很多知识点,后续整理出来会发表出;

4、优化部分我们讲解了网络相关的,下次会整理出图片相关的知识点,一起学习一起进步,加油;

本文转载自微信公众号「Android开发编程」,可以通过以下二维码关注。转载本文请联系Android开发编程公众号。

责任编辑:姜华 来源: Android开发编程
相关推荐

2022-02-16 14:10:51

服务器性能优化Linux

2021-11-29 11:13:45

服务器网络性能

2019-12-13 10:25:08

Android性能优化启动优化

2021-07-27 20:51:02

AndroidDNS网络

2013-02-20 14:32:37

Android开发性能

2013-09-17 10:32:08

Android性能优化数据库

2017-01-15 15:13:37

Android性能优化优化点

2015-09-16 15:48:55

Android性能优化电量

2015-09-16 14:37:50

Android性能优化运算

2015-09-16 13:54:30

Android性能优化渲染

2018-01-09 16:56:32

数据库OracleSQL优化

2009-06-30 11:23:02

性能优化

2019-09-25 08:03:21

Android加速Google

2017-12-23 14:38:41

Android编程开发优化

2015-09-14 09:45:58

虚拟化网络网络性能

2017-03-29 14:44:20

网络性能优化

2019-09-25 08:25:49

RPC网络通信

2019-07-25 13:22:43

AndroidAPK文件优化

2011-06-14 14:17:23

性能优化系统层次

2021-07-16 23:01:03

SQL索引性能
点赞
收藏

51CTO技术栈公众号