網(wǎng)絡庫簡單總結

網(wǎng)絡庫的介紹

1.HttpURLConnection

API簡單,體積較小,因而非常適用于Android項目,但是在android 2.2及以下版本中HttpUrlConnection存在著一些bug,所以建議在android 2.3以后使用HttpUrlConnection,在這之前使用的是HttpClient。

2.HttpClient (Apache )

高效穩(wěn)定,但是維護成本高昂,故android 開發(fā)團隊不愿意維護該庫更青睞輕便的HttpUrlConnection。Android 5.0后已廢棄該庫。

3.OKHttp

Square公司產(chǎn)品,OkHttp相比HttpURLConnection和HttpClient功能更加強大。

4.Volley

Volley是在2013年Google I/O大會上推出了一個新的網(wǎng)絡通信框架,內部封裝了HttpURLConnection和HttpClient, 解決了網(wǎng)絡數(shù)據(jù)解析和線程切換的問題。
主要用于解決通訊頻率高,但傳輸數(shù)據(jù)量小的情景而對于大數(shù)據(jù)量的網(wǎng)絡操作,比如說下載文件等,Volley的表現(xiàn)就會非常糟糕。

其實Volley的使用是很簡單的,總的來說就是發(fā)送一個http的請求,將請求加入到RequestQueue(請求隊列)中,這里的RequestQueue是一個請求隊列對象,它可以緩存所有的HTTP請求,然后按照一定的算法并發(fā)地發(fā)出這些請求。RequestQueue內部的設計就是非常合適高并發(fā)的,因此我們不必為每一次HTTP請求都創(chuàng)建一個RequestQueue對象,這是非常浪費資源的,基本上在每一個需要和網(wǎng)絡交互的Activity中創(chuàng)建一個RequestQueue對象就足夠了。

總的來說我們常用的Volley就下面三個步驟:Volley.newRequestQueue(context).add(request);
1. 創(chuàng)建一個RequestQueue對象。
2. 創(chuàng)建一個StringRequest對象。
3. 將StringRequest對象添加到RequestQueue里面。
下面我們主要通過這三句話來分析一下Volley源碼中的實現(xiàn)原理

volley.png

這是官方的關于Volley工作流程圖 , 其中藍色部分代表主線程,綠色部分代表緩存線程,橙色部分代表網(wǎng)絡線程。我們在主線程中調用RequestQueue的add()方法來添加一條網(wǎng)絡請求,這條請求會先被加入到緩存隊列當中,如果發(fā)現(xiàn)可以找到相應的緩存結果就直接讀取緩存并解析,然后回調給主線程。如果在緩存中沒有找到結果,則將這條請求加入到網(wǎng)絡請求隊列中,然后處理發(fā)送HTTP請求,解析響應結果,寫入緩存,并回調主線程。

第一步:newRequestQueue(context)

 public static RequestQueue newRequestQueue(Context context) {
        return newRequestQueue(context, null);  //執(zhí)行帶兩個參數(shù)的構造方法
    }

