淺談Android網(wǎng)絡(luò)請(qǐng)求的前世今生-元基礎(chǔ)HttpConnection

之前寫過(guò)一篇關(guān)于網(wǎng)絡(luò)請(qǐng)求相關(guān)的文章,主要關(guān)于一些網(wǎng)絡(luò)基礎(chǔ).這篇?jiǎng)t重點(diǎn)講一講Android下httpUrlConnect的內(nèi)容。

相關(guān)鏈接

淺談Android網(wǎng)絡(luò)通信的前世今生--網(wǎng)絡(luò)基礎(chǔ)

Android下的Http client

Android提供了三種Http client:

  1. HttpURLConnection
  2. Apache HttpClient
  3. okHttp

HttpURLConnection

HttpUrlConnection是JDK里提供的聯(lián)網(wǎng)API,我們知道Android SDK是基于Java的,所以當(dāng)然優(yōu)先考慮HttpUrlConnection這種最原始最基本的API,其實(shí)大多數(shù)開源的聯(lián)網(wǎng)框架基本上也是基于JDK的HttpUrlConnection進(jìn)行的封裝罷了

HttpClient(不建議使用)

HttpClient是開源組織Apache提供的Java請(qǐng)求網(wǎng)絡(luò)框架,其最早是為了方便Java服務(wù)器開發(fā)而誕生的,是對(duì)JDK中的HttpUrlConnection各API進(jìn)行了封裝和簡(jiǎn)化,提高了性能并且降低了調(diào)用API的繁瑣,不過(guò)官方已經(jīng)不建議使用。

okhttp

OKHttp是現(xiàn)在主流應(yīng)用使用的網(wǎng)絡(luò)請(qǐng)求方式, 用來(lái)交換數(shù)據(jù)和內(nèi)容, 有效的使用OKHttp可以使你的APP變的更快和減少流量的使用。

  • 支持SPDY,可以合并多個(gè)到同一個(gè)主機(jī)的請(qǐng)求
  • 使用連接池技術(shù)減少請(qǐng)求的延遲(如果SPDY是可用的話)
  • 使用GZIP壓縮減少傳輸?shù)臄?shù)據(jù)量
  • 緩存響應(yīng)避免重復(fù)的網(wǎng)絡(luò)請(qǐng)求

當(dāng)你的網(wǎng)絡(luò)出現(xiàn)擁擠的時(shí)候,就是OKHttp大顯身手的時(shí)候,它可以避免常見的網(wǎng)絡(luò)問(wèn)題,如果你的服務(wù)是部署在不同的IP上面的,如果第一個(gè)連接失敗,OkHTtp會(huì)嘗試其他的連接。這對(duì)現(xiàn)在IPv4+IPv6中常見的把服務(wù)冗余部署在不同的數(shù)據(jù)中心上也是很有必要的。OkHttp將使用現(xiàn)在TLS特性(SNI ALPN)來(lái)初始化新的連接,如果握手失敗,將切換到TLS 1.0。

目前使用率最高的當(dāng)屬okHttp,不過(guò)我們這篇文章還是要先講講Android下網(wǎng)絡(luò)請(qǐng)求的先輩 HttpUrlConnect,本文基于Android API26

簡(jiǎn)單使用

URL url=new URL("www.baidu.com");
HttpURLConnection conn= (HttpURLConnection) url.openConnection();
InputStream in = new BufferedInputStream(conn.getInputStream());

其中關(guān)鍵的兩個(gè)過(guò)程
1.openConnection() 建立tcp連接
2.getInputSteam() 發(fā)送http請(qǐng)求,并獲取返回流

openConnection()

URL.java

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

其中 handler是URLStreamHandler的實(shí)例,handler的創(chuàng)建是在URL的構(gòu)造函數(shù)中,其中調(diào)用了getURLStreamHandler()方法、

