Android每週一輪子:HttpURLConnection

序言

接着上一篇的Volley,本篇原定計劃是OkHttp的,可是在分析道OKhttp底層時,對於IO的包裝等等特性,須要一個可參照的對比的例子,好比HttpURLConnection等,經過這種對比,才能夠看的出其優點。對於Volley,其實只是對於底層網絡庫的封裝,真正的網絡請求的發起仍是經過HttpStack來執行,HttpStack在此以前可選的爲HttpClient和HttpURLConnection。這裏針對HttpURLConnection展開進行分析。分析這樣的一個網絡庫是如何實現的。本代碼基於Android 4.3,4.4和其以後底層的實現採用了OkHttp。java

基礎使用

URL url = new URL("http://www.android.com/");
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
try {
     InputStream in = new BufferedInputStream(urlConnection.getInputStream());
     readStream(in);
} finally {
     urlConnection.disconnect();
}
複製代碼

創建鏈接以後,得到一個InputStream,咱們就能夠從中讀取鏈接創建後的返回數據。若是咱們須要在請求中添加參數也能夠經過獲取一個輸出流,在輸出流中寫入咱們的請求數據。而其底層背後就是創建的一個Socket。android

Socket模型

源碼實現

首先是獲取HttpURLConnection緩存

HttpURLConnection創建流程

首先調用了URL的openConnection方法。bash

public URLConnection openConnection() throws java.io.IOException {
      return handler.openConnection(this);
 }
複製代碼

經過源碼能夠看出,對於鏈接的操做都是經過URLStreamHandler來進行的,對於URLStreamHandler的建立是在URL的構造函數之中。網絡

public URL(URL context, String spec, URLStreamHandler handler) throws MalformedURLException {
    ....
    if (streamHandler == null) {
        setupStreamHandler();
        if (streamHandler == null) {
            throw new MalformedURLException("Unknown protocol: " + protocol);
        }
    }
   try {
        streamHandler.parseURL(this, spec, schemeSpecificPartStart, spec.length());
    } catch (Exception e) {
        throw new MalformedURLException(e.toString());
    }
}
複製代碼

StreamHandler

對於StreamHandler,有多個子類,分別能夠用來進行http,https,ftp等協議流的處理。下面是HttpHandler的建立過程。socket

void setupStreamHandler() {
  //檢查是否有緩存的處理相應協議的StreamHandler
    streamHandler = streamHandlers.get(protocol);
    if (streamHandler != null) {
        return;
    }

    //若是streamHandlerFactory不爲空,經過其建立streamHandler,並將其緩存下來
    if (streamHandlerFactory != null) {
        streamHandler = streamHandlerFactory.createURLStreamHandler(protocol);
        if (streamHandler != null) {
            streamHandlers.put(protocol, streamHandler);
            return;
        }
    }

    //從用戶提供的包中加載相應的StreamHandler,建立相應的實例,並加入到內存緩存中,按照制定的路徑
    String packageList = System.getProperty("java.protocol.handler.pkgs");
    ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
    if (packageList != null && contextClassLoader != null) {
        for (String packageName : packageList.split("\\|")) {
            String className = packageName + "." + protocol + ".Handler";
            try {
                Class<?> c = contextClassLoader.loadClass(className);
                streamHandler = (URLStreamHandler) c.newInstance();
                if (streamHandler != null) {
                    streamHandlers.put(protocol, streamHandler);
                }
                return;
            } catch (IllegalAccessException ignored) {
            } catch (InstantiationException ignored) {
            } catch (ClassNotFoundException ignored) {
            }
        }
    }

    // 若是用戶沒有提供,則會根據協議的要求,加載相應的Handler
    if (protocol.equals("file")) {
        streamHandler = new FileHandler();
    } else if (protocol.equals("ftp")) {
        streamHandler = new FtpHandler();
    } else if (protocol.equals("http")) {
        streamHandler = new HttpHandler();
    } else if (protocol.equals("https")) {
        streamHandler = new HttpsHandler();
    } else if (protocol.equals("jar")) {
        streamHandler = new JarHandler();
    }
    //將Handler加入到緩存
    if (streamHandler != null) {
        streamHandlers.put(protocol, streamHandler);
    }
}
複製代碼