這個方法僅僅只有一行代碼,只是調用了newRequestQueue()的方法重載,并給第二個參數(shù)傳入null。那我們看下帶有兩個參數(shù)的newRequestQueue()方法中的代碼,如下所示:

 public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
        File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);

        String userAgent = "volley/0";
        try {
            String packageName = context.getPackageName();
            PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
            userAgent = packageName + "/" + info.versionCode;
        } catch (NameNotFoundException e) {
        }

        if (stack == null) {   //判斷如果stack是等于null的,則去創(chuàng)建一個HttpStack對象
            if (Build.VERSION.SDK_INT >= 9) {
                stack = new HurlStack();
            } else {
                // Prior to Gingerbread, HttpUrlConnection was unreliable.
                // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }

        Network network = new BasicNetwork(stack);

        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        queue.start();

        return queue;
    }

在上面的代碼中可以看出,如果stack是等于null的,則去創(chuàng)建一個HttpStack對象,這里會判斷如果手機系統(tǒng)版本號是大于9(這里指SDK版本)的,則創(chuàng)建一個HurlStack的實例,否則就創(chuàng)建一個HttpClientStack的實例。實際上HurlStack的內部就是使用HttpURLConnection進行網(wǎng)絡通訊的,而HttpClientStack的內部則是使用HttpClient進行網(wǎng)絡通訊的,至于為什么說需要大于或等于9,是因為SDK為9時對應的系統(tǒng)為android2.3,版本2.3以后推薦使用HttpURLConnection.
創(chuàng)建好了HttpStack之后,接下來又創(chuàng)建了一個Network對象,它是用于根據(jù)傳入的HttpStack對象來處理網(wǎng)絡請求的,緊接著new出一個RequestQueue對象,并調用它的start()方法進行啟動,然后將RequestQueue返回,這樣newRequestQueue()的方法就執(zhí)行結束了。接下啦我們看看RequestQueue的start()方法內部執(zhí)行內容:

    /** Number of network request dispatcher threads to start. */
    private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;//默認網(wǎng)絡請求線程數(shù)量

    /**
     * Starts the dispatchers in this queue.
     */
    public void start() {
        stop();  // Make sure any currently running dispatchers are stopped.
        // Create the cache dispatcher and start it.
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();

        // Create network dispatchers (and corresponding threads) up to the pool size.
        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                    mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

這里先是創(chuàng)建了一個CacheDispatcher的實例,然后調用了它的start()方法,接著在一個for循環(huán)里去創(chuàng)建NetworkDispatcher的實例,并分別調用它們的start()方法。這里的CacheDispatcher和NetworkDispatcher都是繼承自Thread的,而默認情況下for循環(huán)會執(zhí)行四次,也就是說當調用了Volley.newRequestQueue(context)之后,就會有五個線程一直在后臺運行,不斷等待網(wǎng)絡請求的到來,其中CacheDispatcher是緩存線程,NetworkDispatcher是網(wǎng)絡請求線程。

上面得到了RequestQueue之后,我們只需要構建出相應的Request,然后調用RequestQueue的add()方法將Request傳入就可以完成網(wǎng)絡請求操作了,下面我們分析一下add()方法的內部的邏輯

 /**
     * Adds a Request to the dispatch queue.
     * @param request The request to service
     * @return The passed-in request
     */
    public <T> Request<T> add(Request<T> request) {
        // Tag the request as belonging to this queue and add it to the set of current requests.
        request.setRequestQueue(this);
        synchronized (mCurrentRequests) {
            mCurrentRequests.add(request);
        }

        // Process requests in the order they are added.
        request.setSequence(getSequenceNumber());
        request.addMarker("add-to-queue");

        // If the request is uncacheable, skip the cache queue and go straight to the network.
        if (!request.shouldCache()) {  //判斷當前的請求是否可以緩存
            mNetworkQueue.add(request);  //如果不能緩存則直接將這條請求加入網(wǎng)絡請求隊列
            return request;
        }

        // Insert request into stage if there's already a request with the same cache key in flight.
        synchronized (mWaitingRequests) {
            String cacheKey = request.getCacheKey();
            if (mWaitingRequests.containsKey(cacheKey)) {
                // There is already a request in flight. Queue up.
                Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
                if (stagedRequests == null) {
                    stagedRequests = new LinkedList<Request<?>>();
                }
                stagedRequests.add(request);
                mWaitingRequests.put(cacheKey, stagedRequests);
                if (VolleyLog.DEBUG) {
                    VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
                }
            } else {
                // Insert 'null' queue for this cacheKey, indicating there is now a request in
                // flight.
                mWaitingRequests.put(cacheKey, null);
                mCacheQueue.add(request);  //可以緩存的話則在將這條請求加入緩存隊列
            }
            return request;
        }
    }

可以看到,開始的時候會判斷當前的請求是否可以緩存,如果不能緩存則直接將這條請求加入網(wǎng)絡請求隊列,可以緩存的話則將這條請求加入緩存隊列。在默認情況下,每條請求都是可以緩存的,當然我們也可以調用Request的setShouldCache(false)方法來改變這一默認行為。
既然默認每條請求都是可以緩存的,自然就被添加到了緩存隊列中,于是一直在后臺等待的緩存線程就要開始運行起來了,我們看下CacheDispatcher中的run()方法,代碼如下所示:

public class CacheDispatcher extends Thread {
    ......  //省略代碼
    @Override
    public void run() {
        if (DEBUG) VolleyLog.v("start new dispatcher");
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

        // Make a blocking call to initialize the cache.
        mCache.initialize();

        while (true) {   //死循環(huán)
            try {
                // Get a request from the cache triage queue, blocking until
                // at least one is available.
                final Request<?> request = mCacheQueue.take();
                request.addMarker("cache-queue-take");

                // If the request has been canceled, don't bother dispatching it.
                if (request.isCanceled()) {
                    request.finish("cache-discard-canceled");
                    continue;
                }

                // Attempt to retrieve this item from cache.
                Cache.Entry entry = mCache.get(request.getCacheKey());  //從緩存當中取出響應結果
                if (entry == null) {  //如何為空的話則把這條請求加入到網(wǎng)絡請求隊列中
                    request.addMarker("cache-miss");
                    // Cache miss; send off to the network dispatcher.
                    mNetworkQueue.put(request);
                    continue;
                }

                // If it is completely expired, just send it to the network.
                if (entry.isExpired()) {  //如果不為空的但該緩存已過期,則同樣把這條請求加入到網(wǎng)絡請求隊列中
                    request.addMarker("cache-hit-expired");
                    request.setCacheEntry(entry);
                    mNetworkQueue.put(request);
                    continue;
                }

                // We have a cache hit; parse its data for delivery back to the request.
                request.addMarker("cache-hit");
                Response<?> response = request.parseNetworkResponse(
                        new NetworkResponse(entry.data, entry.responseHeaders));   //對數(shù)據(jù)進行解析
                request.addMarker("cache-hit-parsed"); 

                if (!entry.refreshNeeded()) {
                    // Completely unexpired cache hit. Just deliver the response.
                    mDelivery.postResponse(request, response);
                } else {
                    // Soft-expired cache hit. We can deliver the cached response,
                    // but we need to also send the request to the network for
                    // refreshing.
                    request.addMarker("cache-hit-refresh-needed");
                    request.setCacheEntry(entry);

                    // Mark the response as intermediate.
                    response.intermediate = true;

                    // Post the intermediate response back to the user and have
                    // the delivery then forward the request along to the network.
                    mDelivery.postResponse(request, response, new Runnable() {
                        @Override
                        public void run() {
                            try {
                                mNetworkQueue.put(request);
                            } catch (InterruptedException e) {
                                // Not much we can do about this.
                            }
                        }
                    });
                }

            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }
        }
    }
}

可以看到一個while(true)循環(huán),說明緩存線程始終是在運行的,接著會嘗試從緩存當中取出響應結果,如何為空的話則把這條請求加入到網(wǎng)絡請求隊列中,如果不為空的話再判斷該緩存是否已過期,如果已經(jīng)過期了則同樣把這條請求加入到網(wǎng)絡請求隊列中,否則就認為不需要重發(fā)網(wǎng)絡請求,直接使用緩存中的數(shù)據(jù)即可。之后會調用Request的parseNetworkResponse()方法來對數(shù)據(jù)進行解析,再往后就是將解析出來的數(shù)據(jù)進行回調了,這部分代碼的邏輯和NetworkDispatcher后半部分的邏輯是基本相同的,那么我們等下合并在一起看就好了,先來看一下NetworkDispatcher中是怎么處理網(wǎng)絡請求隊列的,代碼如下所示:

public class NetworkDispatcher extends Thread {
    .......//省略代碼
    @Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        while (true) {  //死循環(huán)
            long startTimeMs = SystemClock.elapsedRealtime();
            Request<?> request;
            try {
                // Take a request from the queue.
                request = mQueue.take();
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }

            try {
                request.addMarker("network-queue-take");

                // If the request was cancelled already, do not perform the
                // network request.
                if (request.isCanceled()) {
                    request.finish("network-discard-cancelled");
                    continue;
                }

                addTrafficStatsTag(request);

                // Perform the network request.
                NetworkResponse networkResponse = mNetwork.performRequest(request);  //執(zhí)行網(wǎng)絡請求
                request.addMarker("network-http-complete");

                // If the server returned 304 AND we delivered a response already,
                // we're done -- don't deliver a second identical response.
                if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                    request.finish("not-modified");
                    continue;
                }

                // Parse the response here on the worker thread.
                Response<?> response = request.parseNetworkResponse(networkResponse);  //解析響應的網(wǎng)絡數(shù)據(jù)
                request.addMarker("network-parse-complete");

                // Write to cache if applicable.
                // TODO: Only update cache metadata instead of entire record for 304s.
                if (request.shouldCache() && response.cacheEntry != null) {
                    mCache.put(request.getCacheKey(), response.cacheEntry);
                    request.addMarker("network-cache-written");
                }

                // Post the response back.
                request.markDelivered();
                mDelivery.postResponse(request, response);
            } catch (VolleyError volleyError) {
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                parseAndDeliverNetworkError(request, volleyError);
            } catch (Exception e) {
                VolleyLog.e(e, "Unhandled exception %s", e.toString());
                VolleyError volleyError = new VolleyError(e);
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                mDelivery.postError(request, volleyError);
            }
        }
    }

    private void parseAndDeliverNetworkError(Request<?> request, VolleyError error) {
        error = request.parseNetworkError(error);
        mDelivery.postError(request, error);
    }
}