static URLStreamHandler getURLStreamHandler(String protocol) {

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

        boolean checkedWithFactory = false;

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

        // Try java protocol handler
        if (handler == null) {
            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 {
                        ClassLoader cl = ClassLoader.getSystemClassLoader();
                        cls = Class.forName(clsName, true, cl);
                    } catch (ClassNotFoundException e) {
                        ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
                        if (contextLoader != null) {
                            cls = Class.forName(clsName, true, contextLoader);
                        }
                    }
                    if (cls != null) {
                        handler  =
                          (URLStreamHandler)cls.newInstance();
                    }
                } catch (ReflectiveOperationException ignored) {
                }
            }
        }

        // Fallback to built-in stream handler.
        // Makes okhttp the default http/https handler
        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;

            // 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. 從handlers中取

  2. 如果URLStreamHandlerFactory不為空.讓URLStreamHandlerFactory生成

  3. 根據(jù)協(xié)議 protocol生成,注意老版本中http和https用的是

    streamHandler = new HttpHandler();
    streamHandler = new HttpsHandler();

后來(lái)的版本用的是,底層換成了okhttp的類

handler = (URLStreamHandler)Class.forName("com.android.okhttp.HttpHandler").newInstance();

handler = (URLStreamHandler)Class.forName("com.android.okhttp.HttpsHandler").newInstance();

最后還有個(gè)并發(fā)檢查,避免因?yàn)槎嗑€程的原因?qū)е律啥鄠€(gè)handler實(shí)例

 synchronized (streamHandlerLock) {

    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);
    }

}

URLStreamHandler是一個(gè)抽象類,子類包括http,https,ftp等實(shí)現(xiàn)類,這里我們只看http的實(shí)現(xiàn)類

protected URLConnection openConnection(URL var1) throws IOException {
    return this.openConnection(var1, (Proxy)null);
}

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

這里會(huì)生成一個(gè)HttpUrlConnection()對(duì)象,注意這里的HttpUrlConnection()對(duì)象和最開始的HttpUrlConnection()對(duì)象不是一個(gè),包名不同,一個(gè)是sun公司的包,一個(gè)是谷歌官方的包。

sun/HttpURLConnection.java

protected HttpURLConnection(URL var1, Proxy var2, Handler var3) {
    super(var1);
    ....
    if(this.instProxy instanceof ApplicationProxy) {
        try {
            this.cookieHandler = CookieHandler.getDefault();
        } catch (SecurityException var5) {
            ;
        }
    } else {
        this.cookieHandler = (CookieHandler)AccessController.doPrivileged(new PrivilegedAction() {
            public CookieHandler run() {
                return CookieHandler.getDefault();
            }
        });
    }

    this.cacheHandler = (ResponseCache)AccessController.doPrivileged(new PrivilegedAction() {
        public ResponseCache run() {
            return ResponseCache.getDefault();
        }
    });
}

去掉部分不重要的代碼之后,剩下的代碼貌似都在獲取緩存值。沒有進(jìn)行正常的網(wǎng)絡(luò)連接。

此時(shí)猜測(cè) API26的HttpUrlConnection的網(wǎng)絡(luò)請(qǐng)求發(fā)生在 getInputStream()中,而 openConnection()是為獲取上次請(qǐng)求的緩存狀態(tài)

getInputStream()

URLConnection.java中

public InputStream getInputStream() throws IOException {
    throw new UnknownServiceException("protocol doesn't support input");
}

同樣URLConnection是個(gè)抽象類,跳到對(duì)應(yīng)的HttpUrlConnection.java類中

public synchronized InputStream getInputStream() throws IOException {
    this.connecting = true;
    SocketPermission var1 = this.URLtoSocketPermission(this.url);
    if(var1 != null) {
        try {
            return (InputStream)AccessController.doPrivilegedWithCombiner(new PrivilegedExceptionAction() {
                public InputStream run() throws IOException {
                    return HttpURLConnection.this.getInputStream0();
                }
            }, (AccessControlContext)null, new Permission[]{var1});
        } catch (PrivilegedActionException var3) {
            throw (IOException)var3.getException();
        }
    } else {
        return this.getInputStream0();
    }
}

