9、Android性能优化之网络优化

####前言 互联网时代, App做为于用户交互的端, 能够说其实是一个界面, 产品的业务, 服务都是由Server提供的. 而App与Server的交互依赖于网络, 故而网络优化, 也是咱们的App优化中不可缺乏的一个优化项. 典型的HTTP请求流程说明:html

典型HTTP请求流程

####一、网络链接对用户的影响 App的网络链接对于用户来讲, 影响不少, 且多数状况下都很直观, 直接影响用户对这个App的使用体验. 其中较为重要的几点: 流量App的流量消耗对用户来讲是比较敏感的, 毕竟流量是花钱的嘛. 如今大部分人的手机上都有安装流量监控的工具App, 用来监控App的流量使用. 若是咱们的App这方面没有控制好, 会给用户很差的使用体验.android

电量电量相对于用户来讲, 没有那么明显. 通常用户可能不会太注意. 可是如前文电量优化中说的那样, 网络链接(radio)是对电量影响很大的一个因素. 因此咱们也要加以注意.git

用户等待也就是用户体验, 良好的用户体验, 才是咱们留住用户的第一步. 若是App请求等待时间长, 会给用户网络卡, 应用反应慢的感受, 若是有对比, 有替代品, 咱们的App极可能就会被用户无情抛弃.github

####二、分析网络链接的工具 2.1 Network Monitor Android Studio内置的Monitor工具中就有一个Network Monitor:sql

Network Monitor

其中: Rx --- R(ecive) 表示下行流量, 即下载接收. Tx --- T(ransmit) 表示上行流量, 即上传发送.数据库

怎么使用Network Monitor? Network monitor实时跟踪选定应用的数据请求状况. 咱们能够连上手机, 选定调试应用进程, 而后在App上操做咱们须要分析的页面请求. 例如, 上图就是以CoderPub为例, 针对从repo列表界面进入repo详情界面的监控数据. 能够看到从10s到30s之间, 20s时间内发生了屡次数据请求, 且22s到27s之间的请求数据量还很大. 分析代码能够看到, 在请求repo详情的时候是打包了不少请求的:api

@Override
public Observable<RepoDetail> getRepoDetail(String owner, String name) {
    return Observable.zip(mRepoService.get(owner, name),
            mRepoService.contributors(owner, name),
            mRepoService.listForks(owner, name, "newest"),
            mRepoService.readme(owner, name),
            isStarred(owner, name),
            new Func5<Repo, ArrayList<User>, ArrayList<Repo>, Content, Boolean, RepoDetail>() {
                @Override
                public RepoDetail call(Repo repo, ArrayList<User> users, ArrayList<Repo> forks, Content readme, Boolean isStarred) {
                    RepoDetail detail = new RepoDetail();

                    repo.setStarred(isStarred);
                    detail.setBaseRepo(repo);
                    detail.setForks(forks);

                    // because the readme content is encode with Base64 by github.
                    readme.content = StringUtil.base64Decode(readme.content);
                    detail.setReadme(readme);

                    detail.setContributors(users);
                    return detail;
                }
            });
}

复制代码

这也验证了14s到20s间的四次数据请求, 另外因为repo详情界面会显示做者以及贡献者的图片, 而图片的数据量相对大, 故而23s到27s间有屡次数据量很大的请求发生. 这个实际是有不少优化空间的, 咱们稍后再说.缓存

2.2 WiresharkFiddlerCharlesr等抓包工具bash

使用Charles、Fiddler等抓包工具一样能够实现Network Monitor的功能,并且更增强大。服务器

Charles使用
2.3Stetho

Stetho是Facebook出品的一个Android应用的调试工具。无需Root便可经过Chrome,在Chrome Developer Tools中可视化查看应用布局,网络请求,sqlite,preference等。一样集成了Stetho以后也能够很方便的查看网络请求的各类状况。

Paste_Image.png

####三、网络优化