同樣地,我們看到了類似的while(true)循環(huán),說明網(wǎng)絡請求線程也是在不斷運行的。在死循環(huán)中會調用Network的performRequest()方法來去發(fā)送網(wǎng)絡請求,而Network是一個接口,我們在上面創(chuàng)建 stack是通過這段代碼(Network network = new BasicNetwork(stack);)實現(xiàn)網(wǎng)絡請求的,所以這里具體的實現(xiàn)是BasicNetwork,所以我們在BasicNetwork類中查看performRequest()方法,如下所示:

public class BasicNetwork implements Network {
   .....//省略代碼
    @Override
    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        long requestStart = SystemClock.elapsedRealtime();
        while (true) {
            HttpResponse httpResponse = null;
            byte[] responseContents = null;
            Map<String, String> responseHeaders = Collections.emptyMap();
            try {
                // Gather headers.
                Map<String, String> headers = new HashMap<String, String>();
                addCacheHeaders(headers, request.getCacheEntry());
                httpResponse = mHttpStack.performRequest(request, headers); //調用了HttpStack的performRequest()方法
                StatusLine statusLine = httpResponse.getStatusLine();
                int statusCode = statusLine.getStatusCode();

                responseHeaders = convertHeaders(httpResponse.getAllHeaders());
                // Handle cache validation.
                if (statusCode == HttpStatus.SC_NOT_MODIFIED) {

                    Entry entry = request.getCacheEntry();
                    if (entry == null) {
                        return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,
                                responseHeaders, true,
                                SystemClock.elapsedRealtime() - requestStart);  //組裝成一個NetworkResponse對象進行返回
                    }
                    // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
                    entry.responseHeaders.putAll(responseHeaders);
                    return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
                            entry.responseHeaders, true,
                            SystemClock.elapsedRealtime() - requestStart); //組裝成一個NetworkResponse對象進行返回
                }

