彻底掌握网络通信(十三)HttpURLConnection进行网络请求深度分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yi_master/article/details/80893194

彻底掌握网络通信(一)Http协议基础知识
彻底掌握网络通信(二)Apache的HttpClient基础知识
彻底掌握网络通信(三)Android源码中HttpClient的在不同版本的使用
彻底掌握网络通信(四)Android源码中HttpClient的发送框架解析
彻底掌握网络通信(五)DefaultRequestDirector解析
彻底掌握网络通信(六)HttpRequestRetryHandler解析
彻底掌握网络通信(七)ConnectionReuseStrategy,ConnectionKeepAliveStrategy解析
彻底掌握网络通信(八)AsyncHttpClient源码解读
彻底掌握网络通信(九)AsyncHttpClient为什么无法用Fiddler来抓包
彻底掌握网络通信(十)AsyncHttpClient如何发送JSON解析JSON,以及一些其他用法
彻底掌握网络通信(十一)HttpURLConnection进行网络请求的知识准备
彻底掌握网络通信(十二)HttpURLConnection进行网络请求概览

  1. Url的组成

    一个完整的UrL的示例如下:

http://www.runoob.com/index.html?language=cn#j2se

通过代码我来看下Url类的打印结果

         URL url = new URL("http://www.runoob.com/index.html?language=cn#j2se");
         System.out.println("URL 为:" + url.toString());
         System.out.println("协议为:" + url.getProtocol());
         System.out.println("验证信息:" + url.getAuthority());
         System.out.println("文件名及请求参数:" + url.getFile());
         System.out.println("主机名:" + url.getHost());
         System.out.println("路径:" + url.getPath());
         System.out.println("端口:" + url.getPort());
         System.out.println("默认端口:" + url.getDefaultPort());
         System.out.println("请求参数:" + url.getQuery());
         System.out.println("定位位置:" + url.getRef());

看下输出

URL 为:http://www.runoob.com/index.html?language=cn#j2se
协议为:http
验证信息:www.runoob.com
文件名及请求参数:/index.html?language=cn
主机名:www.runoob.com
路径:/index.html
端口:-1
默认端口:80(HTTP 协议默认的端口号为 80)
请求参数:language=cn
定位位置:j2se (定位到网页中 id 属性为 j2se 的 HTML 元素位置 )

2 . Android中使用HttpUrlConnection连接网络的第一步,创建URL和HttpHandler

    public URL(String spec) throws MalformedURLException {
        this(null, spec);
    }

最终会调用如下代码完成url和httphandler的创建,并对URL类中8各重要成员通过URLStreamHandler完成赋值

    public URL(URL context, String spec, URLStreamHandler handler)
        throws MalformedURLException
    {
        /**删除部分代码,删除的代码是对spec的特殊处理,如去除空格等等*/
        try {
            /**获得URLStreamHandler**/
            if (handler == null &&
                (handler = getURLStreamHandler(protocol)) == null) {
                throw new MalformedURLException("unknown protocol: "+protocol);
            }
            /**删除部分代码*/
            //通过URLSteamHandler来设置URL.java中的几个成员属性,即
            //(String protocol, String host, int port,String authority, String userInfo, String path,String query, String ref)
            //含义分别是协议名称,如http; 
            //host; 
            //端口; 
            //URL中的授权部分;
            //URL中的信用信息部分;
            //URL中的path
            //Url中的query
            //Url中的ref
            handler.parseURL(this, spec, start, limit);
        }
    }

这里我们重点看下HttpHandler是如何创建的,参见第8行的getURLStreamHandler方法

    static URLStreamHandler getURLStreamHandler(String protocol) {

        URLStreamHandler handler = handlers.get(protocol);
        if (handler == null) {

            boolean checkedWithFactory = false;

            // Use the factory (if any),刚开始factory == null;
            if (factory != null) {
                handler = factory.createURLStreamHandler(protocol);
                checkedWithFactory = true;
            }

            // Try java protocol handler,先通过java属性去获得URLStreamHandler           
            if (handler == null) {
                /*省略部分代码*/
            }

            // Fallback to built-in stream handler.
            // Makes okhttp the default http/https handler,使用系统http/https去获取URLStreamHandler
            if (handler == null) {
                try {
                    // BEGIN Android-changed
                    // Use of okhttp for http and https
                    // Removed unnecessary use of reflection for sun classes
                    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();
                    }
                    // END Android-changed
                } catch (Exception e) {
                    throw new AssertionError(e);
                }
            }

            synchronized (streamHandlerLock) {

                URLStreamHandler handler2 = null;
/*省略部分代码*/             
                // Insert this handler into the hashtable
                if (handler != null) {
                    handlers.put(protocol, handler);
                }

            }
        }

        return handler;

    }

getURLStreamHandler方法主要逻辑有三个步骤

  • 尝试从系统设置中获取URLStreamHandler,代码参见第15行,不过这个if判断是获取不到的,顾直接跳转到第21行就好

  • 如果从系统设置获取URLStreamHandler失败,则尝试自己构建URLStreamHandler,参看第21行

  • 如果构建成功,将URLStreamHandler的实例放入hashtable中

第26行,如果协议是file,则构建sun.net.www.protocol.file.Handler,其代码位置在源码的libcore\ojluni\src\main\java\sun\net\www\protocol\file路径下,Handler类也是基础自URLStreamHandler
第32行,如果协议是http,则构建com.android.okhttp.HttpHandler

疑问1:在源码中找不到com.android.okhttp.HttpHandler
这里会使用jar重打包技术,将com.android.okhttp.HttpHandler重新命名为com.squareup.okhttp.HttpHandler

