[URL系列一]URL类源码解读

[URL系列一]URL类源码解读


1.对该类的理解

1.1 何为URL?(http://gaozh:pwd@www.gaozhenhua.cn:80/test1/test2/?a=3&b=4#reftest)

1.2 何为streamHandler

根据协议protocol不同,对应的URL的处理方式也是不同的,streamHandler即:与当前URL对应的处理对象

系统默认提供以下处理对象:
  • 1.file -> FileHandler
  • 2.ftp -> FtpHandler
  • 3.http -> HttpHandler
  • 4.https -> HttpsHandler
  • 5.jar -> JarHandler
协议扩展时,要么直接指定handler,要么指定streamHandlerFactory

1.3 构造方法

  • URL(String spec)
  • URL(URL context, String spec)
  • URL(URL context, String spec, URLStreamHandler handler)
  • URL(String protocol, String host, String file)
  • URL(String protocol, String host, int port, String file)
  • URL(String protocol, String host, int port, String file, URLStreamHandler handler)

1.4 其它方法

  • 1.一些常用简单方法 如:getContent openStream
  • 2.通过openConnection拿到URLConnection后的高级扩展使用

1.5 总结

一般所有的网络请求都是从构建URL,openConnection开始的。后续则是针对不同的协议进行设置,及输入输出流的读取等操作


2.源码

// 摘自android源码4.0 android_sourcecode\libcore\luni\src\main\java\java\net

package java.net;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Hashtable;
import java.util.jar.JarFile;
import libcore.net.http.HttpHandler;
import libcore.net.http.HttpsHandler;
import libcore.net.url.FileHandler;
import libcore.net.url.FtpHandler;
import libcore.net.url.JarHandler;
import libcore.net.url.UrlUtils;

// A Uniform Resource Locator that identifies the location of an Internet resource

public final class URL implements Serializable {
    private static final long serialVersionUID = -7627629688361524110L;

    // 协议解析工厂类
    private static URLStreamHandlerFactory streamHandlerFactory;

    // 协议与协议解析映射表
    private static final Hashtable<String, URLStreamHandler> streamHandlers = new Hashtable<String, URLStreamHandler>();
    // http://gaozh:pwd@www.gaozhenhua.cn:80/test1/test2/?a=3&b=4#reftest
    // http
    private String protocol;
    // gaozh:pwd@www.gaozhenhua.cn:80
    private String authority;
    // www.gaozhenhua.cn
    private String host;
    // 80
    private int port = -1;
    // /test1/test2/?a=3&b=4
    private String file;
    // reftest
    private String ref;

    // gaozh:pwd
    private transient String userInfo;
    // /test1/test2/
    private transient String path;
    // a=3&b=4
    private transient String query;

    transient URLStreamHandler streamHandler;

    /**
     * The cached hash code, or 0 if it hasn't been computed yet. Unlike the RI,
     * this implementation's hashCode is transient because the hash code is
     * unspecified and may vary between VMs or versions.
     */
    private transient int hashCode;

    // Sets the stream handler factory for this VM.
    public static synchronized void setURLStreamHandlerFactory(URLStreamHandlerFactory factory) {
        if (streamHandlerFactory != null) {
            throw new Error("Factory already set");
        }
        streamHandlers.clear();
        streamHandlerFactory = factory;
    }

    // 构造方法
    public URL(String spec) throws MalformedURLException {
        this((URL) null, spec, null);
    }

    // 构造方法
    public URL(URL context, String spec) throws MalformedURLException {
        this(context, spec, null);
    }

    // 构造方法
    public URL(URL context, String spec, URLStreamHandler handler) throws MalformedURLException {
        if (spec == null) {
            throw new MalformedURLException();
        }
        // 1.如果指定解析对象直接使用解析者
        if (handler != null) {
            streamHandler = handler;
        }
        // 2.url字符串两头去空格
        spec = spec.trim();
        // 3.解析协议名称
        protocol = UrlUtils.getSchemePrefix(spec);
        int schemeSpecificPartStart = protocol != null ? (protocol.length() + 1) : 0;

        // If the context URL has a different protocol, discard it because we can't use it.
        // 保护机制?
        if (protocol != null && context != null && !protocol.equals(context.protocol)) {
            context = null;
        }

        // Inherit from the context URL if it exists.
        // 继承
        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) {
            // 无法继承,协议未知,over
            throw new MalformedURLException("Protocol not found: " + spec);
        }

        if (streamHandler == null) {
            setupStreamHandler();
            if (streamHandler == null) {
                // 该协议没人能解析的了,异常结束
                throw new MalformedURLException("Unknown protocol: " + protocol);
            }
        }

        // Parse the URL. If the handler throws any exception, throw MalformedURLException instead.
        try {
            // 解析当前URL
            streamHandler.parseURL(this, spec, schemeSpecificPartStart, spec.length());
        } catch (Exception e) {
            throw new MalformedURLException(e.toString());
        }
    }

    // 构造方法
    public URL(String protocol, String host, String file) throws MalformedURLException {
        this(protocol, host, -1, file, null);
    }

    // 构造方法
    public URL(String protocol, String host, int port, String file) throws MalformedURLException {
        this(protocol, host, port, file, null);
    }

    // 构造方法
    public URL(String protocol, String host, int port, String file,
            URLStreamHandler handler) throws MalformedURLException {
        if (port < -1) {
            throw new MalformedURLException("port < -1: " + port);
        }
        if (protocol == null) {
            throw new NullPointerException("protocol == null");
        }

        // Wrap IPv6 addresses in square brackets if they aren't already.
        if (host != null && host.contains(":") && host.charAt(0) != '[') {
            host = "[" + host + "]";
        }

        this.protocol = protocol;
        this.host = host;
        this.port = port;

        file = UrlUtils.authoritySafePath(host, file);

        // Set the fields from the arguments. Handle the case where the
        // passed in "file" includes both a file and a reference part.
        int hash = file.indexOf("#");
        if (hash != -1) {
            this.file = file.substring(0, hash);
            this.ref = file.substring(hash + 1);
        } else {
            this.file = file;
        }
        fixURL(false);

        // Set the stream handler for the URL either to the handler
        // argument if it was specified, or to the default for the
        // receiver's protocol if the handler was null.
        if (handler == null) {
            setupStreamHandler();
            if (streamHandler == null) {
                throw new MalformedURLException("Unknown protocol: " + protocol);
            }
        } else {
            streamHandler = handler;
        }
    }

    void fixURL(boolean fixHost) {
        int index;
        if (host != null && host.length() > 0) {
            authority = host;
            if (port != -1) {
                authority = authority + ":" + port;
            }
        }
        if (fixHost) {
            if (host != null && (index = host.lastIndexOf('@')) > -1) {
                userInfo = host.substring(0, index);
                host = host.substring(index + 1);
            } else {
                userInfo = null;
            }
        }
        if (file != null && (index = file.indexOf('?')) > -1) {
            query = file.substring(index + 1);
            path = file.substring(0, index);
        } else {
            query = null;
            path = file;
        }
    }

    // Sets the properties of this URL using the provided arguments.
    protected void set(String protocol, String host, int port, String file, String ref) {
        if (this.protocol == null) {
            this.protocol = protocol;
        }
        this.host = host;
        this.file = file;
        this.port = port;
        this.ref = ref;
        hashCode = 0;
        fixURL(true);
    }

    @Override public boolean equals(Object o) {
        if (o == null) {
            return false;
        }
        if (this == o) {
            return true;
        }
        if (this.getClass() != o.getClass()) {
            return false;
        }
        return streamHandler.equals(this, (URL) o);
    }

    // Returns true if this URL refers to the same resource as {@code otherURL} except the reference field.
    public boolean sameFile(URL otherURL) {
        return streamHandler.sameFile(this, otherURL);
    }

    @Override public int hashCode() {
        if (hashCode == 0) {
            hashCode = streamHandler.hashCode(this);
        }
        return hashCode;
    }

    // Sets the receiver's stream handler to one which is appropriate for its protocol
    void setupStreamHandler() {
        // 检查缓存,有则可以直接使用
        streamHandler = streamHandlers.get(protocol);
        if (streamHandler != null) {
            return;
        }

        // 没有,但指定了生成factory的情况,用factory生成
        if (streamHandlerFactory != null) {
            streamHandler = streamHandlerFactory.createURLStreamHandler(protocol);
            if (streamHandler != null) {
                streamHandlers.put(protocol, streamHandler);
                return;
            }
        }

        // 没有,切没有指定解析factory的情况下,看当前系统中是否存在能解析当前协议的apk
        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) {
                }
            }
        }

        // 最后才会尝试使用系统默认的
        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);
        }
    }

    /**
     * Returns the content of the resource which is referred by this URL. By
     * default this returns an {@code InputStream}, or null if the content type
     * of the response is unknown.
     */
    public final Object getContent() throws IOException {
        return openConnection().getContent();
    }

    /**
     * Equivalent to {@code openConnection().getContent(types)}.
     */
    @SuppressWarnings("unchecked") // Param not generic in spec
    public final Object getContent(Class[] types) throws IOException {
        return openConnection().getContent(types);
    }

    /**
     * Equivalent to {@code openConnection().getInputStream(types)}.
     */
    public final InputStream openStream() throws IOException {
        return openConnection().getInputStream();
    }

    /**
     * Returns a new connection to the resource referred to by this URL.
     *
     * @throws IOException if an error occurs while opening the connection.
     */
    public URLConnection openConnection() throws IOException {
        return streamHandler.openConnection(this);
    }

    /**
     * Returns a new connection to the resource referred to by this URL.
     *
     * @param proxy the proxy through which the connection will be established.
     * @throws IOException if an I/O error occurs while opening the connection.
     * @throws IllegalArgumentException if the argument proxy is null or of is
     *     an invalid type.
     * @throws UnsupportedOperationException if the protocol handler does not
     *     support opening connections through proxies.
     */
    public URLConnection openConnection(Proxy proxy) throws IOException {
        if (proxy == null) {
            throw new IllegalArgumentException("proxy == null");
        }
        return streamHandler.openConnection(this, proxy);
    }

    /**
     * Returns the URI equivalent to this URL.
     *
     * @throws URISyntaxException if this URL cannot be converted into a URI.
     */
    public URI toURI() throws URISyntaxException {
        return new URI(toExternalForm());
    }

    /**
     * Encodes this URL to the equivalent URI after escaping characters that are
     * not permitted by URI.
     *
     * @hide
     */
    public URI toURILenient() throws URISyntaxException {
        if (streamHandler == null) {
            throw new IllegalStateException(protocol);
        }
        return new URI(streamHandler.toExternalForm(this, true));
    }

    /**
     * Returns a string containing a concise, human-readable representation of
     * this URL. The returned string is the same as the result of the method
     * {@code toExternalForm()}.
     */
    @Override public String toString() {
        return toExternalForm();
    }

    /**
     * Returns a string containing a concise, human-readable representation of
     * this URL.
     */
    public String toExternalForm() {
        if (streamHandler == null) {
            return "unknown protocol(" + protocol + ")://" + host + file;
        }
        return streamHandler.toExternalForm(this);
    }

    private void readObject(ObjectInputStream stream) throws IOException {
        try {
            stream.defaultReadObject();
            if (host != null && authority == null) {
                fixURL(true);
            } else if (authority != null) {
                int index;
                if ((index = authority.lastIndexOf('@')) > -1) {
                    userInfo = authority.substring(0, index);
                }
                if (file != null && (index = file.indexOf('?')) > -1) {
                    query = file.substring(index + 1);
                    path = file.substring(0, index);
                } else {
                    path = file;
                }
            }
            setupStreamHandler();
            if (streamHandler == null) {
                throw new IOException("Unknown protocol: " + protocol);
            }
            hashCode = 0; // necessary until http://b/4471249 is fixed
        } catch (ClassNotFoundException e) {
            throw new IOException(e);
        }
    }

    private void writeObject(ObjectOutputStream s) throws IOException {
        s.defaultWriteObject();
    }

    /** @hide */
    public int getEffectivePort() {
        return URI.getEffectivePort(protocol, port);
    }

    public String getProtocol() {
        return protocol;
    }

    public String getAuthority() {
        return authority;
    }

    public String getUserInfo() {
        return userInfo;
    }

    public String getHost() {
        return host;
    }

    public int getPort() {
        return port;
    }

    public int getDefaultPort() {
        return streamHandler.getDefaultPort();
    }

    public String getFile() {
        return file;
    }

    public String getPath() {
        return path;
    }

    public String getQuery() {
        return query;
    }

    public String getRef() {
        return ref;
    }

    // Sets the properties of this URL using the provided arguments
    protected void set(String protocol, String host, int port, String authority, String userInfo,
            String path, String query, String ref) {
        String file = path;
        if (query != null && !query.isEmpty()) {
            file += "?" + query;
        }
        set(protocol, host, port, file, ref);
        this.authority = authority;
        this.userInfo = userInfo;
        this.path = path;
        this.query = query;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,658评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,482评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,213评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,395评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,487评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,523评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,525评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,300评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,753评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,048评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,223评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,905评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,541评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,168评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,417评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,094评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,088评论 2 352

推荐阅读更多精彩内容