http(S)系列之(五):android之HttpURLConnection源码解析(2):URL创建

参考

HttpURLConnection 源码解析图解,对照如下图片解析会更加清晰。(和本章节内容实际有出入,但是可以作为参考)
在这里插入图片描述
这里有个非常诡异的现象,采用debug模式在URL中不是很准确,起码URL构造方法断点打了就没有走,还有其他的很多不是自己想要的东西(志不在此,我们先不管)
在这里插入图片描述

URL创建

URL url = new URL(urlStr);

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

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

public URL(URL context, String spec, URLStreamHandler handler)
    throws MalformedURLException
 {
 	//基础类型存储于栈中:重新创建一个orginal变量,对spec的操作不影响spec
 	//如果这里是对象类型,那么栈中创建的是original的引用变量,指向堆中的真实对象(和spec的引用变量指向同一个对象),spec里的对象发生变化必然会影响到orginal
	String original = spec;//把spec复制一份出来保留为原始值,然后在对其进行操作
	
	//limit表示spec地址的长度,i相当于一个操作在start~limit区间的始终向前滑动的游标,c表示i滑动到limit位置的字符串对应ascii码值
	//理解一下:例如spec = "https://xxx",首先我们需要找到protocaol协议,i需要滑动到‘:’然后停下,那么c对应的值就是':'的ascii码值,这样做的目的就是通过i的滑动找到protocol协议为https。
    int i, limit, c;//i,c用于解析URL地址
    
     int start = 0;//有效的开始位置,比如spec给的地址前面有很多空格,那么需要先处理掉,start的值就是从有效的字符开始算起
     
     String newProtocol = null;//用于存储spec的协议类型,最终会赋值给全局变量protocol
     boolean aRef=false;//这里用于判断是否存在#:一个承接上下文的引用(表示不熟)
     boolean isRelative = false;//判断当前传入的地址是否全路径地址,否则为全路径地址

	// Check for permission to specify a handler
     if (handler != null) {//这里的handler == null
     	 //SecurityManager在Java中被用来检查应用程序是否能访问一些有限的资源,下面单独提一下该知识点
          SecurityManager sm = System.getSecurityManager();
          if (sm != null) {
              checkSpecifyHandler(sm);
          }
      }

	try {
         limit = spec.length();
         //如果地址末尾存在空格,逐一去除
         while ((limit > 0) && (spec.charAt(limit - 1) <= ' ')) {
             limit--;        //eliminate trailing whitespace
         }
         //如果地址前面有多余的空格,逐一去除
         while ((start < limit) && (spec.charAt(start) <= ' ')) {
             start++;        // eliminate leading whitespace
         }

		//用于判断spec的地址前四个字符是否是url:,如果是start前移4位
         if (spec.regionMatches(true, start, "url:", 0, 4)) {
             start += 4;
         }
         //如果进过以上处理的地址的首字符是#,这里判断是引用字符,aRef = true
         if (start < spec.length() && spec.charAt(start) == '#') {
             /* we're assuming this is a ref relative to the context URL.
              * This means protocols cannot start w/ '#', but we must parse
              * ref URL's like: "hello:there" w/ a ':' in them.
              */
             aRef=true;
         }
         
		 //以下目的:找到有效的网络协议,比如http,https;并且start继续前移用于查找URL地址的后面部分
		 //这里必须对URL地址有所了解,否则这里看起来确实费劲
         for (i = start ; !aRef && (i < limit) &&
                  ((c = spec.charAt(i)) != '/') ; i++) {
             if (c == ':') {

                 String s = spec.substring(start, i).toLowerCase();
                 if (isValidProtocol(s)) {
                     newProtocol = s;
                     start = i + 1;
                 }
                 break;
             }
         }

         // Only use our context if the protocols match.
         protocol = newProtocol;
         //如果context URL不为空,新获取的协议为空,首先忽略context中的协议大小写,然后将context 中的handler赋值并返回
         //这里的context是通过构造方法传递的,这里是null
         if ((context != null) && ((newProtocol == null) ||
                         newProtocol.equalsIgnoreCase(context.protocol))) {
             // inherit the protocol handler from the context
             // if not specified to the constructor
             if (handler == null) {
                 handler = context.handler;
             }

             // If the context is a hierarchical URL scheme and the spec
             // contains a matching scheme then maintain backwards
             // compatibility and treat it as if the spec didn't contain
             // the scheme; see 5.2.3 of RFC2396
             if (context.path != null && context.path.startsWith("/"))
                 newProtocol = null;
			//以下赋值协议,主机host,端口port,路径path等
			//isRelative = true可以看出是相对路径,context是上一次赋值,这里可以直接获取
             if (newProtocol == null) {
                 protocol = context.protocol;
                 authority = context.authority;
                 userInfo = context.userInfo;
                 host = context.host;
                 port = context.port;
                 file = context.file;
                 path = context.path;
                 isRelative = true;
             }
         }

         if (protocol == null) {
             throw new MalformedURLException("no protocol: "+original);
         }

         // Get the protocol handler if not specified or the protocol
         // of the context could not be used
         //核心代码:handler = getURLStreamHandler(protocol))
         if (handler == null &&
             (handler = getURLStreamHandler(protocol)) == null) {
             throw new MalformedURLException("unknown protocol: "+protocol);
         }

         this.handler = handler;

		//目的,如果包含上下文#引用,则从start开始,找到第一个#的下标,并且将#后面的字符串赋值给ref,表示引用;limit(地址长度只算i前面的)
		//#目的
         i = spec.indexOf('#', start);
         if (i >= 0) {
             ref = spec.substring(i + 1, limit);
             limit = i;
         }

         /*
          * Handle special case inheritance of query and fragment
          * implied by RFC2396 section 5.2.2.
          */
         if (isRelative && start == limit) {//如果是相对路径,并且start已经前移到头
             query = context.query;
             if (ref == null) {
                 ref = context.ref;
             }
         }
		//下面详细讲
         handler.parseURL(this, spec, start, limit);

     } catch(MalformedURLException e) {
         throw e;
     } catch(Exception e) {
         MalformedURLException exception = new MalformedURLException(e.getMessage());
         exception.initCause(e);
         throw exception;
     }	
}

