volley(3)

我們再來看看volley是怎么工作的。首先還是要帶著重點去看源碼,我們要關注的地方除了最核心的工作流程之外,還有一個重點就是關心volley的緩存是怎么實現的。因此關注的重點可以分為3個:

  1. 同步請求的過程
  2. 緩存的過程
  3. 結果回調的過程

整個工作流程圖如下圖所示,從流程圖中大致可以看出整個流程大概可以是這樣的:添加請求Reqeust->查詢緩存->網絡獲取->結果回調。

volley工作流程圖.PNG

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

結合前面幾節的內容,包括volley的使用,網絡架構的設計,從中可以知道在一次請求執行的整個過程中是有很多類參與的,比如請求類Request,執行請求類HttpStack,響應類Response等等,在完整的分析volley的工作流程之前,我們還是先看看各個類的實現是怎么樣的。

3.1 Request

Request可以說作為volley框架的使用入口,因為每次請求的開始就是要構建一個Request對象,下面來看看Request的源碼,這里只摘取相對比較關鍵的部分。

public abstract class Request<T> implements Comparable<Request<T>> {
    /**
     * Supported request methods.
     */
    public interface Method {
        int DEPRECATED_GET_OR_POST = -1;
        int GET = 0;
        int POST = 1;
        int PUT = 2;
        int DELETE = 3;
    }

    /**
     * Priority values.  Requests will be processed from higher priorities to
     * lower priorities, in FIFO order.
     */
    public enum Priority {
        LOW,
        NORMAL,
        HIGH,
        IMMEDIATE
    }

    protected String getParamsEncoding() {
        return DEFAULT_PARAMS_ENCODING;
    }

    public String getBodyContentType() {
        return "application/x-www-form-urlencoded; charset=" + getParamsEncoding();
    }

    /**
     * Returns the raw POST or PUT body to be sent.
     *
     * @throws AuthFailureError in the event of auth failure
     */
    public byte[] getBody() throws AuthFailureError {
        Map<String, String> params = getParams();
        if (params != null && params.size() > 0) {
            return encodeParameters(params, getParamsEncoding());
        }
        return null;
    }

    /**
     * Converts <code>params</code> into an application/x-www-form-urlencoded encoded string.
     */
    private byte[] encodeParameters(Map<String, String> params, String paramsEncoding) {
        StringBuilder encodedParams = new StringBuilder();
        try {
            for (Map.Entry<String, String> entry : params.entrySet()) {
                encodedParams.append(URLEncoder.encode(entry.getKey(), paramsEncoding));
                encodedParams.append('=');
                encodedParams.append(URLEncoder.encode(entry.getValue(), paramsEncoding));
                encodedParams.append('&');
            }
            return encodedParams.toString().getBytes(paramsEncoding);
        } catch (UnsupportedEncodingException uee) {
            throw new RuntimeException("Encoding not supported: " + paramsEncoding, uee);
        }
    }


    abstract protected Response<T> parseNetworkResponse(NetworkResponse response);

    abstract protected void deliverResponse(T response);

    /** 
     * Our comparator sorts from high to low priority, and secondarily by
     * sequence number to provide FIFO ordering.
     */
    @Override
    public int compareTo(Request<T> other) {
        Priority left = this.getPriority();
        Priority right = other.getPriority();

        // High-priority requests are "lesser" so they are sorted to the front.
        // Equal priorities are sorted by sequence number to provide FIFO ordering.
        return left == right ?
                this.mSequence - other.mSequence :
                right.ordinal() - left.ordinal();
    }

}

在上面的源碼中,可以看出Request類關注的有以下幾點:

  1. Request的作用是提供構造請求報文時的必要參數,比如請求方法Method,編碼方式,POST請求方式時的Body參數字節數組等等(注意這里說的是POST,因為GET方式不需要,GET方式請求的參數是直接寫在url當中的);
  2. 提供優先級Priority并且利用Priority實現compareTo方法,作用是在Request添加進請求隊列RequestQueue時能夠將優先級高的放在前面從而優先執行;
  3. 最主要的就是提供parseNetworkResponse和deliverResponse接口方法,一個是將網絡返回的數據封裝成持有特定數據類型T的Response類,一個是將解析后的數據T傳送到主線程當中

