前言
在Java中我们经常使用HttpUrlConnection来做http网络请求,使用HttpsUrlConnection来做https网络请求的,但是在Android中我们还是可以使用阿帕奇开源框架 HttpClient来进行网络请求(Android6.0及以上废弃了),Android6.0及以上使用都是OKHttp和HttpUrConnection,我们发现很多所谓的网络请求框架都是基于上面两个实现的,下面我们侧重来看看HttpUrlConnection的具体实现。
简介
HttpUrlConnection是一个多用途,轻量级的Http请求框架(也可以叫客户端),支持get,post,put,delete等各种请求方式,最常用的就是get和post。HttpClient和HttpUrlConnection的比较:
- HttpClient是apache的开源框架,封装了访问http的请求头,参数,内容体,响应等等,使用起来比较方便,而HttpUrlConnection是Java的标准类,什么都没有封装,用起来太原始,不方便,比如重访问的自定义,以及一些高级功能。
- 从稳定性方面来说的话,HttpClient很稳定,功能强,Bug少,容易控制细节,而之前HttpUrlConnection一直存在版本兼容的问题,不过在后续的版本中因为引入了OKhttp,所以解决了这个问题。
网上有人说废弃HttpClient是因为它底层有bug,这里的具体原因我也没有根据代码去跟踪了,不过我最近在研究Android4.4上HttpUrlConnection的代码实现的时候发现其底层是利用了OKHttp的去实现的。其实HttpUrlConnection可以看做是Java中的一个标准库名字,也可以说相当于是一个标准,至于其内部的实现我们可以随心所欲的修改,比如OpenJdk中的实现跟 Android上的实现,以及标准 Oracle Jdk实现都是不一样的,但是其内部方法和包名必须是一样的,其实这么规定也是方便调用者方便使用的。
示例
private void requestGet(HashMap<String, String> paramsMap) {
try {
String baseUrl = "https://xxx.com/getUsers?";
StringBuilder tempParams = new StringBuilder();
int pos = 0;
for (String key : paramsMap.keySet()) {
if (pos > 0) {
tempParams.append("&");
}
String result = String.format("%s=%s", key, URLEncoder.encode(paramsMap.get(key),"utf-8"));
tempParams.append(result);
pos++;
}
String requestUrl = baseUrl + tempParams.toString();
// 新建一个URL对象
URL url = new URL(requestUrl);
// 打开一个HttpURLConnection连接
HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
// 设置连接主机超时时间
urlConn.setConnectTimeout(5 * 1000);
//设置从主机读取数据超时
urlConn.setReadTimeout(5 * 1000);
// 设置是否使用缓存 默认是true
urlConn.setUseCaches(true);
// 设置为 GET 请求
urlConn.setRequestMethod("GET");
//设置请求中的媒体类型信息。
urlConn.setRequestProperty("Content-Type", "application/json");
//设置客户端与服务连接类型
urlConn.addRequestProperty("Connection", "Keep-Alive");
// 开始连接
urlConn.connect();
// 判断请求是否成功
if (urlConn.getResponseCode() == 200) {
// 获取返回的数据
String result = streamToString(urlConn.getInputStream());
Log.e(TAG, "Get方式请求成功,result--->" + result);
} else {
Log.e(TAG, "Get方式请求失败");
}
// 关闭连接
urlConn.disconnect();
} catch (Exception e) {
Log.e(TAG, e.toString());
}
}
上面是一个非常简单的例子,通过这个例子我们大概能了解了其用法,相比于HttpClient来说,HttpUrlConnection的封装要非常简单的,只是一个最最基础的Http请求,也没有连接池什么概念的。
代码分析
首先我们创建一个URL对象,同时传入请求的远程地址
String url = "https://www.baidu.com/";
URL url = new URL(url);
public class URL {
public URL(String spec) throws MalformedURLException {
this((URL) null, spec, null);
}
public URL(URL context, String spec, URLStreamHandler handler) throws MalformedURLException {
.......
// 1. 首先我们会通过字符串正则表达式获取 协议头
protocol = UrlUtils.getSchemePrefix(spec);
int schemeSpecificPartStart = protocol != null ? (protocol.length() + 1) : 0;
.......
// 2. 然后根据具体的协议创建其具体的协议处理工具对象
if (streamHandler == null) {
setupStreamHandler();
if (streamHandler == null) {
throw new MalformedURLException("Unknown protocol: " + protocol);
}
}
//3. 最后解析剩下的url,将host,port,query分别分离出来
try {
streamHandler.parseURL(this, spec, schemeSpecificPartStart, spec.length());
} catch (Exception e) {
throw new MalformedURLException(e.toString());
}
}
void setupStreamHandler() {
//首先根据协议名称从缓存中获取是否存在对象的协议处理类。
streamHandler = streamHandlers.get(protocol);
if (streamHandler != null) {
return;
}
.......
//然后根据不同的协议然后创建不同处理对象
if (protocol.equals("file")) {
streamHandler = new FileHandler();
} else if (protocol.equals("ftp")) {
streamHandler = new FtpHandler();
} else if (protocol.equals("http")) {
//如果是Http协议的话通过反射的方式创建一个 HttpHandle对象,该类属于OKhttp包下的
try {
String name = "com.android.okhttp.HttpHandler";
streamHandler = (URLStreamHandler) Class.forName(name).newInstance();
} catch (Exception e) {
throw new AssertionError(e);
}
} else if (protocol.equals("https")) {
//如果是Https协议的话通过反射的方式创建一个 HttpHandle对象,该类属于OKhttp包下的
try {
String name = "com.android.okhttp.HttpsHandler";
streamHandler = (URLStreamHandler) Class.forName(name).newInstance();
} catch (Exception e) {
throw new AssertionError(e);
}
} else if (protocol.equals("jar")) {
streamHandler = new JarHandler();
}
//最后将创建的对象添加到缓存中
if (streamHandler != null) {
streamHandlers.put(protocol, streamHandler);
}
}
}
通过上面的代码我们知道在创建URL对象的时候,首先会对路径字符串进行分解,提取出host,端口,以及后面跟着的参数等等,最后会创建一个 OKhttp框架中的 HttpHandler对象。接着是调用 openConnection 方法获取HttpURLConnection对象,该类是一个抽象类。
//得到connection对象。
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
//这里的 streamHandler就是上面我们创建的 HttpHandler对象,这里我们需要跟踪OKHttp代码了。
public URLConnection openConnection() throws IOException {
return streamHandler.openConnection(this);
}
最后调用的是 OKHttp 中的HttpHandler类,通过发现我们发现创建了一个 OkHttpClient 对象。
public class HttpHandler extends URLStreamHandler {
protected URLConnection openConnection(URL url) throws IOException {
return newOkHttpClient(null).open(url);
}
protected OkHttpClient newOkHttpClient(Proxy proxy) {
OkHttpClient client = new OkHttpClient();
client.setFollowProtocolRedirects(false);
if (proxy != null) {
client.setProxy(proxy);
}
return client;
}
}
通过OKHttpClient的open方法我们可以看到最后会创建一个 HttpURLConnectionImpl对象,该类继承了 HttpURLConnection.java。
public class OkHttpClient {
public HttpURLConnection open(URL url) {
return open(url, proxy);
}
HttpURLConnection open(URL url, Proxy proxy) {
String protocol = url.getProtocol();
OkHttpClient copy = copyWithDefaults();
copy.proxy = proxy;
if (protocol.equals("http")) return new HttpURLConnectionImpl(url, copy);
if (protocol.equals("https")) return new HttpsURLConnectionImpl(url, copy);
throw new IllegalArgumentException("Unexpected protocol: " + protocol);
}
}
通过上面的代码跟踪和分析,最终我们知道 openConnection方法会根据协议的不同创建 HttpURLConnectionImpl 和HttpsURLConnectionImpl对象,这两个类都是继承了 URLConnection 类。从这里我们就可以分析得出 Android4.4及以上的版本底层的网络请求都是 OKhttp了。虽然 google公司才从Android6.0移除HttpClient的使用,但是却在Android4.4上集成了OkHttp,只是没有对外暴漏出来而已。
接着调用 HttpURLConnectionImpl 类的 connect方法连接服务器。
public class HttpURLConnectionImpl extends HttpURLConnection {
public final void connect() throws IOException {
//首先会根据协议来创建HttpEngine 和 HttpsEngine。
initHttpEngine();
boolean success;
do {
//这里调用 HttpEngine的 sendRequest() 方法
success = execute(false);
} while (!success);
}
private HttpEngine newHttpEngine(String method, RawHeaders requestHeaders,
Connection connection, RetryableOutputStream requestBody) throws IOException {
if (url.getProtocol().equals("http")) {
return new HttpEngine(client, this, method, requestHeaders, connection, requestBody);
} else if (url.getProtocol().equals("https")) {
return new HttpsEngine(client, this, method, requestHeaders, connection, requestBody);
} else {
throw new AssertionError();
}
}
}
通过上面的代码我们就能知道connect内部最后调用的是 HttpEngine 去实现的,到这里的话我们就不再继续分析 OKhttp 的代码,该框架的内部具体实现就留着以后继续来实现了。
总结
通过上面的代码分析我们大概了解了Android 平台在4.4及以上平台 HttpURLConnection底层使用的是开源框架 OKhttp,由于 oracle java的 rt.java的代码没有开源,这里我们也不在继续的分析了。其实 HttpURLConnection 只是定义一套标准,具体的实现则是由各自的平台或者是厂家发挥聪明才智去实现的。我们知道都知道 Http 协议都是应用层协议其底层协议是 TCP/IP 协议的,由此我们可以知道该框架底层的实现肯定是使用 Socket 来做的。目前市面上两个最出名的Java网络请求框架分别是 apache的HttpClient和 Square 公司的 okhttp。接下来的将对 OKhttp进行更深入的分析。