代码中有详细注释,这里注释的比较详细,个人感觉第一既然看了,就看彻底一点;第二,详细的理解肯定有助于对全局意思的理解。以上还有几点补充

1 handler不为空情况下是否具有权限

SecurityManager类,安全管理器,这篇文章有介绍,有兴趣可以去看

 // Check for permission to specify a handler
 if (handler != null) {
      SecurityManager sm = System.getSecurityManager();
      if (sm != null) {
          checkSpecifyHandler(sm);
      }
  }
  
...

 /*
 * Checks for permission to specify a stream handler.
  */
 private void checkSpecifyHandler(SecurityManager sm) {
     sm.checkPermission(SecurityConstants.SPECIFY_HANDLER_PERMISSION);
 }

从Java API的角度去看,应用程序的安全策略是由安全管理器去管理的。安全管理器决定应用是否可以执行某项操作。这些操作具体是否可以执行的依据,是看其能否对一些比较重要的系统资源进行访问,而这项验证由存取控制器进行管控。

2 URL之#号

这里用到了两处,spec.charAt(start) == ‘#’)这个我确实不了解这样的写法,网上也没找到;另外一种

i = spec.indexOf('#', start);
if (i >= 0) {
      ref = spec.substring(i + 1, limit);
      limit = i;
  }

体现一种特殊的作用:例如:表示相应的位置

http://www.example.com/index.html#print

就代表网页index.html的print位置。浏览器读取这个URL后,会自动将print位置滚动至可视区域。

3 URL核心之getURLStreamHandler(protocol))

handler的创建,也是URL请求核心代码之一:获取URLStreamHandler 对象