思考:既然Request的最終目的是將數據傳回主線程,那么為什么分成兩個接口方法?直接在parseNetworkResponse中獲取到最終數據并返回到主線程不就行了么?還需要deliverResponse多此一舉?
答:Request的目的確實是將數據傳回主線程,這里分成兩個方法的原因是,在Request調用parseNetworkResponse()方法的時候,還處于子線程當中,因此不可以將數據返回到主線程。然后在之后的回調傳輸類Delivery.postResponse()之中才切換到了主線程,此時deliverResponse()會被調用,因此此時才可以確實將數據傳回主線程中用于UI更新。

然后在每次請求時需要創建Request的子類,此外還可以自己實現定制特定類型的Request的子類,使用方法參見前面的volley的基本使用以及自定義Request

3.2 Response

與Request相對應,Request類是將負責封裝請求報文中的各種參數信息,而Response類則是負責封裝相應報文中的數據信息,下面是Response的源碼。

public class Response<T> {

    /** Callback interface for delivering parsed responses. */
    public interface Listener<T> {
        /** Called when a response is received. */
        public void onResponse(T response);
    }

    /** Callback interface for delivering error responses. */
    public interface ErrorListener {
        /**
         * Callback method that an error has been occurred with the
         * provided error code and optional user-readable message.
         */
        public void onErrorResponse(VolleyError error);
    }

    /** Returns a successful response containing the parsed result. */
    public static <T> Response<T> success(T result, Cache.Entry cacheEntry) {
        return new Response<T>(result, cacheEntry);
    }

    /**
     * Returns a failed response containing the given error code and an optional
     * localized message displayed to the user.
     */
    public static <T> Response<T> error(VolleyError error) {
        return new Response<T>(error);
    }

    /** Parsed response, or null in the case of error. */
    public final T result;

    /** Cache metadata for this response, or null in the case of error. */
    public final Cache.Entry cacheEntry;

    /** Detailed error information if <code>errorCode != OK</code>. */
    public final VolleyError error;

    /** True if this response was a soft-expired one and a second one MAY be coming. */
    public boolean intermediate = false;

    /**
     * Returns whether this response is considered successful.
     */
    public boolean isSuccess() {
        return error == null;
    }


    private Response(T result, Cache.Entry cacheEntry) {
        this.result = result;
        this.cacheEntry = cacheEntry;
        this.error = null;
    }

    private Response(VolleyError error) {
        this.result = null;
        this.cacheEntry = null;
        this.error = error;
    }
}

由源碼可以看出,Response類主要的作用有兩點:

  1. 持有數據包解析后的特定類型T的數據、一些緩存數據和錯誤信息,主要還是數據T
  2. 提供一些接口Listener供外界使用,一般使用在Request的子類的deliverResponse方法當中

3.3 HttpStack

在介紹完Request類和Response類之后,可以發現,Request類是我們輸入的數據,Response是輸出的數據,而將Reqeust轉換成Response的便是加工執行類HttpStack,它是作用概括來講就是是執行Request請求,與服務器建立連接,并獲取到服務器返回的數據并封裝成HttpResponse類(這里只是封裝成HttpResponse類,而將HttpResponse轉換成Response類則是線程的工作)。因此HttpStack類在整個工作流程中必不可少的,十分重要。詳細分析的話可分為以下兩步:

  1. 提取Request中的參數信息,比如url,Method等等數據,創建HttpURLConnection對象并封裝相應的參數信息;
  2. 將HttpURLConnection中服務器返回的數據封裝成HttpResponse類

在這里需要注意的點就是,我們都知道網絡請求有兩種方式,一種是apache包中的HttpClient,另一種是Android自帶的HttpURLConnection,volley中采取的策略是,兩種方式都可以通過手動的方式設置,分別對應的是HttpClientStack和HurlStack,而在不手動設置的默認情況下,
在SDK9以上的Android版本使用HrulStack,即采用HttpURLConnection的方式
在SDK9以下的則使用HttpClientStack,即采用的是HttpClient的方式
由于Android6.0以后直接將apache包從SDK中移除了,因此HttpURLConnection將會成為Android中唯一進行網絡請求的方法,因此這里只介紹HurlStack。

