Android HttpUrlConnection 分析

前言

在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进行更深入的分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值