//一个缓存,以协议为key存储协议对应的URLStreamHandler对象
static Hashtable<String,URLStreamHandler> handlers = new Hashtable<>();

/**
 * Returns the Stream Handler.
 * @param protocol the protocol to use
 */
static URLStreamHandler getURLStreamHandler(String protocol) {

    URLStreamHandler handler = handlers.get(protocol);
    if (handler == null) {//缓存中不存在

        boolean checkedWithFactory = false;

        // Use the factory (if any)
        if (factory != null) {//判断创建URLSteamHandler的工厂可存在,如果存在,使用工厂创建
        	//这里显然factory == null
            handler = factory.createURLStreamHandler(protocol);
            checkedWithFactory = true;
        }

        // Try java protocol handler
        if (handler == null) {
            // Android-changed: Android doesn't need AccessController.
            // Remove unnecessary use of reflection for sun classes
            /*
            packagePrefixList
                = java.security.AccessController.doPrivileged(
                new sun.security.action.GetPropertyAction(
                    protocolPathProp,""));
            if (packagePrefixList != "") {
                packagePrefixList += "|";
            }

            // REMIND: decide whether to allow the "null" class prefix
            // or not.
            packagePrefixList += "sun.net.www.protocol";
             */
             //获取jvm系统属性, 检查java.protocol.handler.pkgs包中有没有,有就创建
            final String packagePrefixList = System.getProperty(protocolPathProp,"");
			//将获取到的属性以“|”分割开
            StringTokenizer packagePrefixIter =
                new StringTokenizer(packagePrefixList, "|");

            while (handler == null &&
                   packagePrefixIter.hasMoreTokens()) {

                String packagePrefix =
                  packagePrefixIter.nextToken().trim();
                try {
                    String clsName = packagePrefix + "." + protocol +
                      ".Handler";
                    Class<?> cls = null;
                    try {//如果找到了,通过反射生成handler类
                        ClassLoader cl = ClassLoader.getSystemClassLoader();
                        // BEGIN Android-changed: Fall back to thread's contextClassLoader.
                        // http://b/25897689
                        cls = Class.forName(clsName, true, cl);
                    } catch (ClassNotFoundException e) {
                        ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
                        if (contextLoader != null) {
                            cls = Class.forName(clsName, true, contextLoader);
                        }
                        // END Android-changed: Fall back to thread's contextClassLoader.
                    }
                    if (cls != null) {
                        handler  =
                          (URLStreamHandler)cls.newInstance();
                    }
                } catch (ReflectiveOperationException ignored) {
                }
            }
        }

        // BEGIN Android-added: Custom built-in URLStreamHandlers for http, https.
        // Fallback to built-in stream handler.
        if (handler == null) {//以上如果都没有处理好handler的终生大事,那就继续到这里了
            try {//createBuiltinHandler最终值得托付,创建handler(你认为我这么较真的人下面会没有对该方法的注解???)
                handler = createBuiltinHandler(protocol);
            } catch (Exception e) {
                throw new AssertionError(e);
            }
        }
        // END Android-added: Custom built-in URLStreamHandlers for http, https.

        synchronized (streamHandlerLock) {//定义了一个锁,目的是确保里面内容的安全性
        //以下两个目的,首先我再去确认一下你这个URLStreamHandler 是否已经被缓存,被工厂创建,如果有这个虽然创建了,但是还是用handler2 

            URLStreamHandler handler2 = null;

            // Check again with hashtable just in case another
            // thread created a handler since we last checked
            handler2 = handlers.get(protocol);

            if (handler2 != null) {
                return handler2;
            }

            // Check with factory if another thread set a
            // factory since our last check
            if (!checkedWithFactory && factory != null) {
                handler2 = factory.createURLStreamHandler(protocol);
            }

            if (handler2 != null) {
                // The handler from the factory must be given more
                // importance. Discard the default handler that
                // this thread created.
                handler = handler2;
            }

            // Insert this handler into the hashtable
            if (handler != null) {
                handlers.put(protocol, handler);
            }

        }
    }

    return handler;

}