以下是HttpStack接口的源碼以及HurlStack的部分對數據處理的源碼。
HttpStack接口就只有一個接口方法,從這里也能看出HttpStack子類的主要作用就是執行Reqeust請求并返回HttpResponse類對象。

public interface HttpStack {
    /**
     * Performs an HTTP request with the given parameters.
     *
     * <p>A GET request is sent if request.getPostBody() == null. A POST request is sent otherwise,
     * and the Content-Type header is set to request.getPostBodyContentType().</p>
     *
     * @param request the request to perform
     * @param additionalHeaders additional headers to be sent together with
     *         {@link Request#getHeaders()}
     * @return the HTTP response
     */
    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
        throws IOException, AuthFailureError;

}

HurlStack的源碼如下:

public class HurlStack implements HttpStack {

    private static final String HEADER_CONTENT_TYPE = "Content-Type";

    /**
     * An interface for transforming URLs before use.
     */
    public interface UrlRewriter {
        /**
         * Returns a URL to use instead of the provided one, or null to indicate
         * this URL should not be used at all.
         */
        public String rewriteUrl(String originalUrl);
    }

    private final UrlRewriter mUrlRewriter;
    private final SSLSocketFactory mSslSocketFactory;

    public HurlStack() {
        this(null);
    }

    /**
     * @param urlRewriter Rewriter to use for request URLs
     */
    public HurlStack(UrlRewriter urlRewriter) {
        this(urlRewriter, null);
    }

    /**
     * @param urlRewriter Rewriter to use for request URLs
     * @param sslSocketFactory SSL factory to use for HTTPS connections
     */
    public HurlStack(UrlRewriter urlRewriter, SSLSocketFactory sslSocketFactory) {
        mUrlRewriter = urlRewriter;
        mSslSocketFactory = sslSocketFactory;
    }