重点来了,网络优化主要从三个方面进行:1. 速度;2. 成功率;3. 流量。 #####3.1 接口设计 API设计 App与Server之间的API设计要考虑网络请求的频次, 资源的状态等. 以便App能够以较少的请求来完成业务需求和界面的展现. 例如, 注册登陆. 正常会有两个API, 注册和登陆, 可是设计API时咱们应该给注册接口包含一个隐式的登陆. 来避免App在注册后还得请求一次登陆接口(有可能失败, 从而致使业务流程失败). 再例如, 上文提到的获取repo详情, 实际上请求了4个接口, 请求了repo的信息, forks列表, contributors列表, readme, 这是由于github提供的接口是尽可能单一职责的. 然而在咱们的实际开发中, 咱们的Server除了提供这些单一职责的小接口外, 最好还能组合一个知足客户端业务需求的repo详情接口出来. Gzip压缩 使用Gzip来压缩request和response, 减小传输数据量, 从而减小流量消耗. 考虑使用Protocol Buffer代替JSON 从前咱们传输数据使用XML, 后来使用JSON代替了XML, 很大程度上也是为了可读性和减小数据量(固然还有映射成POJO的方便程度).

下图是对比Json数据使用 Gzip 压缩先后对比图 返回内容开启 Gzip压缩前

Paste_Image.png
开启Gzip压缩后

Paste_Image.png
对比发现,开启Gzip后能够减小57.3%的数据传输量

Paste_Image.png

Protocol Buffer是Google推出的一种数据交换格式. 若是咱们的接口每次传输的数据量很大的话, 能够考虑下protobuf, 会比JSON数据量小不少. 固然相比来讲, JSON也有其优点, 可读性更高. 本文以网络流量优化的角度推荐protobuf做为一个选择, 具体还需更具实际状况考虑.

图片的Size 上面Network Monitor中看到的22s到27s之间的有屡次请求, 且数据量还很大. 就是在获取图片资源. 图片相对于接口请求来讲, 数据量要大得多. 故而也是咱们须要优化的一个点. 咱们能够在获取图片时告知服务器须要的图片的宽高, 以便服务器给出合适的图片, 避免浪费. 咱们如今不少公司的图片资源都是使用第三方的云存储服务的(七牛, 阿里云存储之类的). 以七牛为例, 能够在请求图片的url中添加诸如质量, 格式, width, height等path来获取合适的图片资源:

imageView2/<mode>/w/<LongEdge>
                 /h/<ShortEdge>
                 /format/<Format>
                 /interlace/<Interlace>
                 /q/<Quality>
                 /ignore-error/<ignoreError>
复制代码

参考七牛官方文档.

#####3.2 图片处理

#####3.2.1 图片下载

#####使用缩略图 App中须要加载的图片按需加载,列表中的图片根据须要的尺寸加载合适的缩略图便可,只有用户查看大图的时候才去加载原图。不只节省流量,同时也能节省内存!以前使用某公司的图片存储服务在原图连接以后拼接宽高参数,根据参数的不一样返回相应的图片。

有许多方式来使得图片更加容易下载,好比使用 WebP图片,动态地调整大小的图片,以及使用图片加载框架。 #####使用WebP图片 经过网络提供WebP文件来减小图片加载的时间和节省网络带宽,WebP文件一般会比它的PNG或者JPG文件小,但会拥有一样的图片质量。甚至是使用有损的设置,WebP也能够输出一个和原图几乎彻底同样的图片。安卓系统从Android4.0(API 14)添加了有损耗的WebP support而且在Android4.2(API 17)对无损的,清晰的WebP提供了支持。 使用WebP格式;一样的照片,采用WebP格式可大幅节省流量,相对于JPG格式的图片,流量能节省将近 25% 到 35 %;相对于 PNG 格式的图片,流量能够节省将近80%。最重要的是使用WebP以后图片质量也没有改变。

#####动态修改图片 应用要求按指定的渲染大小来从网络上请求图片,这个渲染大小和设备规格有关,而且服务器提供的是合适大小的图片。这样可以最小化网络上的数据传输,减小持有图片对内存的大量损耗,直接影响到性能的提升和用户满意度。

当用户不得不等待图片下载的时候用户体验就会有所降低,使用合适的图片尺寸有助于解决这些问题,能够考虑让图片的请求都基于网络的类型或者网络链接的质量,这个尺寸可能会比目标值小。

#####使用图片加载框架 你的APP不该该重复获取图片,图片加载框架好比Glide或者Picasso获取图片,而后缓存,而后hook到你的View来显示占位符图片,直到真正的图片准备好了显示真正的图片。由于图片被缓存下来了,当图片下次被请求的时候,这些图片加载框架会直接从本地缓存中获取。

图片加载框架会管理他们的缓存大小,保留最近使用过的图片,这样你的APP的大小不会无限制地增加。

#####3.2.2 图片上传