(1)System.getProperty

通过 JVM 启动参数 -D java.protocol.handler.pkgs 来设置 URLStreamHandler 实现类的包路径,例如 -D java.protocol.handler.pkgs=com.acme.protocol , 代表处理实现类皆在这个包下。如果需要多个包的话,那么使用“ |” 分割。比如 -D java.protocol.handler.pkgs=com.acme.protocol|com.acme.protocol2 。 SUN 的 JDK 内部实现类均是在 sun.net.www.protocol. 包下,不必设置。 路径下的协议实现类,采用先定义先选择的原则 。

看下如下代码的意思:

// Android-changed: Android doesn't need AccessController.
                // Remove unnecessary use of reflection for sun classes
                /*
                packagePrefixList
                    = java.security.AccessController.doPrivileged(
                    new sun.security.action.GetPropertyAction(
                        protocolPathProp,""));
                if (packagePrefixList != "") {
                    packagePrefixList += "|";
                }

                // REMIND: decide whether to allow the "null" class prefix
                // or not.
                packagePrefixList += "sun.net.www.protocol";
                 */
                final String packagePrefixList = System.getProperty("java.protocol.handler.pkgs","");

                StringTokenizer packagePrefixIter =
                    new StringTokenizer(packagePrefixList, "|");

我们可以自己试下,并没有找到java.protocol.handler.pkgs包下的类名
在这里插入图片描述
(2)createBuiltinHandler

在Android新版源码中(据说是4.4开始)底层的Http协议请求使用的是Okhttp。

// BEGIN Android-added: Custom built-in URLStreamHandlers for http, https.
    /**
     * Returns an instance of the built-in handler for the given protocol, or null if none exists.
     */
    private static URLStreamHandler createBuiltinHandler(String protocol)
            throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        URLStreamHandler handler = null;
        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();
        }
        return handler;
    }

我们来看下URLStreamHandler源码:

/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.squareup.okhttp;
import java.io.IOException;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
public final class HttpHandler extends URLStreamHandler {
    @Override protected URLConnection openConnection(URL url) throws IOException {
        return new OkHttpClient().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 new OkHttpClient().setProxy(proxy).open(url);
    }
    @Override protected int getDefaultPort() {
        return 80;
    }
}

注意:这里com.android.okhttp.HttpsHandler包下,为什么最终定位的是com.squareup.okhttp,因为这里做了包名转换:

因为jarjar-rules的存在,其实路径为/external/okhttp/jarjar-rules.txt,内容如下:
rule com.squareup.** com.android.@1
rule okio.** com.android.okio.@1

4 URL地址解析:handler.parseURL(this, spec, start, limit);