    @Override
    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
            throws IOException, AuthFailureError {
        String url = request.getUrl();
        HashMap<String, String> map = new HashMap<String, String>();
        map.putAll(request.getHeaders());
        map.putAll(additionalHeaders);
        if (mUrlRewriter != null) {
            String rewritten = mUrlRewriter.rewriteUrl(url);
            if (rewritten == null) {
                throw new IOException("URL blocked by rewriter: " + url);
            }
            url = rewritten;
        }
        URL parsedUrl = new URL(url);
        HttpURLConnection connection = openConnection(parsedUrl, request);
        for (String headerName : map.keySet()) {
            connection.addRequestProperty(headerName, map.get(headerName));
        }
        setConnectionParametersForRequest(connection, request);
        // Initialize HttpResponse with data from the HttpURLConnection.
        ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
        int  = connection.getResponseCode();
        if (responseCoderesponseCode == -1) {
            // -1 is returned by getResponseCode() if the response code could not be retrieved.
            // Signal to the caller that something was wrong with the connection.
            throw new IOException("Could not retrieve response code from HttpUrlConnection.");
        }
        StatusLine responseStatus = new BasicStatusLine(protocolVersion,
                connection.getResponseCode(), connection.getResponseMessage());
        BasicHttpResponse response = new BasicHttpResponse(responseStatus);
        response.setEntity(entityFromConnection(connection));
        for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
            if (header.getKey() != null) {
                Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
                response.addHeader(h);
            }
        }
        return response;
    }

    /**
     * Initializes an {@link HttpEntity} from the given {@link HttpURLConnection}.
     * @param connection
     * @return an HttpEntity populated with data from <code>connection</code>.
     */
    private static HttpEntity entityFromConnection(HttpURLConnection connection) {
        BasicHttpEntity entity = new BasicHttpEntity();
        InputStream inputStream;
        try {
            inputStream = connection.getInputStream();
        } catch (IOException ioe) {
            inputStream = connection.getErrorStream();
        }
        entity.setContent(inputStream);
        entity.setContentLength(connection.getContentLength());
        entity.setContentEncoding(connection.getContentEncoding());
        entity.setContentType(connection.getContentType());
        return entity;
    }

    /**
     * Create an {@link HttpURLConnection} for the specified {@code url}.
     */
    protected HttpURLConnection createConnection(URL url) throws IOException {
        return (HttpURLConnection) url.openConnection();
    }

    /**
     * Opens an {@link HttpURLConnection} with parameters.
     * @param url
     * @return an open connection
     * @throws IOException
     */
    private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException {
        HttpURLConnection connection = createConnection(url);

        int timeoutMs = request.getTimeoutMs();
        connection.setConnectTimeout(timeoutMs);
        connection.setReadTimeout(timeoutMs);
        connection.setUseCaches(false);
        connection.setDoInput(true);

        // use caller-provided custom SslSocketFactory, if any, for HTTPS
        if ("https".equals(url.getProtocol()) && mSslSocketFactory != null) {
            ((HttpsURLConnection)connection).setSSLSocketFactory(mSslSocketFactory);
        }

        return connection;
    }

    @SuppressWarnings("deprecation")
    /* package */ static void setConnectionParametersForRequest(HttpURLConnection connection,
            Request<?> request) throws IOException, AuthFailureError {
        switch (request.getMethod()) {
            case Method.DEPRECATED_GET_OR_POST:
                // This is the deprecated way that needs to be handled for backwards compatibility.
                // If the request's post body is null, then the assumption is that the request is
                // GET.  Otherwise, it is assumed that the request is a POST.
                byte[] postBody = request.getPostBody();
                if (postBody != null) {
                    // Prepare output. There is no need to set Content-Length explicitly,
                    // since this is handled by HttpURLConnection using the size of the prepared
                    // output stream.
                    connection.setDoOutput(true);
                    connection.setRequestMethod("POST");
                    connection.addRequestProperty(HEADER_CONTENT_TYPE,
                            request.getPostBodyContentType());
                    DataOutputStream out = new DataOutputStream(connection.getOutputStream());
                    out.write(postBody);
                    out.close();
                }
                break;
            case Method.GET:
                // Not necessary to set the request method because connection defaults to GET but
                // being explicit here.
                connection.setRequestMethod("GET");
                break;
            case Method.DELETE:
                connection.setRequestMethod("DELETE");
                break;
            case Method.POST:
                connection.setRequestMethod("POST");
                addBodyIfExists(connection, request);
                break;
            case Method.PUT:
                connection.setRequestMethod("PUT");
                addBodyIfExists(connection, request);
                break;
            default:
                throw new IllegalStateException("Unknown method type.");
        }
    }

    private static void addBodyIfExists(HttpURLConnection connection, Request<?> request)
            throws IOException, AuthFailureError {
        byte[] body = request.getBody();
        if (body != null) {
            connection.setDoOutput(true);
            connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getBodyContentType());
            DataOutputStream out = new DataOutputStream(connection.getOutputStream());
            out.write(body);
            out.close();
        }
    }
}


雖然源碼有一大長串,但其實都是圍繞著HttpStack的接口方法performRequest()展開的,只是將分別不同的邏輯封裝成不同的方法,比如openConnection方法只是用于創建Connection對象,entityFromConnection方法只是用于將Connection中的數據轉換成Entity對象而已。

我們已經知道HttpStack的工作就是將Request請求轉換成HttpResponse而已,在分析具體做法之前我們先來學習一下網絡請求的一些基礎:
網絡請求的關鍵在于連接參數的配置以及請求報文的構建
連接參數關鍵的參數有超時時間、讀取時間、是否允許輸入、是否使用緩存等等;
Http請求報文主要由3部分構成:起始行,請求頭部和請求數據,而請求數據只有在請求方式為POST時才有內容,GET方式并沒有請求數據,請求報文結構如下圖所示:

Http請求報文格式.PNG

此時HttpStack的工作就清晰明了了,大致上可分為這么3步:

  1. 網絡連接Connection的創建以及連接參數的配置
  2. 通過Request類對象向Connection對象添加請求報文信息,比如請求方式,請求頭部數據,請求數據(適用于請求方式為POST或者PUT的)
  3. 從Connection中獲取服務器響應數據并封裝成HttpResponse類對象

按照這些步驟查看volley中HurlStack的源碼,思路就十分清晰了,performRequest源碼內部確實是這么實現的。

