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阅读 220,137评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,824评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,465评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,131评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,140评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,895评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,535评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,435评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,952评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,081评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,210评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,896评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,552评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,089评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,198评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,531评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,209评论 2 357

推荐阅读更多精彩内容