前言
互联网时代, App作为于用户交互的端, 可以说实际上是一个界面, 产品的业务, 服务都是由Server提供的. 而App与Server的交互依赖于网络, 故而网络优化, 也是我们的App优化中不可缺少的一个优化项.本文对网络优化方面的知识做了一个全面总结,主要内容如下:
一、网络优化方向
1.流量消耗
- 对于用户要尽可能的做到一段时间内流量消耗的精准度量,而且还要能够知道用户不同网络类型及前后台流量消耗情况
- 监控相关:用户流量消耗均值、异常率(一定时间内流量消耗多、网络请求次数多、下载文件过大)
- 完整链路全部监控(每个请求的Request、Response相关的所有信息全部记录),服务端下发指令控制本地上传,客户端在超过阈值之后主动上报
2.网络请求质量
- 用户体验:请求速度、成功率(直接影响到用户体验)
- 监控相关:请求时长、业务成功率、失败率、Top失败接口(详细分析发现问题)
3.其它方向
- 公司成本:带宽、服务器数、CDN等方面的开支
- 网络请求密集对手机耗电量也有一定影响
4.网络优化误区
- 只关注流量消耗,忽视其它维度,往往导致我们做的网络优化不够全面
- 只关注均值、用户整体数据,忽略个体数据
二、网络优化工具
Network Profiler
它是Android Studio自带的工具:
- 显示实时网络活动:网络请求发送、接收数据及连接数
- 需要启用高级分析:这些功能Android Studio并没有默认全部开放,需手动启用高级分析
- 只支持HttpURLConnection和OkHttp网络库
首先我们打开Android Studio,然后在菜单栏选择Run--->Edit Configurations--->Android App--->app--->profiling,然后如下图勾选第一个Enable advanced profiling选项,点击APPLY--->OK即可:
然后进入Network Profiler的视图,此时我发送一条网络请求,然后时间线上就会显示出来这条请求,现在我选中这条请求的一个时间范围,然后下方就会显示出这条请求的名称、大小、类型、状态、时间等,右侧会有这条请求的详细信息,你可以预览数据,我这里的是一个接口请求,如果有图片请求右侧预览界面也可以预览图片:
抓包工具
- Charles:使用Java开发,跨平台,Mac上使用的比较多
- Fiddler:使用C#开发,Windows上使用的比较多
- Wireshark:非常流行的网络封包分析软件,功能强大,可以截取各种网络封包,显示网络封包的详细信息,一般需要了解网络协议
- TcpDump:Linux中强大的网络数据采集分析工具
Stetho
- 强大的应用调试桥,连接Android和Chrome
- 网络监控、视图查看、数据库查看、命令行扩展等
使用方式:
- 添加依赖:implementation 'com.facebook.stetho:stetho-okhttp3:1.5.0'
- 初始化:Stetho.initializeWithDefaults(this);
- 添加拦截器:addNetworkInterceptor
- Chrome浏览器:chrome://inspect
实际使用的话你会发现它和Network Profiler有点像,一般也很少会使用它去做抓包工具,所以关于它的实际用法只是简单的提一下,具体的有感兴趣的可以实际操作一下,
首先添加依赖并初始化,都是上面的步骤:
Stetho.initializeWithDefaults(this);
然后添加拦截器:
最后在浏览器中输入chrome://inspect之后按照下图中框选出的位置点击即可开始抓包,这里可能会需要翻墙,因为我实际测试的时候发现不翻墙是报404的:
三、精准获取流量消耗实践
APP流量消耗偏高是如何判断的
这个问题决定了我们什么时候开始对流量进行优化,这里就简单说一下:
- 绝对值看不出高低:流量的绝对值看不出高低,比如经过测试,我们的APP消耗了20MB的流量,但是它并不是说我们就需要立马优化它,因为你这个流量消耗可能是基于连续使用了APP很长时间的场景下消耗的,所以绝对值不能作为流量是否偏高的唯一统计标准
- 对比竞品,相同场景对比流量消耗:比如用我们的APP和竞品APP在相同的网络环境下同时去跑一个相同功能的流程,保证其他因素都相同,变量唯一,如果我们的APP和竞品的流量消耗差距较大,那么我们肯定要考虑做流量优化了,绝对值和竞品对比二者应该结合使用
- 异常监控超过正常指标:比如设定用户使用某个功能单次消耗流量的值为X,如果上线之后超过了预期值,这种情况就需要确认一下流量消耗是否是偏高的
流量测试方案:
- 设置——流量管理
- 抓包工具:只允许本App联网
- 可以解决大多数问题,但是线上场景线下可能遇不到
当你的APP到了稳定期之后,日活可能会有成百上千万甚至更多,此时APP的功能应该是非常复杂的,并且还具备了其他的一些功能比如说监控,这些功能的网络请求并不是实时上报的,因此做流量消耗的周期应该会很长,不是简单的十几二十分钟就能搞定的。
另外,我们还应该排除别的APP的干扰,比如你想要抓取你的APP的所有网络请求,此时应该打开设置界面进入流量管理,设置只允许你的这个APP可以联网,别的所有APP的联网功能全都关闭,这样使用抓包工具抓到的请求都是这个APP的请求了。这种抓包方案在线下测试一般都是没有问题的,但是某些场景可能会在线上出现,在线下就很难发现,所以这些场景就只能通过线上监控才能发现。
线上线下流量获取方案
1.TrafficStats
- API8以上版本提供的流量数据统计方案:它统计到的数据是手机上次重启之后的流量消耗
- getUidRxBytes(int uid)指定Uid的接收流量
- getTotalTxBytes()总共发送的流量
这种方案其实不推荐使用,所以就不多说了,有兴趣的朋友可以自己尝试一下,直接调用它的静态方法就可以获取了:
存在的问题:
- 无法获取某个时间段内的流量消耗,只能获取一个大的具体值
2.NetworkStatsManager
- API23之后的流量统计
- 可以获取指定时间间隔内的流量信息
- 可以获取不同网络类型下的流量消耗
接下来我们通过实战来统计这个Demo本月消耗的蜂窝流量情况:
后续可以将这个获取流量的方法进一步封装,比如将它封装到基础库中,然后通过入参网络类型、开始时间、结束时间等在应用层调用,更加方便灵活的获取你想要的数据。我们将获取到的结果进行打印输出:
可以看到这个值是29061607,将这个除以1024再除以1024得到的是27MB,然后到手机设置里面找到数据流量排行,选择本月,找到这个Demo看一下系统统计出来的流量消耗是多少:
经过对比发现两个值还是相当接近的,说明我们通过代码统计到的数据应该是很精确的一个值了,所以今后我们就可以统计出线上用户流量消耗的准确值了,如果今后有用户反馈说某一天流量消耗的比较多,我们就可以通过APM后台下发一条指令来回捞一下用户当天具体的流量消耗值,结合用户使用时长,就可以分析出用户的流量消耗是否存在异常。
前后台流量获取方案
场景:线上反馈App后台跑流量
很多用户对后台流量非常关心,大多数用户都是非常害怕你的应用在后台一直跑流量的,如果你只是使用上面讲到的这种方案,实际上我们并不知道消耗的这些流量前后台所占用的比例,所以只获取一个时间段的值不够全面,还需要知道这些流量到底是在前台消耗的还是在后台消耗的。
解决方案:
App启动时执行一个后台任务,这个后台任务每隔一段时间获取一下这段时间内的流量消耗,自己维护一份数据的统计,分别记录用户在前后台的流量消耗总量,结合别的监控在合适的时间上报到APM后台作为流量治理的依据,这样当用户反馈时,我们可以直接查看用户流量消耗统计,然后判断是否存在问题,这种方案可以结合上面的代码来实现,下面说一下实现思路:
首先将上面的代码封装一下,将开始时间和结束时间作为参数从调用方传递:
其次可以监听app处于前台还是后台,可以使用标志位进行判定,具体的监听方法可以直接使用下面这种方式:
然后再开启一个定时任务,每隔一段时间获取一次流量消耗情况:
总结:
- 有一定误差,在可接受范围内:注意我们这里统计的是当时的一段时间内的流量消耗情况,在这段时间内用户有可能是在前后台切换的,所以它肯定会存在一定的误差
- 结合精细化的流量异常报警针对性的解决后台跑流量
四、网络请求流量优化实践
需要使用网络的场景
- 数据:Api请求、资源包(app的升级包、H5的zip包、RN的bundle包)、配置信息(A/BTest等)
- 图片:下载、上传(流量消耗大户)
- 监控:APM相关、单点问题相关
网络请求优化手段
1.数据缓存
- 服务端返回加上过期时间,避免每次重新获取
- 节约流量且大幅提高数据访问速度,更好的提升用户体验
- OkHttp、Volly都有比较好的缓存实践
首先来写一个针对无网络情况下的拦截器NoNetInterceptor,策略模式设置为没有网络时强制开启缓存:
然后在OkHttp中添加缓存:
就这两步就搞定了哦,然后来对比一下结果,没有缓存时关掉网络应用进去就是个空白页,有缓存的话即使关掉网络也是有数据能够展示的,给人一种好的用户体验:
2.增量数据更新
- 加上版本的概念,只传输有变化的数据
- 配置信息
举个栗子:省市区等数据更新,这种类型的数据并不是经常变化的,如果每次都全量更新很明显造成了浪费,所以可以只更新有变化的那部分数据,这个场景需要和服务端实际配合,所以这里就不演示了,不过我相信大家也都明白是什么意思了。
3.数据压缩
- Post请求Body使用GZip压缩:请求时带上GZip请求头,服务端返回时也带上GZip压缩,这样数据流就是被压缩过的
- 请求头压缩:请求头也是需要占用一定的体积的,假设请求头不变的话可以只传递一次,以后都值传递上一次请求的MD5值,服务端作缓存对于需要的信息直接从缓存中获取
- 图片上传之前必须压缩:避免图片原图上传,这里推荐使用鲁班(图片压缩库)这个库,为什么推荐它呢,因为它是号称最接近微信朋友圈的图片压缩算法,GitHub上的star数也是相当高了11.8K
4.优化发送频率和时机
- 合并网络请求,减少请求次数:每个网络请求时都会有冗余信息,比如请求头,合并网络请求可以减少冗余信息的传递
- 性能日志上报:批量+特定场景上报。对于成熟项目来说,会有很多性能日志埋点,这些数据最好不要在每次记录的时候都上传,而是记录的时候就只记录,在合适的时间点,比如用户处于WIFI环境下再去上传,这样对于用户的流量没有太大影响
5.图片相关
- 图片使用策略优化:比如在列表展示时优先使用缩略图,直接展示原图只是扩大了内存的消耗并没有任何实际的意义
- 使用WebP格式图片:在不改变图片尺寸的情况下可以有效的降低图片的物理体积,这个需要结合实际情况进行选择,因为可能实际使用的云服务并没有直接将jpg转换为webp的能力
五、网络请求质量优化
上一部分我们说了网络请求的流量优化,实际上对用户体验影响最大的是网络请求质量很差,这一点也是让人容易忽略的一个地方,我们开发测试阶段基本上都是在公司的wifi环境下进行的,网络环境一般都还是OK的。但是实际上线之后用户的网络环境我们是不可控的,假设有用户经常反馈界面打不开或者是打开较慢,图片加载不出来等情况,这些对用户的使用体验是有巨大影响的,很多情况下用户就会抛弃我们的APP,转而去寻求同类型下体验更好的APP,所以网络请求质量尤为关键。总的来说质量指标就是以下两点:
- 网络请求成功率
- 网络请求速度
HTTPDNS
在介绍质量优化之前,先来说说Http请求的过程:
- 首先开始请求,请求到达运营商的Dns服务器并解析成对应的IP地址
- 创建连接,开始TCP三次握手,然后根据IP地址找到相应的服务器,发起一个请求
- 服务器找到对应的资源原路返回给访问的用户
从上面介绍的请求的过程不难发现网络请求的成功率和速度一开始就受到DNS解析服务的影响,如果域名到IP地址这个过程被劫持、或者解析速度较慢都会严重影响用户体验,DNS被劫持就是用户得到的数据并不是真实想要提供给用户的数据,解析比较慢则会造成用户等待的时间比较长,所以DNS优化是网络请求质量优化的第一步。
- 解决方案:使用HTTPDNS,绕过运营商域名解析过程(它不是使用传统的DNS协议向DNS服务器的53端口发送请求,而是使用Http协议向DNS服务器的80端口发送请求)
- 优势:降低平均访问时长(节省了一次解析过程)、提高连接成功率(降低LocalDNS的劫持,绕过运营商的域名解析过程)
实战OkHttp结合HTTPDNS的使用:
首先引入一个库:HTTPDNS是阿里云面向移动开发者提供的移动端DNS解析服务。通过该SDK,开发者可以在自己的Android APP中获得可靠、实时、精准的DNS解析服务,彻底解决传统DNS面临的域名劫持、解析时延长、调度不精准等问题
接着来创建一个集合OkHttp使用的DNS解析服务的类,此类使用单例模式:
最后在OkHttp中设置自己的DNS解析服务:
这样在网络请求的时候就可以绕过系统DNS解析这一步来提升网络请求的质量。
协议版本升级
接着来说说HTTP协议版本的优化,上面在介绍HTTP请求的时候其中有一步是是创建连接,这里面会出现TCP的三次握手,这个时间是比较长的,如果每次网络请求都走三次握手,很明显效率是非常低的,所以HTTP的不同版本对这一点的优化也是非常多的,下面来看下HTTP协议不同版本之间的主要区别:
- 1.0版本:TCP连接不复用——相对较老,现在基本上没有使用此版本的服务了,它的TCP是不复用的,每个TCP连接只能发送一个请求,如果需要再次请求别的资源就需要重新建立一个连接,TCP创建连接的成本很高,需要三次握手,并且开始阶段发送速度较慢,因此这个版本性能非常差
- 1.1版本:引入持久连接,但数据通讯按次序进行——此版本开始,TCP连接默认不关闭,多个网络请求可以复用,效率得到了保证,但是同一个TCP连接里面所有的数据通信必须按照次序来,处理完一个请求之后再响应下一个请求,如果前面的网络请求比较慢那么后面的就需要等待
- 2.0版本:多工,客户端、服务器双向实时通信——二进制协议,它有一个非常大的好处是多工,默认实现了连接复用,客户端和服务端可以同时发送多个请求和回应,实现了双向的实时通信
如果有条件的情况下,尽量选择高版本的HTTP协议。
网络请求质量监控
- 接口请求耗时、成功率、错误码——实际上这个耗时和成功率服务端也能统计,但是服务端拿到的数据并不完整,因为实际业务场景下有些请求可能并没有到达服务端就已经失败了,这种场景下服务端肯定无法统计这些请求,并且服务端传回的数据加上网络通道的延迟时间肯定要比统计到的时间要长,所以必须在客户端也加上统计
- 图片加载的每一步耗时
1.OkHttp如何获取网络请求的质量数据
OkHttp给我们留了一个回调,叫做EventListener,我们可以自己实现一个EventListener,然后设置给每一次的网络请求:
首先创建一个model并且定义几个成员变量作为统计的对象:
然后自定义一个OkHttpEventListener,并且实现它的回调方法,每次创建OkHttpEventListener的时候就创建出一个OkHttpEvent,在具体的回调方法中为其赋值:
最后在OkHttp中进行设置:
通过上面的方法我们就可以拿到这次网络请求的每一步中包括dns解析的时间、request&response的时间、字节数等信息,这些数据都是做线上监控所必需的。
2.如何监听图片加载进度
关于图片加载现在大家也都是使用开源的解决方案,比较多的是Glide和Fresco了,我们需要做的是监听图片加载的整个过程,然后就可以计算出来每一步的耗时情况了
网络容灾机制
- 备用服务器分流
- 多次失败后一定时间内不进行请求,避免雪崩效应
其它优化
- 资本层面:CDN加速、提高带宽、动静资源分离(更新后清理缓存)
- 减少传输量,注意请求时机及频率
- OkHttp的请求池:当前正在执行网络请求的任务数的最大值默认是64,单个Host可以同时运行5个网络请求,这样做可以防止某个域名的请求过多其它的域名请求没有机会执行
六、网络体系化方案建设
线下测试
- 方案:只抓单独App,将其余App的联网权限全部关闭
- 侧重点:请求有误、多余,网络切换、弱网、无网测试
线上监控分为两个部分:服务端监控和客户端监控
服务端监控
- 请求耗时(区分地域、时间段、版本、机型)
- 失败率(业务失败与请求失败)
- 排名靠前的失败接口、异常接口
客户端监控
- 接口的每一步详细信息(DNS、连接、请求等)
- 请求次数、网络包大小、失败原因
- 图片监控
异常监控体系
- 服务器防刷:超限拒绝访问
- 客户端:大文件预警、异常兜底策略(连续多次请求失败暂停访问并加大重试时长)
- 单点问题追查
性能系列专栏其他文章
关于 Android内存优化你应该了解的知识点
关于 Android启动优化你应该了解的知识点
Android卡顿优化分析及解决方案,全面掌握
关于Android UI绘制优化你应该了解的知识点
关于 Android 稳定性优化你应该了解的知识点
关于Android 线程优化你应该了解的知识点