……
//創建連接并配置連接參數
URL parsedUrl = new URL(url);
HttpURLConnection connection = openConnection(parsedUrl, request);
//添加請求報文頭部信息
for (String headerName : map.keySet()) {
    connection.addRequestProperty(headerName, map.get(headerName));
}
//添加請求方式以及請求數據(只有POST和PUT方式有請求數據)
setConnectionParametersForRequest(connection, request);

//以下就是將Connection中的數據封裝成HttpResponse的過程,具體看上面的全部源碼
……

3.4 NetworkDispatcher & Network

上面介紹了volley工作的3個核心封裝類Request,Response和HttpStack以及它們之間的關系,但是遺留的問題有,它們被調用工作的地方在哪里?還有上面說了HttpStack返回的只是HttpResponse并不是我們需要的Response對象,那么將HttpResponse轉換成Response是在哪里進行的?帶著問題我們開始學習這節知識,NetworkDispatcher和Network,即線程和任務(它們的關系類似于Thread和Runnable,NetworkDispatcher是子線程Thread,實際上工作的是Network)。

3.4.1 NetworkDispatcher

我們都知道Android中進行網絡處理都必須處在子線程當中,而volley當中進行網絡請求的核心類是HttpStack,不難得知HttpStack一定是工作在子線程當中的。volley當中的子線程則是NetworkDispatcher,然后在該線程當中通過Network任務進行網絡請求的任務,而Network中便有HttpStack在工作,因此此時便可以知道HttpStack是工作在子線程里面的了。

但是從NetworkDispatcher這個名字中可以看出來,它的工作應該是作為Network的分發者Dispatcher,因此實際上NetworkDispatcher的工作不僅僅是執行請求獲取響應數據,而且包含了從請求隊列中取出請求對象,執行請求對象獲取響應對象,解析響應對象獲取到最終類型數據,最后將數據post到主線程處理幾個工作流程。可以說,volley的網絡工作,從在子線程中進行請求到在主線程里面執行回調方法整個完整的流程就是在NetworkDispatcher中實現的。具體分為以下幾步:

  1. 從NetworkQueue(繼承于BlockingQueue)請求隊列中取出Request對象
  2. 通過Network對象通過performRequest執行Request請求獲取到NetworkResponse
  3. 通過Request的接口方法parseNetworkResponse將NetworkResponse解析成特定的Response對象
  4. 判斷如果需要緩存則將數據添加到mCache成員當中
  5. 通過ResponseDelivery的postResponse(request, response)方法中調用Resquest的deliverResponse將解析后的數據回調到主線程中處理,處理者通常是創建Request對象時傳入的Response.Listener對象

下面通過流程圖可以形象地看出NetworkDispatcher的工作流程:

NetworkDispatcher工作流程.PNG

然后結合流程圖查看源碼:

public class NetworkDispatcher extends Thread {
    /** The queue of requests to service. */
    private final BlockingQueue<Request> mQueue;
    /** The network interface for processing requests. */
    private final Network mNetwork;
    /** The cache to write to. */
    private final Cache mCache;
    /** For posting responses and errors. */
    private final ResponseDelivery mDelivery;
    /** Used for telling us to die. */
    private volatile boolean mQuit = false;

    /**
     * Creates a new network dispatcher thread.  You must call {@link #start()}
     * in order to begin processing.
     *
     * @param queue Queue of incoming requests for triage
     * @param network Network interface to use for performing requests
     * @param cache Cache interface to use for writing responses to cache
     * @param delivery Delivery interface to use for posting responses
     */
    public NetworkDispatcher(BlockingQueue<Request> queue,
            Network network, Cache cache,
            ResponseDelivery delivery) {
        mQueue = queue;
        mNetwork = network;
        mCache = cache;
        mDelivery = delivery;
    }

    /**
     * Forces this dispatcher to quit immediately.  If any requests are still in
     * the queue, they are not guaranteed to be processed.
     */
    public void quit() {
        mQuit = true;
        interrupt();
    }

    @Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        Request request;
        while (true) {
            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;
                }

