移动互联网时代,用户对网络愈来愈依赖。虽然网络环境在逐渐变好,但也对网络的应用提出了更高的要求,同时开发人员对网络的重视度却在降低。确实 WiFi 场景下用户的网络质量变好了,并且用户对网络流量消耗的敏感度也在降低。android
因为对网络问题的忽视,在网络状况很差的状况下,用户体验会极度降低,这时网络性能优化变得尤其重要。做为一名移动开发者,面对复杂多变的移动网络咱们该如何去优化呢?程序员
一个数据包从手机发出通过无线网络、基站、互联网最后到达咱们的服务器,其中任何一个环节出现问题都会影响用户的体验。用户的网络环境、基站的负载能力、DNS 服务器、CDN 节点的链接速度等这些因素,对移动端应用来讲不受控制。移动端的网络优化,主要分为如下三个方面:web
在网络正常或者良好的时候,怎样更好地利用带宽,进一步提高网络请求的速度。面试
移动端网络复杂多变,在出现网络链接不稳定的时候,怎样最大程度保证网络的连贯性。编程
网路安全不容忽视,怎样有效防止被第三方劫持、窃听甚至篡改。浏览器
除了这三个问题,咱们还可能会关心网络请求形成的耗电、流量问题。对于速度、弱网络以及安全的优化,又该从哪些方面入手呢?首先咱们应该搞清楚一个网络请求的整个过程(对这个过程不熟悉的小伙伴,推荐看下《图解 HTTP》)。缓存
由上图能够看到,整个网络请求主要分为几个步骤,而整个请求耗时能够细分到每个步骤里面。安全
经过 DNS 服务器,拿到对应域名的 IP 地址。在这个步骤,咱们比较关注 DNS 解析耗时状况、运营商 LocalDNS 的劫持、DNS 调度这些问题。性能优化
跟服务器创建链接,这里包括 TCP 三次握手、TLS 密钥协商等工做。多个 IP/端口该如何选择、是否要使用 HTTPS、可否能够减小甚至省下建立链接的时间,这些问题都是咱们优化的关键。服务器
在成功创建链接以后,就能够愉快地跟服务器交互,进行组装数据、发送数据、接收数据、解析数据。咱们关注的是,如何根据网络情况将带宽利用好,怎样快速地侦测到网络延时,在弱网络下如何调整包大小等问题。
链接的关闭看起来很是简单,其实这里的水也很深。这里主要关注主动关闭和被动关闭两种状况,通常咱们都但愿客户端能够主动关闭链接。
所谓网络优化,就是围绕速度、弱网络、安全这三个核心内容,减小每个步骤的耗时,打造快速、稳定且安全的高质量网络。
在实际的开发工做中,咱们不多会像《UNIX 网络编程》那样直接去操做底层的网络接口,通常都会使用网络库。Square 出品的 OkHttp 是目前最流行的 Android 网络库,它还被 Google 加入到 Android 系统内部,为广大开发者提供网络服务。
网络库屏蔽的下层复杂的网络接口,让咱们能够更高效的使用网络请求,极大的提升了咱们的开发效率。我常常看到一些开发者会使用基于网络库再次封装的开源库,这里很不建议开发者使用这些库。
首先不清楚这些库是否能彻底符合咱们的需求;而后这些库的质量良莠不齐,每每在使用中遇到问题没法快速修复。这里强烈建议你们使用一手资源,推荐本身封装,不只能够提高开发效率,还能够提升下本身的编码水平。
据了解业内大厂蘑菇街、头条、UC 浏览器都在 Chromium 网络库上作了二次开发,而微信 Mars 在弱网络方面作了大量优化,拼多多、虎牙、链家、美丽说这些应用都在使用 Mars。
下面咱们来一块儿对比下各个网络库的核心实现。
为何大厂都不使用 OkHttp 呢?主要是由于它不支持跨平台,对于大型应用来讲跨平台是很是重要的。咱们不但愿全部的优化 Android 和 iOS 都要各自去实现一套,不只浪费人力并且还容易出现问题。
对于大厂来讲,不能只局限在客户端网络库的双端统一上,网络优化不只仅是客户端的事情,因此通常都有统一的网络中台,它负责提供前台一整套网络解决方案。
阿里的 ACCS、蚂蚁的 mPaas、携程的网络服务都是公司级的网络中台服务,这样全部的网络优化可让整个集团的全部接入应用受益。
根据网络状态对网络请求进行区别对待,2G 与 WiFi 状态下网络质量确定是不同的,那对应的网络策略也应该是不同的。
例如:在 WiFi 场景下能够进行数据的预取、一些统计的集中上传等;而在 2G 场景下此类操做以及网络请求的次数策略都应该调低。
经过 ConnectivityManager 能够获取当前是否已链接网络。
public static boolean isNetworkConnected(Context context) { if (context == null) returnfalse; ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); if (manager == null) returnfalse; NetworkInfo networkInfo = manager.getActiveNetworkInfo(); if (networkInfo == null) returnfalse; return networkInfo.isAvailable() && networkInfo.isConnected(); }
isAvailable() 与 isConnected() 的区别:
状态 | isConnected() | isAvailable() |
---|---|---|
显示链接已保存,但标题栏没有,即没有实质链接上 | false | true |
显示链接已保存,标题栏也有已链接上的图标 | true | true |
选择不保存后 | false | true |
选择链接,在正在获取 IP 地址时 | false | false |
经过 NetworkInfo 中的 getNetworkType() 方法能够获取当前网络类型。
public static final int NETWORK_NONE = 0; public static final int NETWORK_WIFI = 1; public static final int NETWORK_MOBILE = 10; public static final int NETWORK_2G = 12; public static final int NETWORK_3G = 13; public static final int NETWORK_4G = 14; /** * 获取当前的网络状态 * * @param context * @return 没有网络:0; WIFI:1; 手机网络:10; 2G:12; 3G:13; 4G:14; */ public static int getNetworkType(Context context) { ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); if (connectivityManager == null) return NETWORK_NONE; NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); if (networkInfo == null || !networkInfo.isAvailable()) return NETWORK_NONE; int type = networkInfo.getType(); if (type == ConnectivityManager.TYPE_WIFI) { return NETWORK_WIFI; // WiFi } if (type == ConnectivityManager.TYPE_MOBILE) { TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); if (telephonyManager == null) return NETWORK_NONE; int networkType = telephonyManager.getNetworkType(); switch (networkType) { // 2G case TelephonyManager.NETWORK_TYPE_GPRS: case TelephonyManager.NETWORK_TYPE_CDMA: case TelephonyManager.NETWORK_TYPE_EDGE: case TelephonyManager.NETWORK_TYPE_1xRTT: case TelephonyManager.NETWORK_TYPE_IDEN: return NETWORK_2G; // 3G case TelephonyManager.NETWORK_TYPE_EVDO_A: case TelephonyManager.NETWORK_TYPE_UMTS: case TelephonyManager.NETWORK_TYPE_EVDO_0: case TelephonyManager.NETWORK_TYPE_HSDPA: case TelephonyManager.NETWORK_TYPE_HSUPA: case TelephonyManager.NETWORK_TYPE_HSPA: case TelephonyManager.NETWORK_TYPE_EVDO_B: case TelephonyManager.NETWORK_TYPE_EHRPD: case TelephonyManager.NETWORK_TYPE_HSPAP: return NETWORK_3G; // 4G case TelephonyManager.NETWORK_TYPE_LTE: return NETWORK_4G; default: return NETWORK_MOBILE; } } return NETWORK_NONE; }
首先,建立一个 NetworkReceiver。
public class NetworkReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Log.d("NetworkReceiver", "网络发生变化"); String action = intent.getAction(); if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) { int networkType = NetworkUtil.getNetworkType(context); Log.e("NetworkReceiver", "networkType = " + networkType); Toast.makeText(context, "当前网络:" + networkType, Toast.LENGTH_SHORT).show(); } } }
而后,在 AndroidManifest.xml 文件中注册 NetworkReceiver。
<receiver android:name=".ui.broadcast.NetworkReceiver"> <intent-filter> <action android:name="android.net.conn.CONNECTIVITY_CHANGE" /> <action android:name="android.net.wifi.WIFI_STATE_CHANGED" /> <action android:name="android.net.wifi.STATE_CHANGE" /> </intent-filter> </receiver>
并添加监听网络须要的相关权限。
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> 在 Android 7.0 以后静态注册广播的方式被取消了,因此咱们这里还须要采用动态注册的方
在 Android 7.0 以后静态注册广播的方式被取消了,因此咱们这里还须要采用动态注册的方式。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { NetworkReceiver networkReceiver = new NetworkReceiver(); IntentFilter filter = new IntentFilter(); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); registerReceiver(networkReceiver, filter); } 设置网络缓存
在必定时间内,对服务端返回的数据进行缓存,好比一些接口的数据不会更新(10 分钟或更久变化一次),咱们就能够缓存该接口的数据,设定有效时间,能够减小没必要要的流量消耗。
Android 系统上关于网络请求的 Http Response Cache 是默认关闭的,这样会致使每次即便请求的数据内容是同样的也会须要重复被调用执行,效率低下。
咱们能够经过下面的代码示例开启 HttpResponseCache。
protected void onCreate(Bundle savedInstanceState) { // ... try { File httpCacheDir = new File(context.getCacheDir(), "http"); long httpCacheSize = 10 * 1024 * 1024; // 10 MiB HttpResponseCache.install(httpCacheDir, httpCacheSize); } catch (IOException e) { Log.i(TAG, "HTTP response cache installation failed:" + e); } } protected void onStop() { // ... HttpResponseCache cache = HttpResponseCache.getInstalled(); if (cache != null) { cache.flush(); } }
开启 Http Response Cache 以后,Http 操做相关的返回数据就会缓存到文件系统上,不只仅是主程序本身编写的网络请求相关的数据会被缓存,另外引入的 library 库中的网络相关的请求数据也会被缓存到这个 Cache 中。
备注:若是所有本身从头开始写会比较繁琐复杂,有很多著名的开源框架 Volley、Okhttp 都很好的支持实现自定义缓存。
为了可以减少网络传输的数据量,咱们须要对传输的数据作压缩的处理,这样可以提升网络操做的性能。首先不一样的网络环境,下载速度以及网络延迟是存在差别的,以下图所示:
若是咱们选择在网速更低的网络环境下进行数据传输,这就意味着须要执行更长的时间,而更长的网络操做行为,会致使电量消耗更加严重。另外传输的数据若是不作压缩处理,也一样会增长网络传输的时间,消耗更多的电量。不只如此,未通过压缩的数据,也会消耗更多的流量,使得用户须要付出更多的流量费。
一般来讲,网络传输数据量的大小主要由两部分组成:图片与序列化的数据,那么咱们须要作的就是减小这两部分的数据传输大小,分下面两个方面来讨论。
首先须要作的是减小图片的大小,选择合适的图片保存格式是第一步。下图展现了 PNG、JPEG、WEBP 三种主流格式在占用空间与图片质量之间的对比:
对于 JPEG 与 WEBP 格式的图片,不一样的清晰度对占用空间的大小也会产生很大的影响,适当的减小 JPG 质量,能够大大的缩小图片占用的空间大小。
另外,咱们须要为不一样的使用场景提供当前场景下最合适的图片大小,例如针对全屏显示的状况咱们会须要一张清晰度比较高的图片,而若是只是显示为缩略图的形式,就只须要服务器提供一个相对清晰度低不少的图片便可。
服务器应该支持到为不一样的使用场景分别准备多套清晰度不同的图片,以便在对应的场景下可以获取到最适合本身的图片。这虽然会增长服务端的工做量,但是这个付出却十分值得!
其次须要作的是减小序列化数据的大小,不直接使用 JSON 和 XML 格式数据。
JSON 与 XML 为了提升可读性,在文件中加入了大量的符号,空格等等字符,而这些字符对于程序来讲是没有任何意义的。咱们应该使用 Protocal Buffers,Nano-Proto-Buffers,FlatBuffer 来减少序列化的数据的大小。
Protocol Buffer 是 Google 开发的一种数据交换的格式,它独立于语言,独立于平台。相较于目前经常使用的 JSON,数据量更小,意味着传输速度也更快。
DNS 解析的失败率占联网失败中很大一种,并且首次域名解析通常须要几百毫秒。针对此,咱们能够不用域名,采用 IP 直连省去 DNS 解析过程,节省这部分时间。
另外熟悉阿里云的小伙伴确定知道 HTTPDNS,HTTPDNS 基于 HTTP 协议的域名解析,替代了基于 DNS 协议向运营商 Local DNS 发起解析请求的传统方式,能够避免 Local DNS 形成的域名劫持和跨网访问问题,解决域名解析异常带来的困扰。
文件、图片等的下载,采用断点续传,不浪费用户以前消耗过的流量。
文件的上传失败率比较高,不只仅由于大文件,同时带宽、时延、稳定性等因素在此场景下的影响也更加明显。
使用最新的协议,HTTP 协议有多个版本:0.九、1.0、1.一、2 等。
新版本的协议通过再次的优化,例如:
新的版本不只能够节省资源,一样能够减小流量。
合并网络请求,减小请求次数。对于一些接口类如统计,无需实时上报,将统计信息保存在本地,而后根据策略统一上传。这样头信息仅需上传一次,减小了流量也节省了资源。
Network Profiler 是 Android Profiler 中的一个组件,可帮助开发者识别致使应用卡顿、OOM 和内存泄露。它显示一个应用内存使用量的实时图表,能够捕获堆转储、强制执行垃圾回收以及跟踪内存分配。
能够在 View > Tool Windows > Android Profiler 中打开 Network Profiler 界面。![Network Monitor
](https://upload-images.jianshu...
Network Profiler 的具体使用可查看 Android 开发文档 - 利用 Network Profiler 检查网络流量、Android Studio 3.0 利用 Android Profiler 测量应用性能 这两篇文章。
使用 Charles、Fiddler 等抓包工具一样能够实现 Network Monitor 的功能,并且更增强大。
Stetho 是 Facebook 出品的一个 Android 应用的调试工具。无需 Root 便可经过 Chrome,在 Chrome Developer Tools 中可视化查看应用布局、网络请求、SQLite,Preference 等。一样集成了 Stetho 以后也能够很方便的查看网络请求的各类状况。
阿里P6P7【安卓】进阶资料分享+加薪跳槽必备面试题▲15G的Android架构进阶、视频资料及安卓程序员简历模板