protected void parseURL(URL u, String spec, int start, int limit) {
        // These fields may receive context content if this was relative URL
        //这里除了protocal能获取到,我估计其他字段都是null或者默认值,这句话的目的是如果承接之前的信息,如果已经存在那么直接赋值过来即可
        String protocol = u.getProtocol();
        String authority = u.getAuthority();
        String userInfo = u.getUserInfo();
        String host = u.getHost();
        int port = u.getPort();
        String path = u.getPath();
        String query = u.getQuery();

        // This field has already been parsed
        String ref = u.getRef();

        boolean isRelPath = false;//判断地址是否真实有效
        boolean queryOnly = false;//仅仅是查询,我说这个是代码冗余可信?高级代码不过尔尔
        // BEGIN Android-changed: App compat
        boolean querySet = false;//url地址中是否存在搜索条件
        // END Android-changed: App compat

// FIX: should not assume query if opaque
        // Strip off the query part
        if (start < limit) {
            int queryStart = spec.indexOf('?');
            queryOnly = queryStart == start;
            if ((queryStart != -1) && (queryStart < limit)) {//有搜索条件
                query = spec.substring(queryStart+1, limit);//搜索条件防止query中
                if (limit > queryStart)
                    limit = queryStart;//这里start~limit之间就是去头去尾的地址了,例如www.baidu.com,去除了http://和?name=xxx
                spec = spec.substring(0, queryStart);//   http://www.baidu.com
                // BEGIN Android-changed: App compat
                querySet = true;
                // END Android-changed: App compat
            }
        }

        int i = 0;
        // Parse the authority part if any
        // BEGIN Android-changed: App compat
        // boolean isUNCName = (start <= limit - 4) &&
        //                 (spec.charAt(start) == '/') &&
        //                 (spec.charAt(start + 1) == '/') &&
        //                 (spec.charAt(start + 2) == '/') &&
        //                 (spec.charAt(start + 3) == '/');
        boolean isUNCName = false;//判断是否局域网
        // END Android-changed: App compat
        if (!isUNCName && (start <= limit - 2) && (spec.charAt(start) == '/') &&
            (spec.charAt(start + 1) == '/')) {//两个刚表示http://这里的两个/
            start += 2;
            // BEGIN Android-changed: Check for all hostname termination chars. http://b/110955991
            /*
            i = spec.indexOf('/', start);
            if (i < 0 || i > limit) {
                i = spec.indexOf('?', start);
                if (i < 0 || i > limit)
                    i = limit;
            }
            */
            LOOP: for (i = start; i < limit; i++) {
                switch (spec.charAt(i)) {
                    case '/':  // Start of path
                    case '\\': // Start of path - see https://url.spec.whatwg.org/#host-state
                    case '?':  // Start of query
                    case '#':  // Start of fragment
                        break LOOP;
                }
            }
            // END Android-changed: Check for all hostname termination chars. http://b/110955991
				
			//下面都是在找URL信息
			
            host = authority = spec.substring(start, i);

            int ind = authority.indexOf('@');
            if (ind != -1) {
                if (ind != authority.lastIndexOf('@')) {
                    // more than one '@' in authority. This is not server based
                    userInfo = null;
                    host = null;
                } else {
                    userInfo = authority.substring(0, ind);
                    host = authority.substring(ind+1);
                }
            } else {
                userInfo = null;
            }
            if (host != null) {
                // If the host is surrounded by [ and ] then its an IPv6
                // literal address as specified in RFC2732
                if (host.length()>0 && (host.charAt(0) == '[')) {
                    if ((ind = host.indexOf(']')) > 2) {

                        String nhost = host ;
                        host = nhost.substring(0,ind+1);
                        if (!IPAddressUtil.
                            isIPv6LiteralAddress(host.substring(1, ind))) {
                            throw new IllegalArgumentException(
                                "Invalid host: "+ host);
                        }

                        port = -1 ;
                        if (nhost.length() > ind+1) {
                            if (nhost.charAt(ind+1) == ':') {
                                ++ind ;
                                // port can be null according to RFC2396
                                if (nhost.length() > (ind + 1)) {
                                    port = Integer.parseInt(nhost.substring(ind+1));
                                }
                            } else {
                                throw new IllegalArgumentException(
                                    "Invalid authority field: " + authority);
                            }
                        }
                    } else {
                        throw new IllegalArgumentException(
                            "Invalid authority field: " + authority);
                    }
                } else {
                    ind = host.indexOf(':');
                    port = -1;
                    if (ind >= 0) {
                        // port can be null according to RFC2396
                        if (host.length() > (ind + 1)) {
                            // BEGIN Android-changed: App compat
                            // port = Integer.parseInt(host.substring(ind + 1));
                            char firstPortChar = host.charAt(ind+1);
                            if (firstPortChar >= '0' && firstPortChar <= '9') {
                                port = Integer.parseInt(host.substring(ind + 1));
                            } else {
                                throw new IllegalArgumentException("invalid port: " +
                                                                   host.substring(ind + 1));
                            }
                            // END Android-changed: App compat
                        }
                        host = host.substring(0, ind);
                    }
                }
            } else {
                host = "";
            }
            if (port < -1)
                throw new IllegalArgumentException("Invalid port number :" +
                                                   port);
            start = i;

            // If the authority is defined then the path is defined by the
            // spec only; See RFC 2396 Section 5.2.4.
            // BEGIN Android-changed: App compat
            // if (authority != null && authority.length() > 0)
            //   path = "";
            path = null;
            if (!querySet) {
                query = null;
            }
            // END Android-changed: App compat
        }

        if (host == null) {
            host = "";
        }

        // Parse the file path if any
        if (start < limit) {
            // Android-changed: Check for all hostname termination chars. http://b/110955991
            // if (spec.charAt(start) == '/') {
            if (spec.charAt(start) == '/' || spec.charAt(start) == '\\') {
                path = spec.substring(start, limit);
            } else if (path != null && path.length() > 0) {
                isRelPath = true;
                int ind = path.lastIndexOf('/');
                String seperator = "";
                if (ind == -1 && authority != null)
                    seperator = "/";
                path = path.substring(0, ind + 1) + seperator +
                         spec.substring(start, limit);

            } else {
                String seperator = (authority != null) ? "/" : "";
                path = seperator + spec.substring(start, limit);
            }
        }
        // BEGIN Android-changed: App compat
        //else if (queryOnly && path != null) {
        //    int ind = path.lastIndexOf('/');
        //    if (ind < 0)
        //        ind = 0;
        //    path = path.substring(0, ind) + "/";
        //}
        // END Android-changed: App compat
        if (path == null)
            path = "";

        // BEGIN Android-changed
        //if (isRelPath) {
        if (true) {
        // END Android-changed
            // Remove embedded /./
            while ((i = path.indexOf("/./")) >= 0) {
                path = path.substring(0, i) + path.substring(i + 2);
            }
            // Remove embedded /../ if possible
            i = 0;
            while ((i = path.indexOf("/../", i)) >= 0) {
                // BEGIN Android-changed: App compat
                /*
                 * Trailing /../
                 */
                if (i == 0) {
                    path = path.substring(i + 3);
                    i = 0;
                // END Android-changed: App compat
                /*
                 * A "/../" will cancel the previous segment and itself,
                 * unless that segment is a "/../" itself
                 * i.e. "/a/b/../c" becomes "/a/c"
                 * but "/../../a" should stay unchanged
                 */
                // Android-changed: App compat
                // if (i > 0 && (limit = path.lastIndexOf('/', i - 1)) >= 0 &&
                } else if (i > 0 && (limit = path.lastIndexOf('/', i - 1)) >= 0 &&
                    (path.indexOf("/../", limit) != 0)) {
                    path = path.substring(0, limit) + path.substring(i + 3);
                    i = 0;
                } else {
                    i = i + 3;
                }
            }
            // Remove trailing .. if possible
            while (path.endsWith("/..")) {
                i = path.indexOf("/..");
                if ((limit = path.lastIndexOf('/', i - 1)) >= 0) {
                    path = path.substring(0, limit+1);
                } else {
                    break;
                }
            }
            // Remove starting .
            if (path.startsWith("./") && path.length() > 2)
                path = path.substring(2);

            // Remove trailing .
            if (path.endsWith("/."))
                path = path.substring(0, path.length() -1);

            // Android-changed: App compat: Remove trailing ?
            if (path.endsWith("?"))
                path = path.substring(0, path.length() -1);
        }

        setURL(u, protocol, host, port, authority, userInfo, path, query, ref);
    }