                // Tag the request (if API >= 14)
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                    TrafficStats.setThreadStatsTag(request.getTrafficStatsTag());
                }

                // Perform the network request.
                NetworkResponse networkResponse = mNetwork.performRequest(request);
                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);
                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) {
                parseAndDeliverNetworkError(request, volleyError);
            } catch (Exception e) {
                VolleyLog.e(e, "Unhandled exception %s", e.toString());
                mDelivery.postError(request, new VolleyError(e));
            }
        }
    }

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

由上面的源碼可以看出,關注的重點有2個

  1. 類成員以及構造方法,創建一個NetworkDispatcher主要需要NetworkQueue、Network、Cache和ResponseDelivery四個成員,這里并不用去記憶,只要理解了Dispatcher的工作流程就能記住了,首先取出Request需要NetworkQueue,執行Request請求需要Network,緩存數據需要Cache,最后將數據傳送給主線程的ResponseDelivery。
  2. NetworkDispatcher是一個Thread,工作是在run()方法進行的,NetworkDispatcher的工作流程全都包括在run方法里面,結合之前分析的流程,從run方法中可以提取出關鍵的代碼,如下所示
//1.提取Request對象
request = mQueue.take();
//2.Network執行請求獲取NetworkResponse對象
NetworkResponse networkResponse = mNetwork.performRequest(request);
//3.Request調用接口方法將NetworkResponse對象解析成Response對象
Response<?> response = request.parseNetworkResponse(networkResponse);
//4.Cache對象緩存請求數據
if (request.shouldCache() && response.cacheEntry != null) {
    mCache.put(request.getCacheKey(), response.cacheEntry);
}
//5.ResponseDelivery將response回傳到主線程處理
mDelivery.postResponse(request, response);

3.4.2 Network

上面詳細介紹了NetworkDispatcher詳細的工作,也就是volley的整個網絡工作的流程,其中我們看到在執行Request請求這塊是通過Network實現的,所以這節來看一下Network的具體實現。

在volley中,Network只是一個接口(這里又體現了設計模式中的迪米特法則,即面向接口編程),里面只有一個接口方法performRequest,這里是不是跟之前的HttpStack接口很像,我們通過代碼來對比一下。

Network

public interface Network {

    public NetworkResponse performRequest(Request<?> request) throws VolleyError;
}

HttpStack

public interface HttpStack {

    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
        throws IOException, AuthFailureError;
}

可以看出來Network和HttpStack的作用其實都差不多一樣,只不過Network的performRequest返回的是NetworkResponse,而HttpStack是返回HttpResponse。在上一節NetworkDispatcher中已經提到,HttpStack是包含在Network里面的,意思就是Network只不過是將HttpStack返回的HttpResponse封裝成NetworkResponse而已,下面通過一個圖可以清楚的認識到Network包含HttpStack的關系。

Network與HttpStack的關系.PNG

從圖中可以看出,Request在傳遞給Network之后,Network通過performRequest進行處理,在處理過程中,首先將Request傳遞給了HttpStack處理,然后通過HttpStack的performRequest獲得HttpResponse對象,然后Network對HttpResponse加工處理后便返回NetworkResponse對象了。

注意,上面明明說Network只是個接口,怎么多出來的這個流程?上面直接用Network接口解釋其實是為了方便弄清楚工作流程,就像在上面HttpStack也只是個接口。在volley中有一個實現了Network接口的類BasicNetwork,其實上面的這些工作流程是BasicNetwork實現的,下面帶著流程去觀察BasicNetwork的源碼,主要還是看performRequest里面的就行了。

/**
 * A network performing Volley requests over an {@link HttpStack}.
 */
public class BasicNetwork implements Network {
    protected static final boolean DEBUG = VolleyLog.DEBUG;

    private static int SLOW_REQUEST_THRESHOLD_MS = 3000;

    private static int DEFAULT_POOL_SIZE = 4096;

    protected final HttpStack mHttpStack;

    protected final ByteArrayPool mPool;