一系列處理最后都會(huì)跳轉(zhuǎn)到getInputStream0()函數(shù)內(nèi)

getInputStream0()函數(shù)中的代碼過(guò)多,就不貼出來(lái)了。來(lái)大概講下流程

//判斷是否有緩存,有的話直接返回
if(this.inputStream != null) {
   return this.inputStream;
}


//是否正在進(jìn)行輸入輸出字符流的行為,如果有的判斷輸出流,因?yàn)檫@時(shí)網(wǎng)絡(luò)連接還未建立,所以只可能是輸出流到緩存
 if(this.streaming()) {
    if(this.strOutputStream == null) {
        this.getOutputStream();
    }

    this.strOutputStream.close();
    if(!this.strOutputStream.writtenOK()) {
        throw new IOException("Incomplete output stream");
    }
  }

同樣 getOutputStream 會(huì)跳到getOutputStream0()中

getOutputStream()

//會(huì)對(duì)URL做一次校驗(yàn),主要針對(duì)host,header,protocol和authority等
SocketPermission var1 = this.URLtoSocketPermission(this.url);

getOutputStream0()

//有寫流的行為的話,就一定是POST請(qǐng)求,因?yàn)镚ET請(qǐng)求不需要請(qǐng)求體
//所以強(qiáng)制修改method為POST
if(this.method.equals("GET")) {
    this.method = "POST";
}

//檢查是否有可用的鏈接,沒有則進(jìn)行重新鏈接
if(!this.checkReuseConnection()) {
    this.connect();
}

checkReuseConnection()

//先檢查connected,再檢查reuseClient,說(shuō)明reuseClient是connect的基礎(chǔ)連接
private boolean checkReuseConnection() {
    if(this.connected) {
        return true;
    } else if(this.reuseClient != null) {
        this.http = this.reuseClient;
        this.http.setReadTimeout(this.getReadTimeout());
        this.http.reuse = false;
        this.reuseClient = null;
        this.connected = true;
        return true;
    } else {
        return false;
    }
}

回到getOutputStream0()

//添加請(qǐng)求部分
if(this.streaming() && this.strOutputStream == null) {
    this.writeRequests();
}

接下來(lái)回到主流程 connect()部分會(huì)調(diào)用 plainConnect0()方法

有緩存拿緩存,緩存信息完整的話就不需要重新進(jìn)行網(wǎng)絡(luò)連接了

if(this.cacheHandler != null && this.getUseCaches()) {
    try {
        //對(duì)url做一次處理,兼容一些缺少/等情況的url
        URI var1 = ParseUtil.toURI(this.url);
        if(var1 != null) {
            this.cachedResponse = this.cacheHandler.get(var1, this.getRequestMethod(), this.getUserSetHeaders().getHeaders());
            if("https".equalsIgnoreCase(var1.getScheme()) && !(this.cachedResponse instanceof SecureCacheResponse)) {
                this.cachedResponse = null;
            }

            if(logger.isLoggable(Level.FINEST)) {
                logger.finest("Cache Request for " + var1 + " / " + this.getRequestMethod());
                logger.finest("From cache: " + (this.cachedResponse != null?this.cachedResponse.toString():"null"));
            }

            if(this.cachedResponse != null) {
                this.cachedHeaders = this.mapToMessageHeader(this.cachedResponse.getHeaders());
                this.cachedInputStream = this.cachedResponse.getBody();
            }
        }
    } catch (IOException var6) {
        ;
    }

    if(this.cachedHeaders != null && this.cachedInputStream != null) {
        this.connected = true;
        return;
    }

    this.cachedResponse = null;
}

沒有連接緩存的話

//第一次連接還是再次連接對(duì)應(yīng)的方法不一樣
if(!this.failedOnce) {
    this.http = this.getNewHttpClient(this.url, (Proxy)null, this.connectTimeout);
    this.http.setReadTimeout(this.readTimeout);
} else {
    this.http = this.getNewHttpClient(this.url, (Proxy)null, this.connectTimeout, false);
    this.http.setReadTimeout(this.readTimeout);
}