以上代码没啥具体技术含金量,主要是针对URL地址解析,然后调用setURL方法,

protected void setURL(URL u, String protocol, String host, int port,
                     String authority, String userInfo, String path,
                         String query, String ref) {
    if (this != u.handler) {
        throw new SecurityException("handler for url different from " +
                                    "this handler");
    }
    // ensure that no one can reset the protocol on a given URL.
    u.set(u.getProtocol(), host, port, authority, userInfo, path, query, ref);
}

调用URL的set方法初始化解析所得的值

/**
     * Sets the fields of the URL. This is not a public method so that
     * only URLStreamHandlers can modify URL fields. URLs are
     * otherwise constant.
     *
     * @param protocol the name of the protocol to use
     * @param host the name of the host
       @param port the port number on the host
     * @param file the file on the host
     * @param ref the internal reference in the URL
     */
    void set(String protocol, String host, int port,
             String file, String ref) {
        synchronized (this) {
            this.protocol = protocol;
            this.host = host;
            authority = port == -1 ? host : host + ":" + port;
            this.port = port;
            this.file = file;
            this.ref = ref;
            /* This is very important. We must recompute this after the
             * URL has been changed. */
            hashCode = -1;
            hostAddress = null;
            int q = file.lastIndexOf('?');
            if (q != -1) {
                query = file.substring(q+1);
                path = file.substring(0, q);
            } else
                path = file;
        }
    }