    /**
     * @param httpStack HTTP stack to be used
     */
    public BasicNetwork(HttpStack httpStack) {
        // If a pool isn't passed in, then build a small default pool that will give us a lot of
        // benefit and not use too much memory.
        this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE));
    }

    /**
     * @param httpStack HTTP stack to be used
     * @param pool a buffer pool that improves GC performance in copy operations
     */
    public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) {
        mHttpStack = httpStack;
        mPool = pool;
    }

    @Override
    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        long requestStart = SystemClock.elapsedRealtime();
        while (true) {
            HttpResponse httpResponse = null;
            byte[] responseContents = null;
            Map<String, String> responseHeaders = new HashMap<String, String>();
            try {
                // Gather headers.
                Map<String, String> headers = new HashMap<String, String>();
                addCacheHeaders(headers, request.getCacheEntry());
                httpResponse = mHttpStack.performRequest(request, headers);
                StatusLine statusLine = httpResponse.getStatusLine();
                int statusCode = statusLine.getStatusCode();

                responseHeaders = convertHeaders(httpResponse.getAllHeaders());
                // Handle cache validation.
                if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
                    return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED,
                            request.getCacheEntry().data, responseHeaders, true);
                }

                // 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);
            } catch (SocketTimeoutException e) {
                attemptRetryOnException("socket", request, new TimeoutError());
            } catch (ConnectTimeoutException e) {
                attemptRetryOnException("connection", request, new TimeoutError());
            } catch (MalformedURLException e) {
                throw new RuntimeException("Bad URL " + request.getUrl(), e);
            } catch (IOException e) {
                int statusCode = 0;
                NetworkResponse networkResponse = null;
                if (httpResponse != null) {
                    statusCode = httpResponse.getStatusLine().getStatusCode();
                } else {
                    throw new NoConnectionError(e);
                }
                VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
                if (responseContents != null) {
                    networkResponse = new NetworkResponse(statusCode, responseContents,
                            responseHeaders, false);
                    if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
                            statusCode == HttpStatus.SC_FORBIDDEN) {
                        attemptRetryOnException("auth",
                                request, new AuthFailureError(networkResponse));
                    } else {
                        // TODO: Only throw ServerError for 5xx status codes.
                        throw new ServerError(networkResponse);
                    }
                } else {
                    throw new NetworkError(networkResponse);
                }
            }
        }
    }

    private void addCacheHeaders(Map<String, String> headers, Cache.Entry entry) {
        // If there's no cache entry, we're done.
        if (entry == null) {
            return;
        }

        if (entry.etag != null) {
            headers.put("If-None-Match", entry.etag);
        }

        if (entry.serverDate > 0) {
            Date refTime = new Date(entry.serverDate);
            headers.put("If-Modified-Since", DateUtils.formatDate(refTime));
        }
    }

    /** Reads the contents of HttpEntity into a byte[]. */
    private byte[] entityToBytes(HttpEntity entity) throws IOException, ServerError {
        PoolingByteArrayOutputStream bytes =
                new PoolingByteArrayOutputStream(mPool, (int) entity.getContentLength());
        byte[] buffer = null;
        try {
            InputStream in = entity.getContent();
            if (in == null) {
                throw new ServerError();
            }
            buffer = mPool.getBuf(1024);
            int count;
            while ((count = in.read(buffer)) != -1) {
                bytes.write(buffer, 0, count);
            }
            return bytes.toByteArray();
        } finally {
            try {
                // Close the InputStream and release the resources by "consuming the content".
                entity.consumeContent();
            } catch (IOException e) {
                // This can happen if there was an exception above that left the entity in
                // an invalid state.
                VolleyLog.v("Error occured when calling consumingContent");
            }
            mPool.returnBuf(buffer);
            bytes.close();
        }
    }

    /**
     * Converts Headers[] to Map<String, String>.
     */
    private static Map<String, String> convertHeaders(Header[] headers) {
        Map<String, String> result = new HashMap<String, String>();
        for (int i = 0; i < headers.length; i++) {
            result.put(headers[i].getName(), headers[i].getValue());
        }
        return result;
    }

    ……
}

3.5 ResponseDelivery

再回顧一下NetworkDispatcher的工作流程,可以發現,在處理完請求Request之后可以獲取到Response響應類,在整個流程的最后,需要將Response的數據送回到主線程中回調Response.Listener的onResponse處理。這節介紹的便是volley當中如何將Response送回到主線程當中并回調Request中的deliverResponse方法。

