Android一路走来的网络框架变更【网络协议:HTTP】
在Android 2.2版本之前,HttpClient拥有较少的bug,因此使用它是最好的选择。而在Android 2.3版本及以后,HttpURLConnection则是最佳的选择。它的API简单,体积较小,因而非常适用于Android项目。压缩和缓存机制可以有效地减少网络访问的流量,在提升速度和省电方面也起到了较大的作用。对于新的应用程序应该更加偏向于使用HttpURLConnection,因为在以后的工作当中我们也会将更多的时间放在优化HttpURLConnection上面。
高效稳定、维护成本高
用法:
基于 Apache HttpClient
请求在子线程中完成,在请求线程中返回回调
使用了线程池
使用 RequestParams 类封装了请求参数
持久化 cookies 到 SharedPreferences
支持文件上传
支持 json
支持 Http Basic Auth
用法:
基于 HttpClient(网络层实现为 finalHTTP,是 HttpClient 的再次封装)
开源的 ORM 和 IOC 应用开发框架
小巧灵活、代码入侵量少
轻便灵活易扩展
支持 Https、缓存
用法:
注意添加网络访问权限
3.0- 基于 HttpClient,3.0+ 基于 HttpURLConnection
基于 Afinal 开发的,目前功能比较完善的开源框架
Android 开发团队2013年推出的一个网络通信框架,Android 2.2- 使用 HttpClient, Android 2.3+ 使用 HttpURLConnection
通信封装,支持加载图片,性能优化,适合数据量不大、通信频繁的网络
优势在于处理小文件的 Http 请求
支持处理高分辨率的图像压缩
支持使用 Okhttp 作为传输层
NetworkImageView 在 GC 的使用模式上更加保守,在请求清理上也更加积极
用法:
还可以使用 ImageLoader 来实现对图片的更多处理,如缓存、过滤,用法:
自定义 Request,用法:
Java 的 Http + SPDY 客户端开发包,支持 Android 2.3+
高效,支持 SPDY、连接池、Gzip、Http 缓存
自动处理网络问题,如二次连接、SSL 握手
Android 4.4 开始 HttpURLConnection 底层实现采用 OkHttp
用法:
性能最好,处理最快
适用于 REST API
传输层默认使用 OkHttp
支持 NIO
默认使用 Gson
用法:
Android 8.1 使用的网络框架为 HttpURLConnection,其底层实现依赖于 OkHttp
一个典型的 HttpURLConnection 网络请求过程为:
HttpURLConnection connection = null;
InputStream inputStream = null;
try {
// 1. 实例化一个 URL 对象,传入目标网络地址
URL url = new URL(urlPath);
// 2. 通过 URL 对象的 openConnection() 方法获取 HttpUrlConnection 实例
connection = (HttpURLConnection) url.openConnection();
// 3. 设置 http 请求的连接属性
connection.setRequestMethod("GET");
connection.setUseCaches(false);
connection.setConnectTimeout(3000);
connection.setReadTimeout(5000);
connection.setDoInput(true);
// 4. 通过 getResponseCode() 获取状态码
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
// 5. 通过 getInputStream() 获取服务器返回的输入流
inputStream = connection.getInputStream();
// 6. 读取内容并解析
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] bytes = new byte[1024*512];
int length = 0;
while ((length = inputStream.read(bytes)) > -1) {
byteArrayOutputStream.write(bytes, 0, length);
}
return byteArrayOutputStream.toString();
} catch (Exception e) {
Log.e(TAG, "catch Exception = " + e.getMessage());
e.printStackTrace();
} finally {
if (connection != null) {
// 7. 通过 disconnect() 方法关闭当前连接
connection.disconnect();
connection = null;
}
if (inputStream != null) {
try {
inputStream.close();
inputStream = null;
} catch (IOException e) {
Log.e(TAG, "catch IOException = " + e.getMessage());
e.printStackTrace();
}
}
}
分析具体流程前,首先捋一下源码的路径和一些类继承关系
HttpURLConnection 相关源码对应不同版本的路径:
相关类的继承关系:
接下来具体分析代码调用流程:
首先通过 URL 类的构造函数初始化 URL 类实例,然后调用这个实例的 openConnection() 方法,实际上调用了 openConnection(Proxy proxy),返回 URLConnection 类型对象。
URL 的 openConnection(Proxy proxy)
里面会调用 h a n d l e r . o p e n C o n n e c t i o n ( t h i s , p ) handler.openConnection(this, p) handler.openConnection(this,p), 其中的 handler 是 URLStreamHandler 类型的对象,通过 g e t U R L S t r e a m H a n d l e r ( S t r i n g p r o t o c o l ) getURLStreamHandler(String protocol) getURLStreamHandler(Stringprotocol) 设置,URLStreamHandler 是一个抽象类, o p e n C o n n e c t i o n ( U R L u , P r o x y p ) openConnection(URL u, Proxy p) openConnection(URLu,Proxyp) 的真正实现在其实现子类中,看一下 URLStreamHandler 是如何设置的
根据协议查找一个 URLStreamHandler 的哈希表;
通过 URLStreamHandlerFactory 对象的 createURLStreamHandler() 方法创建;
【这个方法里面重写了 URLStreamHandler 类的 openConnection 方法,并且规定了 http/https 协议的默认端口 80/443】
拿到系统变量 java.protocol.handler.pkgs 的值通过 classloader 去查找对应类,然后去创建类实例;
根据不同的协议去创建不同的类实例
if (protocol.equals("file")) {
handler = new sun.net.www.protocol.file.Handler();
} else if (protocol.equals("ftp")) {
handler = new sun.net.www.protocol.ftp.Handler();
} else if (protocol.equals("jar")) {
handler = new sun.net.www.protocol.jar.Handler();
} else if (protocol.equals("http")) {
handler = (URLStreamHandler)Class.
forName("com.android.okhttp.HttpHandler").newInstance();
} else if (protocol.equals("https")) {
handler = (URLStreamHandler)Class.
forName("com.android.okhttp.HttpsHandler").newInstance();
}
注意,这里 Android 源码才用了 jarjar-rules,即将以一个路径开头的包在编译时打包成另一个路径开头的包,所以 com.android.okhttp.HttpHandler 对应的包应该是 com.squareup.okhttp.HttpHandler,https 的同理,所以 URLStreamHandler 的子类实现分别在 HttpHandler 和 HttpsHandler 中
HttpsHandler 继承自 HttpHandler,openConnection(URL url, Proxy proxy) 的实现是调用了 newOkUrlFactory(proxy) 创建一个 OkUrlFactory 对象并调用它的 open(url) 方法。newOkUrlFactory() 里面首先调用 createHttpOkUrlFactory(proxy) 创建一个 OkUrlFactory 对象,然后通过调用这个对象的 client() 获取成员对象 OkHttpClient 的实例,接着通过调用 OkHttpClient 实例的 setConnectionPool() 去设置连接池。在 createHttpOkUrlFactory(proxy) 里面首先创建一个 OkHttpClient 对象,并且设置超时、重定向、流量加密这些属性,然后传入 client 参数创建一个 OkUrlFactory 对象并返回。
OkUrlFactory 的 open(url) 方法会调用 open(url, client.getProxy()),这里的 client 就是 OkHttpClient 对象。在 open(URL url, Proxy proxy) 中,首先 url.getProtocol() 获取协议,是 http 还是 https,然后设置 client 的 proxy,最后根据协议返回相应的实现类实例,HttpURLConnectionImpl 或 HttpsURLConnectionImpl。
URL#openConnection() 最终会返回 HttpURLConnectionImpl 或 HttpsURLConnectionImpl 对象
HttpURLConnection 是一个抽象类,定义了许多配置连接属性方法,定义了获取相应消息码和响应消息的方法,定义了 disconnect()、usingProxy() 抽象方法,还定义了返回消息码,大致分为
2XX: generally "OK"
3XX: relocation/redirect
4XX: client error
5XX: server error
在建立连接的过程,HttpURLConnection 没有显式调用 connect(),而是直接去调用 getResponseCode() 方法,而 getResponseCode() 里面会调用 getInputStream(),getInputStream() 方法中会间接实现了 connect()
看下 getInputStream() 这个方法,首次在 URLConnection 中定义,在 HttpURLConnectionImpl 重写实现。这个方法里面首先调用 getResponse() 返回类型为响应消息的 HttpEngine 对象,然后通过此对象的 getResponse() 获取返回消息,然后调用 body() 获取消息体,最后调用 byteStream() 返回字节流格式。
HttpEngine 的 getResponse() 方法返回的是一个 Response 对象 userResponse(还有一个 cacheResponse 作用不详),Response 的 body() 方法会返回 ResponseBody 对象。
重点看下 getResponse() 如何返回 HttpEngine 对象的。这个方法里面首先调用 initHttpEngine() 初始化 HttpEngine 对象,接着调用 hasResponse() 判断这个 HttpEngine 对象是否有响应消息,若有则直接将此对象返回,否则会循环调用 execute(true),然后分别通过 httpEngine 的 getResponse() 方法获取 Response 对象和 followUpRequest() 方法获取 Request 对象,并调用 newHttpEngine(method, streamAllocation, (RetryableSink) requestBody, response)。
initHttpEngine() 里面会设置连接为 true,然后调用 newHttpEngine(method, null, null, null) 创建 HttpEngine 对象并返回;而 newHttpEngine(String method, …) 方法里面会进一步构造一系列对象如 Request、OkHttpClient 等然后调用 HttpEngine() 构造函数。
execute(true) 里面则会开始真正的建立连接过程:httpEngine 会调用 sendRequest() 发送请求,然后调用 getConnection() 获取连接,最后调用 readResponse() 读取相应消息。其中,route 和 handshake 都是通过 connection 的 getRoute() 和 getHandshake() 获取。另外,此函数中会捕获异常,然后通过 httpEngine 的 recover(e) 获取 retryEngine 实现重试功能。
再看下 getOutputStream() 这个方法,首先调用 connect(), 然后通过 httpEngine 的 getBufferedRequestBody() 获取 BufferedSink 对象,最后调用 BufferedSink 对象的 outputStream() 方法返回 OutputStream 对象。
connect() 里面做的也是调用 initHttpEngine() 初始化 HttpEngine 和无限重复执行 execute()。
getBufferedRequestBody() 方法中会通过 getRequestBody() 获取请求消息体,然后封装到 BufferedSink 对象中返回。
disconnect() 方法定义在 HttpURLConnection 中实现在 HttpURLConnectionImpl 中,实际上调用了 HttpEngine 对象的 cancel() 方法