HttpHandler創建過程

首先判斷是否已經有建立,從Handler列表中獲取,若是沒有判斷Handler工廠是否存在,若是不存在,加載本地的指定路徑,從中加載並建立相應的實例,最後若是本地路徑也沒有,則根據協議的類型,建立相應的協議Handler。ide

  • HttpHandler

這裏咱們只針對Http協議來看,跟進下HttpHandler的源碼,來了解一下其實現。Handler的鏈接創建,經過HttpURLConnectionImpl實例來進行。函數

protected URLConnection openConnection(URL u) throws IOException {
    return new HttpURLConnectionImpl(u, getDefaultPort());
}
複製代碼
  • 獲取鏈接的輸出流
public final InputStream getInputStream() throws IOException {
    if (!doInput) {
        throw new ProtocolException("This protocol does not support input");
    }

    HttpEngine response = getResponse();

    if (getResponseCode() >= HTTP_BAD_REQUEST) {
        throw new FileNotFoundException(url.toString());
    }

    InputStream result = response.getResponseBody();
    if (result == null) {
        throw new IOException("No response body exists; responseCode=" + getResponseCode());
    }
    return result;
}
複製代碼
  • HttpEngine的創建

調用getResponse得到HttpEngine對象,從中獲取請求的內容。ui

private HttpEngine getResponse() throws IOException {
    //初始化HttpEngine
    initHttpEngine();
    //判斷HttpEngine若是有響應直接返回
    if (httpEngine.hasResponse()) {
        return httpEngine;
    }

    while (true) {
        try {
            httpEngine.sendRequest();
            httpEngine.readResponse();
        } catch (IOException e) {
            OutputStream requestBody = httpEngine.getRequestBody();
            if (httpEngine.hasRecycledConnection()
                    && (requestBody == null || requestBody instanceof RetryableOutputStream)) {
                httpEngine.release(false);
                httpEngine = newHttpEngine(method, rawRequestHeaders, null,
                        (RetryableOutputStream) requestBody);
                continue;
            }
            httpEngineFailure = e;
            throw e;
        }

        Retry retry = processResponseHeaders();
        if (retry == Retry.NONE) {
            httpEngine.automaticallyReleaseConnectionToPool();
            return httpEngine;
        }

        String retryMethod = method;
        OutputStream requestBody = httpEngine.getRequestBody();

        int responseCode = getResponseCode();
        if (responseCode == HTTP_MULT_CHOICE || responseCode == HTTP_MOVED_PERM
                || responseCode == HTTP_MOVED_TEMP || responseCode == HTTP_SEE_OTHER) {
            retryMethod = HttpEngine.GET;
            requestBody = null;
        }

        if (requestBody != null && !(requestBody instanceof RetryableOutputStream)) {
            throw new HttpRetryException("Cannot retry streamed HTTP body",
                    httpEngine.getResponseCode());
        }

        if (retry == Retry.DIFFERENT_CONNECTION) {
            httpEngine.automaticallyReleaseConnectionToPool();
        } else {
            httpEngine.markConnectionAsRecycled();
        }

        httpEngine.release(true);
        //建立HttpEngine
        httpEngine = newHttpEngine(retryMethod, rawRequestHeaders,
                httpEngine.getConnection(), (RetryableOutputStream) requestBody);
    }

}
複製代碼

根據上述方法中的核心調用,逐步展開,首先是初始化HttpEngine,並建立其實例。this