URLStreamHandler作用

/**
 * The abstract class {@code URLStreamHandler} is the common
 * superclass for all stream protocol handlers. A stream protocol
 * handler knows how to make a connection for a particular protocol
 * type, such as {@code http} or {@code https}.
 * <p>
 * In most cases, an instance of a {@code URLStreamHandler}
 * subclass is not created directly by an application. Rather, the
 * first time a protocol name is encountered when constructing a
 * {@code URL}, the appropriate stream protocol handler is
 * automatically loaded.
 *
 * @author  James Gosling
 * @see     java.net.URL#URL(java.lang.String, java.lang.String, int, java.lang.String)
 * @since   JDK1.0
 */

如上URLStreamHandler.java类头部代码,翻译一下它是是常见的所有流协议处理程序的超类(流协议有以下类型),在大多数情况下,URLStreamHandler的实例子类不是由应用程序直接创建的,而是首次在构建协议时(new URL)时实例化
在这里插入图片描述
1.HttpsHandler

/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.squareup.okhttp;
import java.io.IOException;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
public final class HttpsHandler extends URLStreamHandler {
    @Override protected URLConnection openConnection(URL url) throws IOException {
        return new OkHttpClient().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 new OkHttpClient().setProxy(proxy).open(url);
    }
    @Override protected int getDefaultPort() {
        return 443;
    }
}

和http非常相似,唯一的区别是端口号http默认是80,https默认是443

2.JarHandler

以下是jdk1.8中的源码,不同版本略有区别

重点看parseURL方法:解析URL并且通过URL.set初始化URL中的相关信息

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package sun.net.www.protocol.jar;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import sun.net.www.ParseUtil;

public class Handler extends URLStreamHandler {
    private static final String separator = "!/";

    public Handler() {
    }

    protected URLConnection openConnection(URL var1) throws IOException {
        return new JarURLConnection(var1, this);
    }

    private static int indexOfBangSlash(String var0) {
        for(int var1 = var0.length(); (var1 = var0.lastIndexOf(33, var1)) != -1; --var1) {
            if (var1 != var0.length() - 1 && var0.charAt(var1 + 1) == '/') {
                return var1 + 1;
            }
        }

        return -1;
    }

    protected boolean sameFile(URL var1, URL var2) {
        if (var1.getProtocol().equals("jar") && var2.getProtocol().equals("jar")) {
            String var3 = var1.getFile();
            String var4 = var2.getFile();
            int var5 = var3.indexOf("!/");
            int var6 = var4.indexOf("!/");
            if (var5 != -1 && var6 != -1) {
                String var7 = var3.substring(var5 + 2);
                String var8 = var4.substring(var6 + 2);
                if (!var7.equals(var8)) {
                    return false;
                } else {
                    URL var9 = null;
                    URL var10 = null;

                    try {
                        var9 = new URL(var3.substring(0, var5));
                        var10 = new URL(var4.substring(0, var6));
                    } catch (MalformedURLException var12) {
                        return super.sameFile(var1, var2);
                    }

                    return super.sameFile(var9, var10);
                }
            } else {
                return super.sameFile(var1, var2);
            }
        } else {
            return false;
        }
    }