                // Some responses such as 204s do not have content.  We must check.
                if (httpResponse.getEntity() != null) {
                  responseContents = entityToBytes(httpResponse.getEntity());
                } else {
                  // Add 0 byte response as a way of honestly representing a
                  // no-content request.
                  responseContents = new byte[0];
                }

                // if the request is slow, log it.
                long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
                logSlowRequests(requestLifetime, request, responseContents, statusLine);

                if (statusCode < 200 || statusCode > 299) {
                    throw new IOException();
                }
                return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
                        SystemClock.elapsedRealtime() - requestStart);
            } catch (SocketTimeoutException e) {
                    ...... //省略代碼
            }
        }
    }
}

這段代碼主要是一些網(wǎng)絡請求細節(jié)方面的東西,需要注意的是調用了HttpStack的performRequest()方法,這里的HttpStack就是在一開始調用newRequestQueue()方法是創(chuàng)建的實例,默認情況下如果系統(tǒng)版本號大于9就創(chuàng)建的HurlStack對象,否則創(chuàng)建HttpClientStack對象。這兩個對象的內部實際就是分別使用HttpURLConnection和HttpClient來發(fā)送網(wǎng)絡請求的,之后會將服務器返回的數(shù)據(jù)組裝成一個NetworkResponse對象進行返回。