ResponseDelivery顧名思義就是一個Response對象的傳送者Delivery,傳送的目的就是主線程,來看看ResponseDelivery的源碼,由于它只是個接口,因此從接口方法中便能清晰地看到它的作用是什么。

public interface ResponseDelivery {
    /**
     * Parses a response from the network or cache and delivers it.
     */
    public void postResponse(Request<?> request, Response<?> response);

    /**
     * Parses a response from the network or cache and delivers it. The provided
     * Runnable will be executed after delivery.
     */
    public void postResponse(Request<?> request, Response<?> response, Runnable runnable);

    /**
     * Posts an error for the given request.
     */
    public void postError(Request<?> request, VolleyError error);
}

從源碼中可以看出,ResponseDelivery主要的方法就是postResponse,這里用到了post這個詞可以想到該方法的作用就是將數據傳回主線程,因為常用于線程間通信的Handler中的post就是將任務放到主線程中執行。

ResponseDelivery是個接口,那么當然肯定有它的實現類,那就是ExecutorDelivery。在查看它的源碼了解它是如何工作之前,我們先來想一下,如果讓我們寫線程間數據的傳送,我們會怎么實現?

答案顯而易見,當然是用Handler啦,Android中也就只有它來實現線程間通信。那么可以想象得到ExecutorDelivery底層就是用Handler實現的,現在來看一下ExecutorDelivery的實現。

/**
 * Delivers responses and errors.
 */
public class ExecutorDelivery implements ResponseDelivery {
    /** Used for posting responses, typically to the main thread. */
    private final Executor mResponsePoster;

    /**
     * Creates a new response delivery interface.
     * @param handler {@link Handler} to post responses on
     */
    public ExecutorDelivery(final Handler handler) {
        // Make an Executor that just wraps the handler.
        mResponsePoster = new Executor() {
            @Override
            public void execute(Runnable command) {
                handler.post(command);
            }
        };
    }

    /**
     * Creates a new response delivery interface, mockable version
     * for testing.
     * @param executor For running delivery tasks
     */
    public ExecutorDelivery(Executor executor) {
        mResponsePoster = executor;
    }

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

    @Override
    public void postError(Request<?> request, VolleyError error) {
        request.addMarker("post-error");
        Response<?> response = Response.error(error);
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null));
    }

    /**
     * A Runnable used for delivering network responses to a listener on the
     * main thread.
     */
    @SuppressWarnings("rawtypes")
    private class ResponseDeliveryRunnable implements Runnable {
        private final Request mRequest;
        private final Response mResponse;
        private final Runnable mRunnable;

        public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
            mRequest = request;
            mResponse = response;
            mRunnable = 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();
            }
       }
    }
}

從源碼中可以看出postResponse的實現其實很簡單,只需要一個Executor和一個Runnable便可以實現了,所以概括起來就是需要兩步,一個是構建Runnable任務對象,一個是構建執行者Executor。

  1. 構建繼承Runnable的ResponseDeliveryRunnable對象,在run方法中調用mRequest.deliverResponse(mResponse.result);這里其實就是回調的過程,然后這個過程就是發生在主線程當中的;
  2. 構建Executor對象,從源碼中可以看出,Executor是在ExecutorDelivery的構造方法中創建的,注意此時傳入了Handler對象,在execute方法中利用Handler的post方法將Runnable對象傳遞到Handler所在的線程中執行

經過上面兩步,mRequest.deliverResponse(mResponse.result)便執行在了Handler所在的線程當中,因此只要我們在構建ExecutorDelivery時傳入的是主線程的Handler便可以了。這里提前來看看volley中是怎么傳入主線程的Handler的,在volley中構建ExecutorDelivery是在RequestQueue構造方法中構建的,因此只需要看看RequestQueue的構造方法便可以了。

public RequestQueue(Cache cache, Network network, int threadPoolSize) {
    this(cache, network, threadPoolSize,
            new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}

從源碼中可以看出,這里是通過傳入Looper.getMainLooper來獲取到主線程的Handler對象的。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,489評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,290評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,510評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,866評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,036評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,585評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,331評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,536評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,754評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,273評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,505評論 2 379

推薦閱讀更多精彩內容