多年以前Android的网络请求只有Apache开源的HttpClient和JDK的HttpUrlConnection,近几年随着OkHttp的流行Android在高版本的SDK中加入了OkHttp。但在Android官方文档中推荐使用HttpUrlConnection并且其会一直被维护,所以在学习Android网络相关的知识时我们队HttpUrlConnection要有足够的了解。。。。

前几天因为时间的关系只画了图 HttpUrlConnection和Socket的关系图 ,本来说好的第二天续写,结果一直拖到了周末晚上。幸好时间还来的及,趁这短时间影响深刻,将自己解析代码过程记录下来。(PS:解析的过程有什么地方不明白的可以看看 HttpUrlConnection和Socket的关系图 图中讲出的过程和这次代码分析的过程是一样的,只不过代码讲述更加详细。所有源码都是来自Android4.0.4。有代码就有真相~!~!)

类结构图

先给大家展示一张相关类的结构图:

HttpUrlConnection 使用

在分析代码的时候我希望首相脑海中要有一个URL的请求过程。
这是我在网上摘的一个HttpUrlConnection请求小Demo:

public class EsmTest {    /** * 通过HttpURLConnection模拟post表单提交 * @throws Exception */    @Test    public void sendEms() throws Exception {        String wen = "MS2201828";        String btnSearch = "EMS快递查询";        URL url = new URL("http://www.kd185.com/ems.php");        HttpURLConnection conn = (HttpURLConnection) url.openConnection();        conn.setRequestMethod("POST");// 提交模式        // conn.setConnectTimeout(10000);//连接超时 单位毫秒        // conn.setReadTimeout(2000);//读取超时 单位毫秒        conn.setDoOutput(true);// 是否输入参数        StringBuffer params = new StringBuffer();        // 表单参数与get形式一样        params.append("wen").append("=").append(wen).append("&")              .append("btnSearch").append("=").append(btnSearch);        byte[] bypes = params.toString().getBytes();        conn.getOutputStream().write(bypes);// 输入参数        InputStream inStream=conn.getInputStream();        System.out.println(new String(StreamTool.readInputStream(inStream), "gbk"));    }    public void sendSms() throws Exception{        String message="货已发到";        message=URLEncoder.encode(message, "UTF-8");        System.out.println(message);        String path ="http://localhost:8083/DS_Trade/mobile/sim!add.do?message="+message;        URL url =new URL(path);        HttpURLConnection conn = (HttpURLConnection)url.openConnection();        conn.setConnectTimeout(5*1000);        conn.setRequestMethod("GET");        InputStream inStream = conn.getInputStream();            byte[] data = StreamTool.readInputStream(inStream);        String result=new String(data, "UTF-8");        System.out.println(result);    }}

URL产生请求

/*****************URL.java************************//** * 创建一个新的URL实例 */public URL(String spec) throws MalformedURLException {    this((URL) null, spec, null);}public URL(URL context, String spec, URLStreamHandler handler) throws MalformedURLException {    if (spec == null) {        throw new MalformedURLException();    }    if (handler != null) {        streamHandler = handler;    }    spec = spec.trim();    //获取url的协议类型,http,https    protocol = UrlUtils.getSchemePrefix(spec);    //请求开始部分的位置    int schemeSpecificPartStart = protocol != null ? (protocol.length() + 1) : 0;    if (protocol != null && context != null && !protocol.equals(context.protocol)) {        context = null;    }    if (context != null) {        set(context.protocol, context.getHost(), context.getPort(), context.getAuthority(),                context.getUserInfo(), context.getPath(), context.getQuery(),                context.getRef());        if (streamHandler == null) {            streamHandler = context.streamHandler;        }    } else if (protocol == null) {        throw new MalformedURLException("Protocol not found: " + spec);    }    //这里为重点,获取StreamHandler    if (streamHandler == null) {        setupStreamHandler();        if (streamHandler == null) {            throw new MalformedURLException("Unknown protocol: " + protocol);        }    }    try {        //对url的处理        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 (streamHandlerFactory != null) {        streamHandler = streamHandlerFactory.createURLStreamHandler(protocol);        if (streamHandler != null) {            streamHandlers.put(protocol, streamHandler);            return;        }    }    //在同名包下检测一个可用的hadnler    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) {            }        }    }    //如果还是没有创建成功那么new一个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();    }    if (streamHandler != null) {        streamHandlers.put(protocol, streamHandler);    }}
/** * streamHandler实现类为HttpURLConnectionImpl */public final class HttpHandler extends URLStreamHandler {    @Override protected URLConnection openConnection(URL u) throws IOException {        return new HttpURLConnectionImpl(u, getDefaultPort());    }    @Override protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {        if (url == null || proxy == null) {            throw new IllegalArgumentException("url == null || proxy == null");        }        return new HttpURLConnectionImpl(url, getDefaultPort(), proxy);    }    @Override protected int getDefaultPort() {        return 80;    }}

创建连接请求准备

/*****************HttpURLConnectionImpl.java start************************//** * 无论是get还是post都需要建立连接 * post */@Override public final OutputStream getOutputStream() throws IOException {    connect();    OutputStream result = httpEngine.getRequestBody();    if (result == null) {        throw new ProtocolException("method does not support a request body: " + method);    } else if (httpEngine.hasResponse()) {        throw new ProtocolException("cannot write request body after response has been read");    }    return result;}/** * 无论是get还是post都需要建立连接 * get */@Override public final InputStream getInputStream() throws IOException {    if (!doInput) {        throw new ProtocolException("This protocol does not support input");    }    //获取http响应    HttpEngine response = getResponse();    //返回400抛异常    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;}private HttpEngine getResponse() throws IOException {    //初始化http引擎    initHttpEngine();    //是否有响应头信息    if (httpEngine.hasResponse()) {        return httpEngine;    }    try {        while (true) {            //发送请求            httpEngine.sendRequest();            httpEngine.readResponse();            //为下次请求做准备            Retry retry = processResponseHeaders();            if (retry == Retry.NONE) {                httpEngine.automaticallyReleaseConnectionToPool();                break;            }            //如果一个请求不能完成那么接下来为下次请求做准备            String retryMethod = method;            OutputStream requestBody = httpEngine.getRequestBody();            /* * Although RFC 2616 10.3.2 specifies that a HTTP_MOVED_PERM * redirect should keep the same method, Chrome, Firefox and the * RI all issue GETs when following any redirect. */            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();            }            httpEngine.release(true);            httpEngine = newHttpEngine(retryMethod, rawRequestHeaders,                    httpEngine.getConnection(), (RetryableOutputStream) requestBody);        }        return httpEngine;    } catch (IOException e) {        httpEngineFailure = e;        throw e;    }}@Override public final void connect() throws IOException {    initHttpEngine();    try {        httpEngine.sendRequest();    } catch (IOException e) {        httpEngineFailure = e;        throw e;    }}/** * 无论是get还是post都需要初始化Http引擎 */private void initHttpEngine() throws IOException {    if (httpEngineFailure != null) {        throw httpEngineFailure;    } else if (httpEngine != null) {        return;    }    connected = true;    try {        if (doOutput) {            if (method == HttpEngine.GET) {                //如果要写入那么这就是一个post请求                method = HttpEngine.POST;            } else if (method != HttpEngine.POST && method != HttpEngine.PUT) {                //如果你要写入,那么不是post请求也不是put请求那就抛异常吧。                throw new ProtocolException(method + " does not support writing");            }        }        httpEngine = newHttpEngine(method, rawRequestHeaders, null, null);    } catch (IOException e) {        httpEngineFailure = e;        throw e;    }}

创建Socket连接

/********************HttpEngine.java**************//** * Figures out what the response source will be, and opens a socket to that * source if necessary. Prepares the request headers and gets ready to start * writing the request body if it exists. */public final void sendRequest() throws IOException {    if (responseSource != null) {        return;    }    //填充请求头和cookies    prepareRawRequestHeaders();    //初始化响应资源,计算缓存过期时间,判断是否读取缓冲中数据,或者进行网络请求    //responseSource = ?    //CACHE:返回缓存信息    //CONDITIONAL_CACHE:进行网络请求如果网络请求结果无效则使用缓存    //NETWORK:返回网络请求    initResponseSource();    //请求行为记录    if (responseCache instanceof HttpResponseCache) {        ((HttpResponseCache) responseCache).trackResponse(responseSource);    }    //请求资源需要访问网络,但请求头部禁止请求。在这种情况下使用BAD_GATEWAY_RESPONSE替代    if (requestHeaders.isOnlyIfCached() && responseSource.requiresConnection()) {        if (responseSource == ResponseSource.CONDITIONAL_CACHE) {            IoUtils.closeQuietly(cachedResponseBody);        }        this.responseSource = ResponseSource.CACHE;        this.cacheResponse = BAD_GATEWAY_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;    }}private void sendSocketRequest() throws IOException {    if (connection == null) {        connect();    }    if (socketOut != null || requestOut != null || socketIn != null) {        throw new IllegalStateException();    }    socketOut = connection.getOutputStream();    requestOut = socketOut;    socketIn = connection.getInputStream();    if (hasRequestBody()) {        initRequestBodyOut();    }}//打开Socket连接protected void connect() throws IOException {    if (connection == null) {        connection = openSocketConnection();    }}protected final HttpConnection openSocketConnection() throws IOException {    HttpConnection result = HttpConnection.connect(            uri, policy.getProxy(), requiresTunnel(), policy.getConnectTimeout());    Proxy proxy = result.getAddress().getProxy();    if (proxy != null) {        policy.setProxy(proxy);    }    result.setSoTimeout(policy.getReadTimeout());    return result;}
/********************HttpConnection.java**************/public static HttpConnection connect(URI uri, Proxy proxy, boolean requiresTunnel,        int connectTimeout) throws IOException {    //代理直连    if (proxy != null) {        Address address = (proxy.type() == Proxy.Type.DIRECT)                ? new Address(uri)                : new Address(uri, proxy, requiresTunnel);        return HttpConnectionPool.INSTANCE.get(address, connectTimeout);    }    //寻找代理直连    ProxySelector selector = ProxySelector.getDefault();    List<Proxy> proxyList = selector.select(uri);    if (proxyList != null) {        for (Proxy selectedProxy : proxyList) {            if (selectedProxy.type() == Proxy.Type.DIRECT) {                // the same as NO_PROXY                // TODO: if the selector recommends a direct connection, attempt that?                continue;            }            try {                Address address = new Address(uri, 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);            }        }    }    //创建一个直连接    return HttpConnectionPool.INSTANCE.get(new Address(uri), connectTimeout);}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 {            //DNS解析,socket连接(这块不做详细分析)            socketCandidate.connect(                    new InetSocketAddress(addresses[i], config.socketPort), connectTimeout);            break;        } catch (IOException e) {            if (i == addresses.length - 1) {                throw e;            }        }    }    this.socket = socketCandidate;}
/********************HttpConnectionPool.java**************/public HttpConnection get(HttpConnection.Address address, int connectTimeout)        throws IOException {    //首先尝试重用现有的HTTP连接。    synchronized (connectionPool) {        List<HttpConnection> connections = connectionPool.get(address);        if (connections != null) {            while (!connections.isEmpty()) {                HttpConnection connection = connections.remove(connections.size() - 1);                if (!connection.isStale()) { // TODO: this op does I/O!                    // Since Socket is recycled, re-tag before using                    final Socket socket = connection.getSocket();                    SocketTagger.get().tag(socket);                    return connection;                }            }            connectionPool.remove(address);        }    }    //无法找到可以复用的链接是,创建一个新的链接    return address.connect(connectTimeout);}/********************HttpConnection.Address.java**************/public HttpConnection connect(int connectTimeout) throws IOException {    return new HttpConnection(this, connectTimeout);}

输出内容获取

/********************HttpEngine.java**************/public final void readResponse() throws IOException {    //如果有响应头    if (hasResponse()) {        return;    }    //readResponse之前是否sendRequest    if (responseSource == null) {        throw new IllegalStateException("readResponse() without sendRequest()");    }    //如果不进行网络请求直接返回    if (!responseSource.requiresConnection()) {        return;    }    //刷新请求头    if (sentRequestMillis == -1) {        int contentLength = requestBodyOut instanceof RetryableOutputStream                ? ((RetryableOutputStream) requestBodyOut).contentLength()                : -1;        writeRequestHeaders(contentLength);    }    //刷新请求体    if (requestBodyOut != null) {        requestBodyOut.close();        if (requestBodyOut instanceof RetryableOutputStream) {            ((RetryableOutputStream) requestBodyOut).writeToSocket(requestOut);        }    }    requestOut.flush();    requestOut = socketOut;    //解析响应头    readResponseHeaders();    responseHeaders.setLocalTimestamps(sentRequestMillis, System.currentTimeMillis());    //判断响应体类型    if (responseSource == ResponseSource.CONDITIONAL_CACHE) {        if (cachedResponseHeaders.validate(responseHeaders)) {            if (responseCache instanceof HttpResponseCache) {                ((HttpResponseCache) responseCache).trackConditionalCacheHit();            }            //释放资源            release(true);            //返回缓存信息            setResponse(cachedResponseHeaders.combine(responseHeaders), cachedResponseBody);            return;        } else {            IoUtils.closeQuietly(cachedResponseBody);        }    }    if (hasResponseBody()) {        maybeCache(); // reentrant. this calls into user code which may call back into this!    }    initContentStream(getTransferStream());}private InputStream getTransferStream() throws IOException {    if (!hasResponseBody()) {        return new FixedLengthInputStream(socketIn, cacheRequest, this, 0);    }    if (responseHeaders.isChunked()) {        return new ChunkedInputStream(socketIn, cacheRequest, this);    }    if (responseHeaders.getContentLength() != -1) {        return new FixedLengthInputStream(socketIn, cacheRequest, this,                responseHeaders.getContentLength());    }    return new UnknownLengthHttpInputStream(socketIn, cacheRequest, this);}private void initContentStream(InputStream transferStream) throws IOException {    //是否gzip压缩    if (transparentGzip && responseHeaders.isContentEncodingGzip()) {        responseHeaders.stripContentEncoding();        responseBodyIn = new GZIPInputStream(transferStream);    } else {        responseBodyIn = transferStream;    }}

整个请求的响应流程大概就是这样子的,其中的涉及的路由信息获取,DNS解析与缓存,请求的缓存过期等都还没有仔细研读。不过这些也够自己消化一段时间了^_^,相信自己现在回过头来看OkHttp的实现应该不是那么困难了。

默默肃立的路灯,像等待检阅的哨兵,站姿笔挺,瞪着炯炯有神的眼睛,时刻守护着这城市的安宁。一排排、一行行路灯不断向远方延伸,汇聚成了一支支流光溢彩的河流,偶尔有汽车疾驰而去,也是一尾尾鱼儿在河里游动。夜已深~!~!~!

更多相关文章

  1. Android(安卓)缓存框架 ASimpleCache
  2. Android关于ViewPager+Fragment缓存问题
  3. Android清除所有应用的缓存(包括系统应用)的两种实现方法
  4. ANDROID Porting系列五、定制化
  5. android 网络图片双缓存
  6. Android(安卓)Asynchronous Http Client-Android异步网络请求客
  7. Android异步加载图像(含线程池,缓存方法)
  8. Android网络之HttpUrlConnection和Socket关系图解
  9. Android练手小项目(KTReader)基于mvp架构(三)

随机推荐

  1. 史上最全的android studio 插件大全整理
  2. Android之ActionBar学习
  3. 在Android Studio中使用Wifi连接Android
  4. 安卓boot recovery解包打包
  5. android 适配
  6. Android中基于NuPlayer的RTSP框架学习
  7. AIDL实现进程间通信
  8. Android Power Management 实现
  9. 转:android ro.debuggable属性调试修改(mp
  10. Android初级程序员需要掌握的知识