Android性能优化之网络优化

在移动互联网的快速发展环境下,手机用户日益对网络的使用或体验有着更深度的诉求,因此应用中的网络体验已经显得由此重要。
首页 新闻资讯 行业资讯 Android性能优化之网络优化

[[414087]]

前言小计

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

网络影响:

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

2.流量的流失;

3.电量的消耗;

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

图片

一、网络数据缓存优化

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

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

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

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

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

(5)okhttp上的缓存设置

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

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

复制

public class NoNetInterceptor implements Interceptor {     @Override     public Response intercept(Chain chain) throws IOException {         Request request = chain.request();         Request.Builder builder = request.newBuilder();         if(!Utils.isNetworkConnected(PerformanceApp.getApplication())){             builder.cacheControl(CacheControl.FORCE_CACHE);         }         return chain.proceed(builder.build());     } }  static {         OkHttpClient.Builder client = new OkHttpClient.Builder();         HttpLoggingInterceptor logging = new HttpLoggingInterceptor();         logging.setLevel(HttpLoggingInterceptor.Level.BODY);         Cache cache = new Cache(PerformanceApp.getApplication().getCacheDir(),10*1024*1024);         client.                 cache(cache).                 eventListenerFactory(OkHttpEventListener.FACTORY).                 dns(OkHttpDNS.getIns(PerformanceApp.getApplication())).                 addInterceptor(new NoNetInterceptor()).                 addInterceptor(logging);         final Retrofit RETROFIT = new Retrofit.Builder()                 .baseUrl(HTTP_SPORTSNBA_QQ_COM)                 .addConverterFactory(FastJsonConverterFactory.create())                 .client(client.build())                 .build();         API_SERVICE = RETROFIT.create(APIService.class);     }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

  • 18.

  • 19.

  • 20.

  • 21.

  • 22.

  • 23.

  • 24.

  • 25.

  • 26.

  • 27.

  • 28.

  • 29.

二、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 列表;

请求的方式是这样的。

复制

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

  • 2.

手机客户端自然知道手机在哪个运营商、哪个地址。由于是直接的 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() 方法来注册此拦截器即可;

复制

class HTTPDNSInterceptor : Interceptor{     override fun intercept(chain: Interceptor.Chain): Response {         val originRequest = chain.request()         val httpUrl = originRequest.url()         val url = httpUrl.toString()         val host = httpUrl.host()         val hostIP = HttpDNS.getIpByHost(host)         val builder = originRequest.newBuilder()         if(hostIP!=null){             builder.url(HttpDNS.getIpUrl(url,host,hostIP))             builder.header("host",hostIP)         }         val newRequest = builder.build()         val newResponse = chain.proceed(newRequest)         return newResponse     }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

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

②OKHttp 标准 API 接入

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

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

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


复制

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

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

三、数据传输优化

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

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开发编程公众号。