疑问2:HttpHandler到底是什么
这里写图片描述

URLStreamHandler.java的作用是:他是一个抽象类,针对具体的协议来创建具体的链接;

  • HttpHandler是针对http协议而创建的协议处理者;

  • HttpsHandler是针对https协议而创建的协议处理者;

3 . Android中使用HttpUrlConnection连接网络的第二步,调用openConnection

/** URL.JAVA*/
    public URLConnection openConnection() throws java.io.IOException {
        return handler.openConnection(this);
    }

针对http协议他会调用HttpHandler.java的openConnection

    @Override protected URLConnection openConnection(URL url) throws IOException {
        return newOkUrlFactory(null /* proxy */).open(url);
    }

    @Override protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {
        if (url == null || proxy == null) {
            throw new IllegalArgumentException("url == null || proxy == null");
        }
        return newOkUrlFactory(proxy).open(url);
    }

在这个方法里面,通过创建OkUrlFactory工厂类,并调用其open方法,返回Http协议的具体实现者HttpURLConnectionImpl,通过HttpURLConnectionImpl我们就可以连接socket,获取响应了

接下来我们具体看下newOkUrlFactory方法

    protected OkUrlFactory newOkUrlFactory(Proxy proxy) {
        OkUrlFactory okUrlFactory = createHttpOkUrlFactory(proxy);
        // For HttpURLConnections created through java.net.URL Android uses a connection pool that
        // is aware when the default network changes so that pooled connections are not re-used when
        // the default network changes.
        okUrlFactory.client().setConnectionPool(configAwareConnectionPool.get());
        return okUrlFactory;
    }
    public static OkUrlFactory createHttpOkUrlFactory(Proxy proxy) {
        OkHttpClient client = new OkHttpClient();

        // Explicitly set the timeouts to infinity.
        client.setConnectTimeout(0, TimeUnit.MILLISECONDS);
        client.setReadTimeout(0, TimeUnit.MILLISECONDS);
        client.setWriteTimeout(0, TimeUnit.MILLISECONDS);

        // Set the default (same protocol) redirect behavior. The default can be overridden for
        // each instance using HttpURLConnection.setInstanceFollowRedirects().
        client.setFollowRedirects(HttpURLConnection.getFollowRedirects());

        // Do not permit http -> https and https -> http redirects.
        client.setFollowSslRedirects(false);

        // Permit cleartext traffic only (this is a handler for HTTP, not for HTTPS).
        client.setConnectionSpecs(CLEARTEXT_ONLY);

        // When we do not set the Proxy explicitly OkHttp picks up a ProxySelector using
        // ProxySelector.getDefault().
        if (proxy != null) {
            client.setProxy(proxy);
        }

        // OkHttp requires that we explicitly set the response cache.
        OkUrlFactory okUrlFactory = new OkUrlFactory(client);

        // Use the installed NetworkSecurityPolicy to determine which requests are permitted over
        // http.
        okUrlFactory.setUrlFilter(CLEARTEXT_FILTER);

        ResponseCache responseCache = ResponseCache.getDefault();
        if (responseCache != null) {
            AndroidInternal.setResponseCache(okUrlFactory, responseCache);
        }
        return okUrlFactory;
    }

这个方法很简单,其步骤如下

  • 第2行 new一个OkHttpClient

  • 第5行到底23行,设置OkHttpClient 实例的属性

  • new一个OkUrlFactory工厂类,传入OkHttpClient实例

根据上面的分析,openConnection会调用OkUrlFactory的open方法

  public HttpURLConnection open(URL url) {
    return open(url, client.getProxy());
  }

  HttpURLConnection open(URL url, Proxy proxy) {
    String protocol = url.getProtocol();
    OkHttpClient copy = client.copyWithDefaults();
    copy.setProxy(proxy);

    if (protocol.equals("http")) return new HttpURLConnectionImpl(url, copy, urlFilter);
    if (protocol.equals("https")) return new HttpsURLConnectionImpl(url, copy, urlFilter);
    throw new IllegalArgumentException("Unexpected protocol: " + protocol);
  }
  • 第7行中,通过copyWithDefaults方法复制一个OkHttpClient,copyWithDefaults方法主要是当OkHttpClient某些设置没有被配置的时候,使用系统属性配置

  • 第10行,通过OkHttpClient的副本我们就可以创建HttpURLConnectionImpl实例,这样后续的socket的链接和数据发送接收就交给它了

我们来总结下openConnection的步骤

  • 创建OkHttpClient实例,该类的作用是配置一个http连接,设置在http连接中用到的各种参数;打个比方就是他是一个用户界面,通过这个界面,你可以设置这个系统的参数

  • 使用上面的OkHttpClient实例,去创建一个OkUrlFactory实例

  • 调用OkUrlFactory的Open方法完成具体的一个Http连接的实现者,这里是HttpURLConnectionImpl;所以OkUrlFactory的作用就是根据具体的协议创建不同的协议实现者

依旧沿用之前的导弹发射故事:

在OkHttpClient这个操作界面上,操作人员通过界面设置各种参数,点击发送的时候,OkUrlFactory判断是发射一枚火箭,则构建火箭实例,即此处的HttpURLConnectionImpl;

疑问3:在步骤2中,setConnectionPool作用是什么

疑问4:在步骤2中,ResponseCache的作用是什么

带着问题,我们将进入下一篇文章

猜你喜欢

转载自blog.csdn.net/yi_master/article/details/80893194