图片(文件)的上传失败率比较高,不只仅由于大文件,同时带宽、时延、稳定性等因素在此场景下的影响也更加明显;

避免整文件传输,采用分片传输; 根据网络类型以及传输过程当中的变化动态的修改分片大小; 每一个分片失败重传的机会。 备注:图片上传是一项看似简单、共性不少但实际上复杂、须要细分的工做。移动互联网的场景和有线的场景是有不少区别的,例如移动网络的质量/带宽常常会发生“跳变”,但有线网络倒是“渐变”。

#####3.3 网络缓存 适当的缓存, 既可让咱们的应用看起来更快, 也能避免一些没必要要的流量消耗. 关于Android App的网络缓存, 请参考MVP架构实现的Github客户端(4-加入网络缓存)一文. #####3.4 打包网络请求 当接口设计不能知足咱们的业务需求时. 例如可能一个界面须要请求多个接口, 或是网络良好, 处于Wifi状态下时咱们想获取更多的数据等. 这时就能够打包一些网络请求, 例如请求列表的同时, 获取Header点击率较高的的item项的详情数据. 能够经过一些统计数据来帮助咱们定位用户接下来的操做是高几率的, 提早获取这部分的数据.

#####3.5 监听相关状态 经过监听设备的状态: 休眠状态 充电状态 网络状态

在状态弱网下优化:

压缩/减小数据传输量 利用缓存减小网络传输 针对弱网(移动网络), 不自动加载图片 界面先反馈, 请求延迟提交例如, 用户点赞操做, 能够直接给出界面的点同意功的反馈, 使用JobScheduler在网络状况较好的时候打包请求.

结合JobScheduler来根据实际状况作网络请求. 比方说Splash闪屏广告图片, 咱们能够在链接到Wifi时下载缓存到本地; 新闻类的App能够在充电, Wifi状态下作离线缓存.

#####3.6 调整数据传输

有几种方式让你的APP适应网络条件,提供一个较好的用户体验的,好比,对请求划分优先等级来最小化用户等待信息的时间。也能够检测并适应较慢网络速度和发生网络链接的时候的发生的改变。

优先考虑带宽 不该该假设设备链接的网络是一个长时间持续而且稳定可靠的网络,app应该对网络请求划分优先级尽量快地展现最有用的信息给用户。

马上呈现给用户一些实质的信息是一个比较好的用户体验,相对于让用户等待那些不那么必要的信息来讲。这能够减小用户不得不等待的时间,增长APP在慢速网络时的实用性。

为了达到这个目的,对网络请求进行排序好比文本的获取应该在富媒体以前,文本请求通常都比较小,压缩更好,而且传输速度快,这意味着你的APP能够快速地先显示内容。

在慢速网络的时候使用更少的带宽 你的APP传输数据的能力是否及时取决于网络链接,检测这些网络的质量而且调整你的APP使用网络的行为能够提供一个更好的用户体验。

使用下列的方法来检测外部不容易观察的网络质量,使用从这些方法返回的数据,你的APP应该调整对网络的使用来为用户的操做提供一个及时的响应

ConnectivityManager> isActiveNetworkMetered() ConnectivityManager> getActiveNetworkInfo() ConnectivityManager> getNetworkCapabilities(Network) TelephonyManager> getNetworkType() 在一个慢速的网络链接中,考虑只下载低分辨率的媒体或者直接不下载。确保你的用户能够在慢速网络中继续使用你的APP,对于没有图片或者图片仍然在加载的状况,应该先显示一个占位符,使用 Palette library建立一个动态的占位符,生成一个符合目标图片颜色的占位符

在Android 7.0或更高版本的设备上,用户能够打开Data Saver设置,能够帮助最小化数据的使用,Android 7.0扩展ConnectivityManager来检测Data Saver设置。

检测网络改变,而后修改APP的行为 网络质量不是固定不变的,它会随着地理位置,网络流量和当地人口密度发生改变。APP 应该检测网络中的改变而且相应地调整带宽,让APP能够更好地适应网络质量,能够实现下面的这些方法检测网络状态:

ConnectivityManager> getActiveNetworkInfo()
ConnectivityManager> getNetworkCapabilities(Network)
TelephonyManager> getDataState()
复制代码

随着网络质量的降低,减小请求的数量,随着网络质量的提高,你能够提升你的请求量到最优级别。 在更高的网络质量下,不计费使用流量的网络,能够考虑预取数据让数据提早可用。从用户体验的立场,这可能意味着一个新闻阅读应用在2G网络下一次只能获取3篇文章,而在WIFI状态下一次能够获取20篇文章。