不管是否是第一次連接都會(huì)生成一個(gè)HttpClient對(duì)象,而這個(gè)對(duì)象才是連接網(wǎng)絡(luò)的主要成員。其中變量var3表示是否是第一次發(fā)起連接,如果是,并且httpClient對(duì)象var5是有緩存的,這時(shí)需要做一些緩存清理,變量重置的操作。因?yàn)樾枰匦麻_啟一個(gè)新的連接,需要先把老的連接清理掉

public static HttpClient New(URL var0, Proxy var1, int var2, boolean var3, HttpURLConnection var4) throws IOException {
    if(var1 == null) {
        var1 = Proxy.NO_PROXY;
    }

    HttpClient var5 = null;
    if(var3) {
        var5 = kac.get(var0, (Object)null);
        if(var5 != null && var4 != null && var4.streaming() && var4.getRequestMethod() == "POST" && !var5.available()) {
            var5.inCache = false;
            var5.closeServer();
            var5 = null;
        }

        if(var5 != null) {
            if(var5.proxy != null && var5.proxy.equals(var1) || var5.proxy == null && var1 == null) {
                synchronized(var5) {
                    var5.cachedHttpClient = true;

                    assert var5.inCache;

                    var5.inCache = false;
                    if(var4 != null && var5.needsTunneling()) {
                        var4.setTunnelState(TunnelState.TUNNELING);
                    }

                    logFinest("KeepAlive stream retrieved from the cache, " + var5);
                }
            } else {
                synchronized(var5) {
                    var5.inCache = false;
                    var5.closeServer();
                }

                var5 = null;
            }
        }
    }
    
    //生成HttpClient對(duì)象,進(jìn)行網(wǎng)絡(luò)連接
    if(var5 == null) {
        var5 = new HttpClient(var0, var1, var2);
    } else {
        SecurityManager var6 = System.getSecurityManager();
        if(var6 != null) {
            if(var5.proxy != Proxy.NO_PROXY && var5.proxy != null) {
                var6.checkConnect(var0.getHost(), var0.getPort());
            } else {
                var6.checkConnect(InetAddress.getByName(var0.getHost()).getHostAddress(), var0.getPort());
            }
        }

        var5.url = var0;
    }

    return var5;
}

**HttpClient.java **

protected HttpClient(URL var1, Proxy var2, int var3) throws IOException {
    this.cachedHttpClient = false;
    this.poster = null;
    this.failedOnce = false;
    this.ignoreContinue = true;
    this.usingProxy = false;
    this.keepingAlive = false;
    this.keepAliveConnections = -1;
    this.keepAliveTimeout = 0;
    this.cacheRequest = null;
    this.reuse = false;
    this.capture = null;
    this.proxy = var2 == null?Proxy.NO_PROXY:var2;
    this.host = var1.getHost();
    this.url = var1;
    this.port = var1.getPort();
    if(this.port == -1) {
        this.port = this.getDefaultPort();
    }

    this.setConnectTimeout(var3);
    this.capture = HttpCapture.getCapture(var1);
    //開啟連接服務(wù)
    this.openServer();
}

openServer()函數(shù)內(nèi)調(diào)用 openServer(this.host, this.port);
傳入host和port開啟 serverSocket.setTcpNoDelay(true);服務(wù)

網(wǎng)絡(luò)連接成功,http請(qǐng)求參數(shù)也已經(jīng)設(shè)置成功,接下來(lái)只等待網(wǎng)絡(luò)回調(diào)把數(shù)據(jù)流寫會(huì)緩存即可。