private void initHttpEngine() throws IOException {
    if (httpEngineFailure != null) {
        throw httpEngineFailure;
    } else if (httpEngine != null) {
        return;
    }

    connected = true;
    try {
        if (doOutput) {
            if (method == HttpEngine.GET) {
                // they are requesting a stream to write to. This implies a POST method
                method = HttpEngine.POST;
            } else if (method != HttpEngine.POST && method != HttpEngine.PUT) {
                // If the request method is neither POST nor PUT, then you're not writing throw new ProtocolException(method + " does not support writing"); } } httpEngine = newHttpEngine(method, rawRequestHeaders, null, null); } catch (IOException e) { httpEngineFailure = e; throw e; } } 複製代碼
protected HttpEngine newHttpEngine(String method, RawHeaders requestHeaders,
        HttpConnection connection, RetryableOutputStream requestBody) throws IOException {
    return new HttpEngine(this, method, requestHeaders, connection, requestBody);
}
複製代碼

在newHttpEngie中,new了一個HttpEngine的實例。

public HttpEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders,
        HttpConnection connection, RetryableOutputStream requestBodyOut) throws IOException {
    this.policy = policy;
    this.method = method;
    this.connection = connection;
    this.requestBodyOut = requestBodyOut;

    try {
        uri = policy.getURL().toURILenient();
    } catch (URISyntaxException e) {
        throw new IOException(e);
    }

    this.requestHeaders = new RequestHeaders(uri, new RawHeaders(requestHeaders));
}
複製代碼

在執行完了HttpEngine的初始化方法以後,調用了其sendRequest方法,首先會進行緩存的判斷,最後會判斷其是否須要鏈接,若是須要,則會調用相應的鏈接方法:sendSocketRequest。

public final void sendRequest() throws IOException {
    if (responseSource != null) {
        return;
    }

    prepareRawRequestHeaders();
    initResponseSource();
    if (responseCache instanceof ExtendedResponseCache) {
        ((ExtendedResponseCache) responseCache).trackResponse(responseSource);
    }


    if (requestHeaders.isOnlyIfCached() && responseSource.requiresConnection()) {
        if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
            IoUtils.closeQuietly(cachedResponseBody);
        }
        this.responseSource = ResponseSource.CACHE;
        this.cacheResponse = GATEWAY_TIMEOUT_RESPONSE;
        RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(cacheResponse.getHeaders());
        setResponse(new ResponseHeaders(uri, rawResponseHeaders), cacheResponse.getBody());
    }

    if (responseSource.requiresConnection()) {
        //放鬆socket創建請求
        sendSocketRequest();
    } else if (connection != null) {
        HttpConnectionPool.INSTANCE.recycle(connection);
        connection = null;
    }
}
複製代碼

首先判斷connection是否爲空,若是爲空,調用connect方法,而後得到該鏈接的OutputStream,InputStream。

private void sendSocketRequest() throws IOException {
    if (connection == null) {
        connect();
    }

    if (socketOut != null || requestOut != null || socketIn != null) {
        throw new IllegalStateException();
    }
    //創建socket後,返回其讀寫流
    socketOut = connection.getOutputStream();
    requestOut = socketOut;
    socketIn = connection.getInputStream();

    if (hasRequestBody()) {
        initRequestBodyOut();
    }
}
複製代碼

實際鏈接過程調用

protected void connect() throws IOException {
    if (connection == null) {
        connection = openSocketConnection();
    }
}
複製代碼

開啓Socket鏈接,這裏調用了HttpConnect的鏈接函數。

protected final HttpConnection openSocketConnection() throws IOException {
    HttpConnection result = HttpConnection.connect(uri, getSslSocketFactory(),
            policy.getProxy(), requiresTunnel(), policy.getConnectTimeout());
    Proxy proxy = result.getAddress().getProxy();
    if (proxy != null) {
        policy.setProxy(proxy);
    }
    result.setSoTimeout(policy.getReadTimeout());
    return result;
}
複製代碼

對於具體的鏈接任務交給了HttpConnection來處理,調用其鏈接方法。會從鏈接池中獲取相應的鏈接,調用其get方法。

public static HttpConnection connect(URI uri, SSLSocketFactory sslSocketFactory,
        Proxy proxy, boolean requiresTunnel, int connectTimeout) throws IOException {

    if (proxy != null) {
        Address address = (proxy.type() == Proxy.Type.DIRECT)
                ? new Address(uri, sslSocketFactory)
                : new Address(uri, sslSocketFactory, proxy, requiresTunnel);
        return HttpConnectionPool.INSTANCE.get(address, connectTimeout);
    }

    /*
     * Try connecting to each of the proxies provided by the ProxySelector
     * until a connection succeeds.
     */
    ProxySelector selector = ProxySelector.getDefault();
    List<Proxy> proxyList = selector.select(uri);
    if (proxyList != null) {
        for (Proxy selectedProxy : proxyList) {
            if (selectedProxy.type() == Proxy.Type.DIRECT) {
                continue;
            }
            try {
                Address address = new Address(uri, sslSocketFactory,
                        selectedProxy, requiresTunnel);
                return HttpConnectionPool.INSTANCE.get(address, connectTimeout);
            } catch (IOException e) {
                // failed to connect, tell it to the selector
                selector.connectFailed(uri, selectedProxy.address(), e);
            }
        }
    }

    /*
     * Try a direct connection. If this fails, this method will throw.
     */
    return HttpConnectionPool.INSTANCE.get(new Address(uri, sslSocketFactory), connectTimeout);
}
複製代碼

根據傳遞的請求,從HttpConnectionPool中獲取HttpConnection鏈接。當其中不存在該鏈接的時候,從新建立一個實例,而後返回。

public HttpConnection get(HttpConnection.Address address, int connectTimeout)
        throws IOException {
    // First try to reuse an existing HTTP connection.
    synchronized (connectionPool) {
        List<HttpConnection> connections = connectionPool.get(address);
        while (connections != null) {
            HttpConnection connection = connections.remove(connections.size() - 1);
            if (connections.isEmpty()) {
                connectionPool.remove(address);
                connections = null;
            }
            if (connection.isEligibleForRecycling()) {
                // Since Socket is recycled, re-tag before using
                Socket socket = connection.getSocket();
                SocketTagger.get().tag(socket);
                return connection;
            }
        }
    }

    //當咱們沒法找到一個可用的鏈接,這個時候,咱們須要從新建立一個新的鏈接
    return address.connect(connectTimeout);
}
複製代碼

Address 爲HttpConnection的一個內部類。

public HttpConnection connect(int connectTimeout) throws IOException {
    return new HttpConnection(this, connectTimeout);
}
複製代碼

此時會再建立一個新的鏈接 ,在HttpConnection的構造函數之中是真正的socket鏈接創建的地方。

private HttpConnection(Address config, int connectTimeout) throws IOException {
    this.address = config;
    Socket socketCandidate = null;
    InetAddress[] addresses = InetAddress.getAllByName(config.socketHost);
    for (int i = 0; i < addresses.length; i++) {
        socketCandidate = (config.proxy != null && config.proxy.type() != Proxy.Type.HTTP)
                ? new Socket(config.proxy)
                : new Socket();
        try {
            socketCandidate.connect(
                    new InetSocketAddress(addresses[i], config.socketPort), connectTimeout);
            break;
        } catch (IOException e) {
            if (i == addresses.length - 1) {
                throw e;
            }
        }
    }

    this.socket = socketCandidate;
}
複製代碼

請求體的寫入,得到Socket的寫入流,而後將咱們的請求數據寫入

private void writeRequestHeaders(int contentLength) throws IOException {
    if (sentRequestMillis != -1) {
        throw new IllegalStateException();
    }

    RawHeaders headersToSend = getNetworkRequestHeaders();
    byte[] bytes = headersToSend.toHeaderString().getBytes(Charsets.ISO_8859_1);

    if (contentLength != -1 && bytes.length + contentLength <= MAX_REQUEST_BUFFER_LENGTH) {
        requestOut = new BufferedOutputStream(socketOut, bytes.length + contentLength);
    }

    sentRequestMillis = System.currentTimeMillis();
    requestOut.write(bytes);
}
複製代碼

鏈接的創建過程

小結

對於HttpURLConnection庫,是一個相對比較簡單的網絡庫,最開始經過根據設置的URL信息,建立一個Socket鏈接,而後得到Socket鏈接後獲得Socket的InputStream和OutputStream,而後經過其獲取數據和寫入數據,其內部提供的功能比較少,僅限於幫助咱們作一些簡單的http的包裝,核心類是HttpConnection,HttpEngine兩個類。

相關文章
相關標籤/搜索