当网络链接状态发生改变时会发出CONNECTIVITY_CHANGE广播,APP在前台运行的状况下,能够经过注册广播接收器来接受这个广播,在接收广播之后,你应该再评估当前的网络状态而且调整你的UI,处理网络请求,不可以在manifest文件中声明这个广播Action,由于在Android7.0版本以后这个Action就被删除了。

#####3.6 IP直连与HttpDns;

DNS解析的失败率占联网失败中很大一种,并且首次域名解析通常须要几百毫秒。针对此,咱们能够不用域名,才用IP直连省去 DNS 解析过程,节省这部分时间。

另外熟悉阿里云的小伙伴确定知道HttpDns:HttpDNS基于Http协议的域名解析,替代了基于DNS协议向运营商Local DNS发起解析请求的传统方式,能够避免Local DNS形成的域名劫持和跨网访问问题,解决域名解析异常带来的困扰。

#####3.7请求打包

合并网络请求,减小请求次数。对于一些接口类如统计,无需实时上报,将统计信息保存在本地,而后根据策略统一上传。这样头信息仅需上传一次,减小了流量也节省了资源。

#####3.8请求频率优化

能够经过提供一个最佳的网络体验来加强用户体验,好比,你可让你的APP在离线状态下依然可使用,使用GcmNetworkManager和ContentProvider,删除重复的网络请求。

让你的APP在离线状态下依然可用 在一些比较偏僻的地方,一般网络信号都不会很好,APP失去网络链接是很常见的事情。建立一个可以在离线状态依然正常使用的APP意味着用户能够在任什么时候候和你的APP进行互动。能够经过把网络数据保存在本地来实现这个需求,缓存数据,而且把发出的请求添加到队列中,当网络恢复的时候再及时发出。 在可能的状况下,app不该该通知用户网络已经失去链接了,只有当用户进行操做,而且这个操做必定须要网络链接的支持,才通知用户当前处于没有网络的状态。

当一个设备处于没有网络链接状态时,应用应该容许用户发出的网络请求,在网络链接恢复之后再执行这些网络请求。一个例子就是一个邮件客户端容许用户在设备处于离线的状态下依然可以建立,发送,查看,移动和删除已经存在的邮件,就是由于这些操做被保存下来而且在网络恢复之后再执行。这样的话,APP就可以在设备有网络和没有网络的时候都提供一个类似的用户体验。

使用GcmNetworkManager和contentProvider 确保你的APP使用数据库或者类似的结构来保存全部的数据到磁盘上,这样它就可以在无论网络条件如何的状况下表现出极佳的体验,好比使用(SQLite和ContentProvider)缓存数据, GCM Network Manager ( GcmNetworkManager)提供一个健全的机制和服务器同步数据当 content providers (ContentProvider)缓存这些数据,结合来提供一个容许在离线状态继续使用的结构。

App应该缓存从网络上获取的内容,在发起持续的请求以前,app应该先显示本地的缓存数据。这确保了app无论设备有没有网络链接或者是很慢或者是不可靠的网络,都可以为用户提供服务。

除去网络请求 一个离线优先的结构初始化时会尝试从本地获取数据,失败之后,从网络请求数据。从网络检索之后,数据会缓存到本地。这就确保对于同一块数据发起网络请求只会发生一次,对随后的请求都会使用本地数据来完成请求。为了达到这个,使用本地数据库来对数据持久化(一般使用android.database.sqlite 或者 SharedPreferences)

这个结构一样简化一个APP的流畅性,在离线和在线状态之间一方从网络获取数据保存到本地,另外一方从缓存获取数据展现给用户。

对于短暂不持续的数据,也就是内容更新快的数据,使用一个有大小,或者时间限制的磁盘缓存,好比DiskLruCache,数据一般不会发生改变的就只从网络请求一次而后缓存下来之后使用,这样的数据通常是图片或者非临时的文档好比新闻文章或者推送消息。

####4.结语

网络优化, 是App优化中至关重要的一项优化. 除了客户端, 接口的优化外, 不少一部分优化还依赖于服务器端, 包括服务器端的代码开发, 部署方式等. 跟你的服务器开发/运维工程师一块儿聊聊这个话题吧:)

特别感谢 黑白咖 夜之魔 anly_jun 石先

相关文章
相关标签/搜索