在NetworkDispatcher中收到了NetworkResponse這個返回值后又會調用Request的parseNetworkResponse()方法來解析NetworkResponse中的數(shù)據(jù),以及將數(shù)據(jù)寫入到緩存,這個方法的實現(xiàn)是交給Request的子類來完成的,因為不同種類的Request解析的方式也肯定不同。如果想自定義Request的方式,其中parseNetworkResponse()這個方法就是必須要重寫的
在解析完了NetworkResponse中的數(shù)據(jù)之后,又會調用ExecutorDelivery的postResponse()方法來回調解析出的數(shù)據(jù),代碼如下所示:

1.在NetworkDispatcher類中
       private final ResponseDelivery mDelivery;
 // Parse the response here on the worker thread.
                Response<?> response = request.parseNetworkResponse(networkResponse);   //解析數(shù)據(jù)
                request.addMarker("network-parse-complete");

                mDelivery.postResponse(request, response);  //回調解析出的數(shù)據(jù),具體實現(xiàn)在下面代碼中

2.在ExecutorDelivery類中回調解析出來的數(shù)據(jù)
public class ExecutorDelivery implements ResponseDelivery {
    @Override
    public void postResponse(Request<?> request, Response<?> response) {
        postResponse(request, response, null);
    }

    @Override
    public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
        request.markDelivered();
        request.addMarker("post-response");
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
    }
}

其中,在mResponsePoster的execute()方法中傳入了一個ResponseDeliveryRunnable對象,這樣就可以保證該對象中的run()方法就是在主線程當中運行的了,我們看下run()方法中的代碼是什么樣的:

private class ResponseDeliveryRunnable implements Runnable {
        @SuppressWarnings("unchecked")
        @Override
        public void run() {
            // If this request has canceled, finish it and don't deliver.
            if (mRequest.isCanceled()) {
                mRequest.finish("canceled-at-delivery");
                return;
            }

            // Deliver a normal response or error, depending.
            if (mResponse.isSuccess()) {
                mRequest.deliverResponse(mResponse.result);
            } else {
                mRequest.deliverError(mResponse.error);
            }

            // If this is an intermediate response, add a marker, otherwise we're done
            // and the request can be finished.
            if (mResponse.intermediate) {
                mRequest.addMarker("intermediate-response");
            } else {
                mRequest.finish("done");
            }

            // If we have been provided a post-delivery runnable, run it.
            if (mRunnable != null) {
                mRunnable.run();
            }
       }

分析重點代碼即可,主要看Request的deliverResponse()方法,這個方法也是我們在自定義Request時需要重寫的另外一個方法,每一條網(wǎng)絡請求的響應都是回調到這個方法中,最后我們再在這個方法中將響應的數(shù)據(jù)回調到Response.Listener的onResponse()方法中就可以了。

5.Retrofit.

Square公司產(chǎn)品,內部封裝了OKhttp, 解決了網(wǎng)絡數(shù)據(jù)解析和線程切換的問題。

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容