    protected int hashCode(URL var1) {
        int var2 = 0;
        String var3 = var1.getProtocol();
        if (var3 != null) {
            var2 += var3.hashCode();
        }

        String var4 = var1.getFile();
        int var5 = var4.indexOf("!/");
        if (var5 == -1) {
            return var2 + var4.hashCode();
        } else {
            URL var6 = null;
            String var7 = var4.substring(0, var5);

            try {
                var6 = new URL(var7);
                var2 += var6.hashCode();
            } catch (MalformedURLException var9) {
                var2 += var7.hashCode();
            }

            String var8 = var4.substring(var5 + 2);
            var2 += var8.hashCode();
            return var2;
        }
    }

    protected void parseURL(URL var1, String var2, int var3, int var4) {
        String var5 = null;
        String var6 = null;
        int var7 = var2.indexOf(35, var4);
        boolean var8 = var7 == var3;
        if (var7 > -1) {
            var6 = var2.substring(var7 + 1, var2.length());
            if (var8) {
                var5 = var1.getFile();
            }
        }

        boolean var9 = false;
        if (var2.length() >= 4) {
            var9 = var2.substring(0, 4).equalsIgnoreCase("jar:");
        }

        var2 = var2.substring(var3, var4);
        if (var9) {
            var5 = this.parseAbsoluteSpec(var2);
        } else if (!var8) {
            var5 = this.parseContextSpec(var1, var2);
            int var10 = indexOfBangSlash(var5);
            String var11 = var5.substring(0, var10);
            String var12 = var5.substring(var10);
            ParseUtil var13 = new ParseUtil();
            var12 = var13.canonizeString(var12);
            var5 = var11 + var12;
        }

        this.setURL(var1, "jar", "", -1, var5, var6);
    }

    private String parseAbsoluteSpec(String var1) {
        Object var2 = null;
        boolean var3 = true;
        int var6;
        if ((var6 = indexOfBangSlash(var1)) == -1) {
            throw new NullPointerException("no !/ in spec");
        } else {
            try {
                String var4 = var1.substring(0, var6 - 1);
                new URL(var4);
                return var1;
            } catch (MalformedURLException var5) {
                throw new NullPointerException("invalid url: " + var1 + " (" + var5 + ")");
            }
        }
    }

    private String parseContextSpec(URL var1, String var2) {
        String var3 = var1.getFile();
        int var4;
        if (var2.startsWith("/")) {
            var4 = indexOfBangSlash(var3);
            if (var4 == -1) {
                throw new NullPointerException("malformed context url:" + var1 + ": no !/");
            }

            var3 = var3.substring(0, var4);
        }

        if (!var3.endsWith("/") && !var2.startsWith("/")) {
            var4 = var3.lastIndexOf(47);
            if (var4 == -1) {
                throw new NullPointerException("malformed context url:" + var1);
            }

            var3 = var3.substring(0, var4 + 1);
        }

        return var3 + var2;
    }
}

**注意:**这里HttpHandler,HttpsHandler是在android源码目录下:com.android.okhttp;File,Ftp,Jar是在jdk目录下:sun.net.www.protocol。

URL作用

先自行查看URL.java注解(太长不贴代码),主要三个作用:1.对传递过来的URL相关信息存储;2.根据url传递的协议实例化对应的流协议。

getURLStreamHandler()获取流协议中:

这里流协议的创建首先判断缓存中是否存在,如果有则直接复制;还有通过工厂模式创建,都没有再实例化;这里为了防止因为多线程并行导致创建(流协议有且仅有一个)多个的问题,采取了对象锁措施,并且在锁内在“问”一遍,缓存中是否存在?工厂是否创建过?如果没有,在实现赋值并且加入到缓存中

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值