補(bǔ)充說(shuō)明

  • HttpURLConnection的connect()函數(shù),實(shí)際上只是建立了一個(gè)與服務(wù)器的tcp連接,并沒有實(shí)際發(fā)送http請(qǐng)求。 無(wú)論是post還是get,http請(qǐng)求實(shí)際上直到HttpURLConnection的getInputStream()這個(gè)函數(shù)里面才正式發(fā)送出去。
  • 在用POST方式發(fā)送URL請(qǐng)求時(shí),URL請(qǐng)求參數(shù)的設(shè)定順序是重中之重, 對(duì)connection對(duì)象的一切配置(那一堆set函數(shù)) 都必須要在connect()函數(shù)執(zhí)行之前完成。而對(duì)outputStream的寫操作,又必須要在inputStream的讀操作之前。 這些順序?qū)嶋H上是由http請(qǐng)求的格式?jīng)Q定的。
  • http請(qǐng)求實(shí)際上由兩部分組成, 一個(gè)是http頭,所有關(guān)于此次http請(qǐng)求的配置都在http頭里面定義, 一個(gè)是正文content。 connect()函數(shù)會(huì)根據(jù)HttpURLConnection對(duì)象的配置值生成http頭部信息,因此在調(diào)用connect函數(shù)之前, 就必須把所有的配置準(zhǔn)備好。
  • 在http頭后面緊跟著的是http請(qǐng)求的正文,正文的內(nèi)容是通過(guò)outputStream流寫入的,實(shí)際上outputStream不是一個(gè)網(wǎng)絡(luò)流,充其量是個(gè)字符串流,往里面寫入的東西不會(huì)立即發(fā)送到網(wǎng)絡(luò), 而是存在于內(nèi)存緩沖區(qū)中,待outputStream流關(guān)閉時(shí),根據(jù)輸入的內(nèi)容生成http正文。 至此,http請(qǐng)求的東西已經(jīng)全部準(zhǔn)備就緒。在getInputStream()函數(shù)調(diào)用的時(shí)候,就會(huì)把準(zhǔn)備好的http請(qǐng)求 正式發(fā)送到服務(wù)器了,然后返回一個(gè)輸入流,用于讀取服務(wù)器對(duì)于此次http請(qǐng)求的返回信息。由于http 請(qǐng)求在getInputStream的時(shí)候已經(jīng)發(fā)送出去了(包括http頭和正文),因此在getInputStream()函數(shù) 之后對(duì)connection對(duì)象進(jìn)行設(shè)置(對(duì)http頭的信息進(jìn)行修改)或者寫入outputStream(對(duì)正文進(jìn)行修改) 都是沒有意義的了,執(zhí)行這些操作會(huì)導(dǎo)致異常的發(fā)生。

參考文章

Android-淺析-HttpURLConnection
網(wǎng)絡(luò)請(qǐng)求HttpURLConnection剖析
Android每周一輪子:HttpURLConnection

另外

個(gè)人的github
閑暇之余寫的故事

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,318評(píng)論 25 708
  • 6.1 公鑰密鑰加密原理 6.1.1 基礎(chǔ)知識(shí) 密鑰:一般就是一個(gè)字符串或數(shù)字,在加密或者解密時(shí)傳遞給加密/解密算...
    AndroidMaster閱讀 4,116評(píng)論 1 8
  • 網(wǎng)絡(luò)請(qǐng)求是android客戶端很重要的部分。下面從入門級(jí)開始介紹下自己Android網(wǎng)絡(luò)請(qǐng)求的實(shí)踐歷程。希望能給剛...
    passiontim閱讀 1,469評(píng)論 0 17
  • 讓我感謝你,贈(zèng)我空歡喜。 01 昨天周杰倫杭州演唱會(huì)點(diǎn)歌環(huán)節(jié)~有個(gè)妹子說(shuō):“杰倫,今天我的前男友也在現(xiàn)場(chǎng),他要結(jié)婚...
    七米霞閱讀 1,080評(píng)論 26 30
  • R 閱讀原文 如果我們想利用他人的內(nèi)疚,我們通常采取的辦法是,把自己不愉快的感受歸咎于對(duì)方。家長(zhǎng)也許會(huì)和孩子說(shuō):“...
    木一葸二毛閱讀 303評(píng)論 1 0

友情鏈接更多精彩內(nèi)容