Giter Site home page Giter Site logo

volley's Introduction

Volley源码解析

为了学习Volley的网络框架,我在AS中将Volley代码重新撸了一遍,感觉这种照抄代码也是一种挺好的学习方式.再分析Volley源码之前,我们先考虑一下,如果我们自己要设计一个网络请求框架,需要实现哪些事情,有哪些注意事项?

我的总结如下:

  1. 需要抽象出request请求类(包括url, params, method等),抽象出request请求类之后,我们可以对其继承从而实现丰富的扩展功能.

  2. 需要抽象出response类.即服务器返回的结果需要抽象出来,方便我们继承扩展.

  3. 需要实现并发和异步操作.具体包括:

    3-1. 抽象出Http请求类,封装基本操作.

    3-2. 将Http请求类在子线程中执行,最好能支撑并发.

    3-3. 由于需要并发,所以要用队列控制,并且能随时终止并发.

    3-4. 子线程获取结果后,需要支持异步,将请求结果返回给主线程.

  4. 最好能实现缓存.当request抽象出来后,那相同的request请求可以直接从本地获取,不需要再通过网络获取.

  5. 缓存需要有缓存替换机制,超时更新机制等.

在我总结的这些问题的基础上,我们来学习一下Volley是如何解决并实现这些问题的.


网络请求抽象类

Request类就是Volley抽象出来的网络请求类了.我已经对其进行了中文注解,大家可以直接看一下其实现代码:

/**
 * Volley的网络请求基类
 */
@SuppressWarnings("unused")
public abstract class Request<T> implements Comparable<Request<T>> {
    /** 默认参数编码是UTF-8. */
    private static final String DEFAULT_PARAMS_ENCODING = "UTF-8";

    /** Volley支持的Http请求类型,我们一般常用的就是GET和POST. */
    public interface Method {
        int DEPRECATED_GET_OR_POST = -1;
        int GET = 0;
        int POST = 1;
        int PUT = 2;
        int DELETE = 3;
        int HEAD = 4;
        int OPTIONS = 5;
        int TRACE = 6;
        int PATCH = 7;
    }

    /** 当前Request的HTTP请求类型. */
    private final int mMethod;

    /** 请求的url. */
    private final String mUrl;

    /** 默认的TrafficStats的tag. */
    private final int mDefaultTrafficStatsTag;

    /** request请求失败时的回调接口. */
    private final Response.ErrorListener mErrorListener;

    /** request的请求序列号,用于请求队列FIFO时排序查找使用. */
    private Integer mSequence;

    /** request的投放队列,该队列可采用FIFO方式执行request请求. */
    private RequestQueue mRequestQueue;

    /** 该request请求是否需要缓存,默认http request请求都是可以缓存的. */
    private boolean mShouldCache = true;

    /** 该request请求是否被取消的标志. */
    private boolean mCanceled = false;

    /** 该request是否已经获取请求结果. */
    private boolean mResponseDelivered = false;

    /** 遇到服务器错误(5xx)时,该request请求是否需要重试. */
    private boolean mShouldRetryServerErrors = false;

    /** request重试策略. */
    private RetryPolicy mRetryPolicy;

    /**
     * 保存request缓存的结果.
     * 因为当一个request可以被缓存,但是又必须要刷新(即需要从网络重新获取时),我们保存该缓存结果,可以确保该结果
     * 不被cache的替换策略清除掉,以防服务器返回“Not Modified”时,我们可以继续使用该缓存结果.
     */
    private Cache.Entry mCacheEntry = null;

    /**
     * 创建一个Http request对象.
     *
     * @param method HTTP请求方式(GET, POST, PUT, DELETE...).
     * @param url HTTP请求的url.
     * @param listener 当HTTP访问出错时,用户设置的回调的接口.
     */
    public Request(int method, String url, Response.ErrorListener listener) {
        mMethod = method;
        mUrl = url;
        mErrorListener = listener;
        mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
    }

    /** 返回HTTP请求方式. */
    public int getMethod() {
        return mMethod;
    }

    /** 返回HTTP请求错误时的回调接口. */
    public Response.ErrorListener getErrorListener() {
        return mErrorListener;
    }

    /** 返回统计类使用的Tag. */
    public int getTrafficStatsTag() {
        return mDefaultTrafficStatsTag;
    }

    /**
     * 使用url的host字段的hash值作为统计类的tag.
     */
    private static int findDefaultTrafficStatsTag(String url) {
        if (!TextUtils.isEmpty(url)) {
            Uri uri = Uri.parse(url);
            if (uri != null) {
                String host = uri.getHost();
                if (host != null) {
                    return host.hashCode();
                }
            }
        }
        return 0;
    }

    /** 设置重试接口.典型的组合模式,关联关系. */
    public Request<?> setRetryPolicy(RetryPolicy retryPolicy) {
        mRetryPolicy = retryPolicy;
        return this;
    }

    /** 调试打印当前请求进度使用 */
    public void addMarker(String tag) {
        Log.e("Volley", tag);
    }

    /** 用于告知请求队列当前request已经结束. */
    void finish(final String tag) {
        if (mRequestQueue != null) {
            mRequestQueue.finish(this);
        }
    }

    /** 设置当前request的请求队列. */
    public Request<?> setRequestQueue(RequestQueue requestQueue) {
        mRequestQueue = requestQueue;
        return this;
    }

    /** 设置当前request在当前request队列的系列号. */
    public final Request<?> setSequence(int sequence) {
        mSequence = sequence;
        return this;
    }

    /** 返回request请求的序列号. */
    public final int getSequence() {
        if (mSequence == null) {
            throw new IllegalStateException("getSequence called before setSequence");
        }
        return mSequence;
    }

    /** 返回request的url. */
    public String getUrl() {
        return mUrl;
    }

    /** 使用request的url作为volley cache缓存系统存储的key值(默认url可唯一标识一个request). */
    public String getCacheKey() {
        return getUrl();
    }

    /** 设置request对应的volley cache缓存系统中的请求结果. */
    public Request<?> setCacheEntry(Cache.Entry entry) {
        mCacheEntry = entry;
        return this;
    }

    /** 返回request的cache系统的请求结果. */
    public Cache.Entry getCacheEntry() {
        return mCacheEntry;
    }

    /** 标识该request已经被取消. */
    public void cancel() {
        mCanceled = true;
    }

    /** 返回该request是否被取消标识. */
    public boolean isCanceled() {
        return mCanceled;
    }

    /** 返回该request的headers. */
    public Map<String, String> getHeaders() throws AuthFailureError {
        return Collections.emptyMap();
    }

    /** 返回该request的请求体中参数,如果是GET请求,则直接返回null. */
    protected Map<String, String> getParams() throws AuthFailureError {
        return null;
    }

    /** 返回该request请求参数编码. */
    protected String getParamsEncoding() {
        return DEFAULT_PARAMS_ENCODING;
    }

    /** 获取request body content type. */
    public String getBodyContentType() {
        return "application/x-www-form-urlencoded; charset="
                + getParamsEncoding();
    }

    /** 返回request请求参数体. */
    public byte[] getBody() throws AuthFailureError {
        Map<String, String> params = getParams();
        if (params != null && params.size() > 0) {
            return encodeParameters(params, getParamsEncoding());
        }
        return null;
    }

    /** 构造post请求参数体. */
    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);
        }
    }

    /** 设置当前request是否需要被缓存. */
    public final Request<?> setShouldCache(boolean shouldCache) {
        mShouldCache = shouldCache;
        return this;
    }

    /** 返回当前request是否需要被缓存. */
    public final boolean shouldCache() {
        return mShouldCache;
    }

    /** 设置request的重试接口. */
    public final Request<?> setShouldRetryServerErrors(boolean shouldRetryServerErrors) {
        mShouldRetryServerErrors = shouldRetryServerErrors;
        return this;
    }

    /** 返回该request当遇到服务器错误时是否需要重试标志 */
    public final boolean shouldRetryServerErrors() {
        return mShouldRetryServerErrors;
    }

    /** request优先级枚举类. */
    public enum  Priority {
        LOW,
        NORMAL,
        HIGH,
        IMMEDIATE
    }

    /** 返回当前request的优先级.子类可以重写该方法修改request的优先级. */
    public Priority getPriority() {
        return Priority.NORMAL;
    }

    /** 返回重试的时间,用于日志记录. */
    public final int getTimeoutMs() {
        return mRetryPolicy.getCurrentTimeout();
    }

    /** 返回重试接口. */
    public RetryPolicy getRetryPolicy() {
        return mRetryPolicy;
    }

    /** 用于标识已经将response传给该request. */
    public void markDelivered() {
        mResponseDelivered = true;
    }

    /** 返回该request是否有response delivered. */
    public boolean hasHadResponseDelivered() {
        return mResponseDelivered;
    }

    /** 子类必须重写该方法,用来解析http请求的结果. */
    abstract protected Response<T> parseNetworkResponse(NetworkResponse response);

    /** 子类可以重写该方法,从而获取更精准的出错信息. */
    protected VolleyError parseNetworkError(VolleyError volleyError) {
        return volleyError;
    }

    /** 子类必须重写该方法用于将网络结果返回给用户设置的回调接口. */
    abstract protected void deliverResponse(T response);

    /** 将网络错误传递给回调接口. */
    public void deliverError(VolleyError error) {
        if (mErrorListener != null) {
            mErrorListener.onErrorResponse(error);
        }
    }

    /** 先判断执行顺序,再判断request优先级. */
    @Override
    public int compareTo(@NonNull Request<T> another) {
        Priority left = this.getPriority();
        Priority right = another.getPriority();

        return left == right ? this.mSequence - another.mSequence :
                right.ordinal() - left.ordinal();
    }

    @Override
    public String toString() {
        String trafficStatsTag = "0x" + Integer.toHexString(getTrafficStatsTag());
        return (mCanceled ? "[X]" : "[ ]") + getUrl() + " " + trafficStatsTag + " " +
                getPriority() + " " + mSequence;
    }
}

代码虽然很长,但是都是对request很好的抽象,建议大家结合HTTP协议阅读一下该源码. Request中的泛型T用来对结果进行泛型表示,当定义出request基类之后,我们可以很轻松的对其进行继承,从而扩展出我们想要的request请求.

例如Volley提供的StringRequest,源码如下:

/** 一个返回结果的String的request实现类 */
@SuppressWarnings("unused")
public class StringRequest extends Request<String>{
    private final Response.Listener<String> mListener;

    /** 根据给定的METHOD设置对应的request. */
    public StringRequest(int method, String url, Response.Listener<String> listener,
                         Response.ErrorListener errorListener) {
        super(method, url, errorListener);
        mListener = listener;
    }

    /** 默认为GET请求的request. */
    public StringRequest(String url, Response.Listener<String> listener,
                         Response.ErrorListener errorListener) {
        this(Method.GET, url, listener, errorListener);
    }

    /** 将HTTP请求结果转换为String. */
    @Override
    protected Response<String> parseNetworkResponse(NetworkResponse response) {
        String parsed;

        try {
            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
        } catch (UnsupportedEncodingException e) {
            parsed = new String(response.data);
        }
        return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
    }

    /** 将解析的String结果传递给用户的回调接口. */
    @Override
    protected void deliverResponse(String response) {
        mListener.onResponse(response);
    }
}

有了这个StringRequest类示例,我们也可以参考其实现很方便的对Request类进行扩展.再对request进行扩展时,我们通常只需要实现两个方法即可:

  1. deliverResponse:这个方法很简单,就是将网络解析的结果传递给用户设置的回调接口.
  2. parseNetworkResponse : 这个方法比较关键,我们主要也是来重写该方法.如果我需要返回JsonObject,那么我就需要将参数NetworkResponse在该方法中转换成JsonObject.
  3. getParams : 这个方法是如果有POST参数时,需要重写该方法.

介绍完Request抽象,那我们继续来看一下Response抽象.


网络请求结果抽象类

Response.java

Response是Volley抽象出来对网络请求结果进行封装的类.具体注释源码如下:

/** 网络请求结果的封装类.其中泛型T为网络解析结果. */
public class Response<T> {
    /** request请求成功回调接口, 用于用户自行处理网络请求返回的结果. */
    public interface Listener<T> {
        void onResponse(T response);
    }

    /** request请求失败回调接口,用于用户自行处理网络请求失败的情况. */
    public interface ErrorListener {
        void onErrorResponse(VolleyError error);
    }

    /** 构造一个request请求成功的response对象. */
    public static <T> Response<T> success(T result, Cache.Entry cacheEntry) {
        return new Response<T>(result, cacheEntry);
    }

    /** 构造一个request请求失败的response对象. */
    public static <T> Response<T> error(VolleyError error) {
        return new Response<T>(error);
    }

    /** request的网络请求解析结果. */
    public final T result;

    /** request的缓存内容. */
    public final Cache.Entry cacheEntry;

    /** 请求错误内容. */
    public final VolleyError error;

    /** 当前结果是否为中间请求结果. */
    public boolean intermediate = false;

    /** 返回当前request请求结果是否成功. */
    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只是对request请求结果的进一步封装.真正的HTTP Request请求结果的抽象其实是NetworkResponse类.

NetworkResponse.java

NetworkResponse类是真正的HTTP网络请求结果类,其注释源码如下:

/** HTTP网络请求结果抽象类. */
public class NetworkResponse {
    /** HTTP响应状态码. */
    public final int statusCode;

    /** HTTP响应信息. */
    public final byte[] data;

    /** 服务器状态码304代表未修改 */
    public final boolean notModified;

    /** HTTP请求的往返延迟. */
    public final long networkTimeMs;

    /** HTTP响应头信息. */
    public final Map<String, String> headers;
    
    public NetworkResponse(int statusCode, byte[] data, Map<String, String> headers,
                           boolean notModified, long networkTimeMs) {
        this.statusCode = statusCode;
        this.data = data;
        this.headers = headers;
        this.notModified = notModified;
        this.networkTimeMs = networkTimeMs;
    }

    public NetworkResponse(int statusCode, byte[] data, Map<String, String> headers,
                           boolean notModified) {
        this(statusCode, data, headers, notModified, 0);
    }

    public NetworkResponse(byte[] data) {
        this(HttpURLConnection.HTTP_OK, data, Collections.<String, String>emptyMap(), false, 0);
    }

    public NetworkResponse(byte[] data, Map<String, String> headers) {
        this(HttpURLConnection.HTTP_OK, data, headers, false, 0);
    }
}

网络请求的并发和异步

在讲解网络请求的并发和异步之前,我们先来看一下,Volley是如何封装网络请求的.

HurlStack.java

这个类封装了HttpURLConnection类的构造操作,我自己实现网络请求时,也会封装这些重复的HttpURLConnection构造代码.注释代码如下:

/** 封装HttpURLConnection类,简化网络请求代码. */
public class HurlStack implements HttpStack {
    private static final String HEADER_CONTENT_TYPE = "Content-Type";

    private final SSLSocketFactory mSslSocketFactory;

    /** 默认创建一个HTTP请求类. */
    public HurlStack() {
        this(null);
    }

    /** 创建一个HTTPS请求类. */
    public HurlStack(SSLSocketFactory sslSocketFactory) {
        mSslSocketFactory = sslSocketFactory;
    }

    /** HTTP or HTTPS请求真正执行的地方 */
    @Override
    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
            throws IOException, AuthFailureError {
        HashMap<String, String> map = new HashMap<String, String>();
        map.putAll(request.getHeaders());
        map.putAll(additionalHeaders);

        // 构造HttpURLConnection,封装一些固定参数.
        String url = request.getUrl();
        URL parsedUrl = new URL(url);
        HttpURLConnection connection = openConnection(parsedUrl, request);
        // 构造http请求的header.
        for (String headerName: map.keySet()) {
            connection.addRequestProperty(headerName, map.get(headerName));
        }
        // 构造http请求的body.
        setConnectionParametersForRequest(connection, request);

        // Initialize HttpResponse with data from the HttpURLConnection
        ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
        int responseCode = connection.getResponseCode();
        if (responseCode == -1) {
            throw new IOException("Could not retrieve response code from HttpUrlConnection.");
        }

        // 使用apache提供的BasicHttpResponse来封装请求.
        StatusLine responseStatus = new BasicStatusLine(protocolVersion,
                connection.getResponseCode(), connection.getResponseMessage());
        BasicHttpResponse response = new BasicHttpResponse(responseStatus);
        if (hasResponseBody(request.getMethod(), responseStatus.getStatusCode())) {
            response.setEntity(entityFromConnection(connection));
        }
        for (Map.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;
    }

    /** 封装HttpURLConnection类的构造函数. */
    private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException {
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setInstanceFollowRedirects(HttpURLConnection.getFollowRedirects());

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

        if ("https".equals(url.getProtocol()) && mSslSocketFactory != null) {
            ((HttpsURLConnection)connection).setSSLSocketFactory(mSslSocketFactory);
        }

        return connection;
    }

    /* package */ static void setConnectionParametersForRequest(HttpURLConnection connection,
                                                                Request<?> request)
            throws IOException, AuthFailureError {
        switch (request.getMethod()) {
            case Request.Method.GET:
                connection.setRequestMethod("GET");
                break;
            case Request.Method.POST:
                connection.setRequestMethod("POST");
                addBodyIfExists(connection, request);
                break;
        }
    }

    /** 添加POST请求参数到HttpURLConnection中. */
    private static void addBodyIfExists(HttpURLConnection connection, Request<?> request)
            throws AuthFailureError, IOException {
        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.flush();
        }
    }

    /** 判断当前request请求结果是否有响应体. */
    private boolean hasResponseBody(int requestMethod, int responseCode) {
        return requestMethod != Request.Method.HEAD
                && !(HttpStatus.SC_CONTINUE <= responseCode && responseCode <= HttpStatus.SC_OK)
                && responseCode != HttpStatus.SC_NO_CONTENT
                && responseCode != HttpStatus.SC_NOT_MODIFIED;
    }

    /** 保存Http Body. */
    private 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;
    }
}

当用户new出HurlStack对象,调用它的performRequest方法,即可以发出HTTP请求,并获取HTTP请求结果. 但是,Android主线程中是不允许进行耗时操作的,所以Volley实现了并发访问HurlStack的performRequest的方法. 至于HurlStack的并发访问,就需要看NetworkDispatcher的实现.

NetworkDispatcher.java

NetworkDispatcher是一个线程,用来调度处理网络请求.启动后会不断从网络请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给ResponseDelivery去执行后续处理,并判断结果是否要进行缓存. NetworkDispatcher的执行流程图如下: NetworkDispatcher

NetworkDispatcher中文注释代码如下:

/** 调度网络请求线程. */
public class NetworkDispatcher extends Thread{
    /** 网络请求队列. */
    private final BlockingQueue<Request<?>> mQueue;

    /** 封装了HurlStack的网络类,其performRequest方法是单个request请求真正执行的地方. */
    private final Network mNetwork;

    /** 缓存类,存储请求结果的缓存. */
    private final Cache mCache;

    /** 请求结果传递类. */
    private final ResponseDelivery mDelivery;

    /** 暂停线程的标志位,替换Thread自身的stop方法. */
    private volatile boolean mQuit = false;

    /** 构造网络请求调度线程类. */
    public NetworkDispatcher(BlockingQueue<Request<?>> queue,
                             Network network, Cache cache, ResponseDelivery delivery) {
        mQueue = queue;
        mNetwork = network;
        mCache = cache;
        mDelivery = delivery;
    }

    /** 强制停止当前调度线程. */
    public void quit() {
        mQuit = true;
        interrupt();
    }

    @Override
    public void run() {
        android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        while (true) {
            long startTimeMs = SystemClock.elapsedRealtime();
            Request<?> request;
            try {
                // 使用BlockingQueue实现了生产者-消费者模型.
                // 消费者是该调度线程.
                // 生产者是request网络请求.
                request = mQueue.take();
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }

            try {
                if (request.isCanceled()) {
                    continue;
                }

                addTrafficStatsTag(request);

                // 真正执行网络请求的地方.
                NetworkResponse networkResponse = mNetwork.performRequest(request);

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

                // 在当前线程中解析网络结果.
                // 不同的Request实现的parseNetworkResponse是不同的(例如StringRequest和JsonRequest).
                Response<?> response = request.parseNetworkResponse(networkResponse);

                //
                if (request.shouldCache() && response.cacheEntry != null) {
                    mCache.put(request.getCacheKey(), response.cacheEntry);
                }

                // 将网络请求结果进行传递.
                // ResponseDelivery调用顺序如下:
                // ResponseDelivery.postResponse==>ResponseDeliveryRunnable[Runnable]->run
                // ==>Request->deliverResponse==>用户设置的Listener回调接口
                request.markDelivered();
                mDelivery.postResponse(request, response);
            } catch (VolleyError volleyError) {
                volleyError.printStackTrace();
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                parseAndDeliverNetworkError(request, volleyError);
            } catch (Exception e) {
                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);
    }

    private void addTrafficStatsTag(Request<?> request) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            TrafficStats.setThreadStatsTag(request.getTrafficStatsTag());
        }
    }
}

这里还有一点需要说明,NetworkDispatcher真正执行Http request请求时,并不是直接使用HurlStack类的performRequest方法,而是又对其进行了一个封装,封装成了Network类.

Network.java

Network.java的源码如下:

/** 网络接口,处理网络请求 */
public interface Network {
    NetworkResponse performRequest(Request<?> request) throws VolleyError;
}

可以看到,Network有一个子类需要实现的方法,和HurlStack的具体执行HTTP请求的方法的名称是一样的.那为什么Volley要多此一举对HurlStack进行进一步封装呢?

  1. 这是因为Volley向下兼容到Android2.3之下的版本,而Android2.3以下的版本构造Http请求时推荐使用的是HttpClient类,所以这里Volley做了一个适配器模式的封装.也就是说,HurlStack类只需要负责对HttpURLConnection进行封装,HttpClientStack只需要对HttpClient类进行封装.
  2. 封装更多的处理操作.包括:缓存新鲜度验证、超时重试等.

至于Network接口的具体实现类是BasicNetwork类,其注释源码如下:

/** Volley默认的网络接口实现类. */
public class BasicNetwork implements Network {
    /** 网络请求真正实现类. */
    private final HttpStack mHttpStack;

    public BasicNetwork(HttpStack httpStack) {
        mHttpStack = httpStack;
    }

    @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 {
                // 构造Cache的HTTP headers,主要是添加If-None-Match和If-Modified-Since两个字段
                // 当客户端发送的是一个条件验证请求时,服务器可能返回304状态码.
                // If-Modified-Since:代表服务器上次修改是的日期值.
                // If-None-Match:服务器上次返回的ETag响应头的值.
                Map<String, String> headers = new HashMap<String, String>();
                addCacheHeaders(headers, request.getCacheEntry());

                // 调用HurlStack的performRequest方法执行网络请求, 并将请求结果存入httpResponse变量中
                httpResponse = mHttpStack.performRequest(request, headers);

                StatusLine statusLine = httpResponse.getStatusLine();
                int statusCode = statusLine.getStatusCode();
                responseHeaders = convertHeaders(httpResponse.getAllHeaders());

                // 当服务端返回304状态码时,直接将Volley缓存中结果返回
                if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
                    Cache.Entry entry = request.getCacheEntry();
                    if (entry == null) {
                        return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,
                                responseHeaders, true,
                                SystemClock.elapsedRealtime() - requestStart);
                    }

                    // A HTTP 304 response dose not have all header filed. We
                    // have to use the header fields from the cache entry plus
                    // the new ones from the response.
                    entry.responseHeaders.putAll(responseHeaders);
                    return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
                            entry.responseHeaders, true,
                            SystemClock.elapsedRealtime() - requestStart);
                }

                // Some responses such as 204s do not have content. We mush check
                if (httpResponse.getEntity() != null) {
                    responseContents = entityToBytes(httpResponse.getEntity());
                } else {
                    responseContents = new byte[0];
                }

                if (statusCode < 200 || statusCode > 299) {
                    throw new IOException();
                }

                return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
                        SystemClock.elapsedRealtime() - requestStart);
            } 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;
                if (httpResponse != null) {
                    statusCode = httpResponse.getStatusLine().getStatusCode();
                } else {
                    throw new NoConnctionError(e);
                }
                NetworkResponse networkResponse;
                if (responseContents != null) {
                    networkResponse = new NetworkResponse(statusCode, responseContents,
                            responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);
                    if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
                            statusCode == HttpStatus.SC_FORBIDDEN) {
                        attemptRetryOnException("auth",
                                request, new AuthFailureError(networkResponse));
                    } else if (statusCode >= 400 && statusCode <= 499) {
                        throw new ClientError(networkResponse);
                    } else if (statusCode >= 500 && statusCode <= 599) {
                        if (request.shouldRetryServerErrors()) {
                            attemptRetryOnException("server",
                                    request, new ServerError(networkResponse));
                        } else {
                            throw new ServerError(networkResponse);
                        }
                    } else {
                        // 3xx?
                        throw new ServerError(networkResponse);
                    }
                } else {
                    attemptRetryOnException("network", request, new NetworkError());
                }
            }
        }
    }

    private void addCacheHeaders(Map<String, String> headers, Cache.Entry entry) {
        if (entry == null) {
            return;
        }

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

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

    private static Map<String, String> convertHeaders(Header[] headers) {
        Map<String, String> result = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
        for (Header header : headers) {
            result.put(header.getName(), header.getValue());
        }
        return result;
    }

    /**
     * 将服务器返回的InputStream输入流转换成byte数组.
     * 这个函数让我实现的话,我会使用StringBuffer来替换ByteArrayOutputStream来实现字符串拼接.
     */
    private byte[] entityToBytes(HttpEntity entity) throws IOException, ServerError {
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();

        byte[] buffer = new byte[1024];

        try {
            InputStream in = entity.getContent();
            if (in == null) {
                throw new ServerError();
            }
            int count;
            while ((count = in.read(buffer)) != -1) {
                bytes.write(buffer, 0, count);
            }
            return bytes.toByteArray();
        } finally {
            try {
                entity.consumeContent();
            } catch (IOException e){
                e.printStackTrace();
            }
            bytes.close();
        }
    }

    private void attemptRetryOnException(String logPrefix, Request<?> request,
                                         VolleyError exception) throws VolleyError{
        RetryPolicy retryPolicy = request.getRetryPolicy();
        int oldTimeout = request.getTimeoutMs();

        retryPolicy.retry(exception);
        Log.e("Volley", String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));
    }
}

RequestQueue.java

RequestQueue是Volley框架的核心类,用户在使用Volley时,就是将一个Request加入到RequestQueue来完成请求操作的.所以,RequestQueue既是request的存储仓库,也是NetworkDispatcher的调度核心. 由于RequestQueue其中还包括Volley的缓存机制,我们稍后会对缓存机制进行讲解,所以这里只看跟NetworkDispatcher调度相关的源码.

RequestQueue类的注释代码如下:

/** Request请求调度队列. */
@SuppressWarnings("unused")
public class RequestQueue {
    /**
     * Callback interface for completed requests.
     */
    public interface RequestFinishedListener<T> {
        void onRequestFinished(Request<T> request);
    }

    /** 为每一个request申请独立的序列号. */
    private AtomicInteger mSequenceGenerator = new AtomicInteger();

    /**
     * Staging area for requests that already have a duplicate request in flight.
     */
    private final Map<String, Queue<Request<?>>> mWaitingRequests =
            new HashMap<String, Queue<Request<?>>>();

    /** 保存所有被加入到当前队列的request集合. */
    private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();

    /**
     * The cache triage queue.
     */
    private final PriorityBlockingQueue<Request<?>> mCacheQueue =
            new PriorityBlockingQueue<Request<?>>();

    /** 存储需要进行网络通信的request的存储队列. */
    private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
            new PriorityBlockingQueue<Request<?>>();

    /** RequestQueue默认开启的网络线程的数量. */
    private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;

    /**
     * Cache interface for retrieving and storing responses.
     */
    private final Cache mCache;

    /** 封装request网络请求的Network类. */
    private final Network mNetwork;

    /** 网络请求传输结果实现类. */
    private final ResponseDelivery mDelivery;

    /** 网络请求线程数组. */
    private NetworkDispatcher[] mDispatchers;

    /** 缓存线程 */
    private CacheDispatcher mCacheDispatcher;

    private List<RequestFinishedListener> mFinishedListeners =
            new ArrayList<RequestFinishedListener>();

    public RequestQueue(Cache cache, Network network) {
        this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
    }

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

    /**
     * Creates the worker pool.
     * @param cache A cache to use for persisting responses to disk
     * @param network A Network interface for performing HTTP requests
     * @param threadPoolSize Number of network dispatcher threads to create
     * @param delivery A ResponseDelivery interface for posting responses and errors
     */
    public RequestQueue(Cache cache, Network network, int threadPoolSize,
                        ResponseDelivery delivery) {
        mCache = cache;
        mNetwork = network;
        mDispatchers = new NetworkDispatcher[threadPoolSize];
        mDelivery = delivery;
    }

    /** 开启request的缓存线程和多个网络请求线程 */
    public void start() {
        // 关闭所有正在运行的缓存线程和网络请求线程.
        stop();

        // 默认开启DEFAULT_NETWORK_THREAD_POOL_SIZE(4)个线程来执行request网络请求.
        for (int i = 0; i < mDispatchers.length; i ++) {
            // 将NetworkDispatcher线程与mNetworkQueue这个队列进行绑定.
            // NetworkDispatcher会使用生产者-消费者模型从mNetworkQueue获取request请求,并执行.
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                    mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

    /** 停止所有的缓存线程和网络请求线程. */
    private void stop() {
        for (NetworkDispatcher dispatcher : mDispatchers) {
            if (dispatcher != null) {
                dispatcher.quit();
            }
        }
    }

    /** 将Request请求加入到调度队列中. */
    public <T> Request<?> 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);
        }

        // 分配request唯一的序列号.
        request.setSequence(getSequenceNumber());

        // request不允许缓存,则直接将request加入到mNetworkQueue当中
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
        }
    }

    /** 提供request请求序列号. */
    private int getSequenceNumber() {
        return mSequenceGenerator.incrementAndGet();
    }
}

RequestQueue在构造函数中,会默认生成4个NetworkDispatcher线程,并且将NetworkDispatcher线程与mNetworkQueue进行绑定,然后start NetworkDispatcher执行网络请求操作.

异步

前面已经详细讲解了一个Request是如何被并发处理的,那现在回到我们的3-4问题,子线程中并发处理的结果如何异步传递给用户设置的Listener回调接口. 从NetworkDispatcher最后传递结果的代码:

request.markDelivered();
mDelivery.postResponse(request, response);

我们就可以看出,异步回调是通过ResponseDelivery类实现的.

ResponseDelivery.java

ResponseDelivery的中文注释源码如下:

/** 网络结果分发接口类. */
public interface ResponseDelivery {
    /**
     * Parses a response from the network or cache and delivers it.
     */
    void postResponse(Request<?> request, Response<?> response);

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

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

在RequestQueue中,ResponseDelivery的实现类为ExecutorDelivery类.

ExecutorDelivery

众所周知,Android中实现异步肯定是需要用到Handler、Looper和Message机制的.ExecutorDelivery的实现异步的机制也是居于Handler机制. 我们先来看一下,RequestQueue中ExecutorDelivery是如何被构造的:

ResponseDelivery delivery = new ExecutorDelivery(new Handler(Looper.getMainLooper()));

可以看到,RequestQueue将绑定主线程Looper对象的Handler对象传递给了ExecutorDelivery,这样我们通过handler发送的消息其实都是在主线程进行处理了. ExecutorDelivery的中文注释源码如下:

/**
 * 网络请求结果传递类.(实现异步功能,主线程传递数据给子线程)
 */
@SuppressWarnings("unused")
public class ExecutorDelivery implements ResponseDelivery {
    /**
     * 构造执行已提交的Runnable任务对象.
     */
    private final Executor mResponsePoster;

    public ExecutorDelivery(final Handler handler) {
        mResponsePoster = new Executor() {
            @Override
            public void execute(@NonNull Runnable command) {
                // 所有的Runnable通过绑定主线程Looper的Handler对象最终在主线程执行.
                handler.post(command);
            }
        };
    }

    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();
        mResponsePoster.execute(
                new ResponseDeliveryRunnable(request, response, runnable)
        );
    }

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

    /** 在主线程执行的Runnable类 */
    @SuppressWarnings("unchecked")
    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;
        }

        @Override
        public void run() {
            // 如果request被取消,则不回调用户设置的Listener接口
            if (mRequest.isCanceled()) {
                mRequest.finish("canceled-at-delivery");
                return;
            }

            // 通过response状态标志,来判断是回调用户设置的Listener接口还是ErrorListener接口
            if (mResponse.isSuccess()) {
                mRequest.deliverResponse(mResponse.result);
            } else {
                mRequest.deliverError(mResponse.error);
            }

            if (mResponse.intermediate) {
                mRequest.addMarker("intermediate-response");
            } else {
                // 通知RequestQueue终止该Request请求
                mRequest.finish("done");
            }

            if (mRunnable != null) {
                mRunnable.run();
            }
        }
    }
}

缓存机制

前面讲解了并发和异步的实现,接下来,我们就来看一下Volley的缓存机制.再学习Volley缓存实现方案之前,我们先来感受一下Google I/O大会上Volley官方一张宣传图片: Volley

这张图片非常形象的表达了Volley适合频繁的网络请求.接下来,我们就从Volley的缓存系统入手,介绍一下为什么Volley适合频繁的网络请求.

Cache.java

既然要缓存Request请求,那我们首先就需要抽象出缓存对象.而Cache类就是对缓存对象的抽象描述:

/** 缓存内存的抽象接口 */
@SuppressWarnings("unused")
public interface Cache {
    /** 通过key获取请求的缓存实体. */
    Entry get(String key);

    /** 存入一个请求的缓存实体. */
    void put(String key, Entry entry);

    void initialize();

    void invalidate(String key, boolean fullExpire);

    /** 移除指定的缓存实体. */
    void remove(String key);

    /** 清空缓存. */
    void clear();

    /** 真正HTTP请求缓存实体类. */
    class Entry {
        /** HTTP响应体. */
        public byte[] data;

        /** HTTP响应首部中用于缓存新鲜度验证的ETag. */
        public String etag;

        /** HTTP响应时间. */
        public long serverDate;

        /** 缓存内容最后一次修改的时间. */
        public long lastModified;

        /** Request的缓存过期时间. */
        public long ttl;

        /** Request的缓存新鲜时间. */
        public long softTtl;

        /** HTTP响应Headers. */
        public Map<String, String> responseHeaders = Collections.emptyMap();

        /** 判断缓存内容是否过期. */
        public boolean isExpired() {
            return this.ttl < System.currentTimeMillis();
        }

        /** 判断缓存是否新鲜,不新鲜的缓存需要发到服务端做新鲜度的检测. */
        public boolean refreshNeeded() {
            return this.softTtl < System.currentTimeMillis();
        }
    }
}

Cache接口定义规定了缓存实体的内容和其需要实现的方法.在RequestQueue中,Cache的实现类是DiskBasedCache类.

DiskBasedCache.java

DiskBasedCache类的主要作用是:实现了基于Disk的对象存储类,并提供替换策略.代码比较简单,中文注释的代码如下:

/** 基于Disk的缓存实现类. */
@SuppressWarnings("ResultOfMethodCallIgnored")
public class DiskBasedCache implements Cache {
    /** 默认硬盘最大的缓存空间(5M). */
    private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024;

    /** 标记缓存起始的MAGIC_NUMBER. */
    private static final int CACHE_MAGIC = 0x20150306;

    /**
     * High water mark percentage for the cache.
     */
    private static final float HYSTERESIS_FACTOR = 0.9f;

    /**
     * Map of the Key, CacheHeaders pairs.
     */
    private final Map<String, CacheHeader> mEntries =
            new LinkedHashMap<String, CacheHeader>(16, 0.75f, true);

    /** 目前使用的缓存字节数. */
    private long mTotalSize = 0;

    /** 硬盘缓存目录. */
    private final File mRootDirectory;

    /** 硬盘缓存最大容量(默认5M). */
    private final int mMaxCacheSizeInBytes;

    public DiskBasedCache(File rootDirectory) {
        this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);
    }

    public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
        mRootDirectory = rootDirectory;
        mMaxCacheSizeInBytes = maxCacheSizeInBytes;
    }

    /** 清空缓存内容. */
    @Override
    public synchronized void clear() {
        File[] files = mRootDirectory.listFiles();
        if (files != null) {
            for (File file : files) {
                file.delete();
            }
        }
        mEntries.clear();
        mTotalSize = 0;
    }

    /** 从Disk中根据key获取并构造HTTP响应体Cache.Entry. */
    @Override
    public synchronized Entry get(String key) {
        CacheHeader entry = mEntries.get(key);
        if (entry == null) {
            return null;
        }

        File file = getFileForKey(key);
        CountingInputStream cis = null;
        try {
            cis = new CountingInputStream(new BufferedInputStream(new FileInputStream(file)));
            // 读完CacheHeader部分,并通过CountingInputStream的bytesRead成员记录已经读取的字节数.
            CacheHeader.readHeader(cis);
            // 读取缓存文件存储的HTTP响应体内容.
            byte[] data = streamToBytes(cis, (int)(file.length() - cis.bytesRead));
            return entry.toCacheEntry(data);
        } catch (IOException e) {
            remove(key);
            return null;
        } finally {
            if (cis != null) {
                try {
                    cis.close();
                } catch (IOException ignored) {
                }
            }
        }
    }

    /** 初始化Disk缓存系统.
     * 作用是:遍历Disk缓存系统,将缓存文件中的CacheHeader和key存储到Map对象中. */
    @Override
    public void initialize() {
        if (!mRootDirectory.exists() && !mRootDirectory.mkdirs()) {
            return;
        }

        File[] files = mRootDirectory.listFiles();
        if (files == null) {
            return;
        }

        for (File file : files) {
            BufferedInputStream fis = null;
            try {
                fis = new BufferedInputStream(new FileInputStream(file));
                CacheHeader entry = CacheHeader.readHeader(fis);
                entry.size = file.length();
                putEntry(entry.key, entry);
            }catch (IOException e) {
                file.delete();
                e.printStackTrace();
            }finally {
                if (fis != null) {
                    try {
                        fis.close();
                    } catch (IOException ignored) {
                    }
                }
            }
        }
    }

    /** 标记指定的cache过期. */
    @Override
    public synchronized void invalidate(String key, boolean fullExpire) {
        Entry entry = get(key);
        if (entry != null) {
            entry.softTtl = 0;
            if (fullExpire) {
                entry.ttl = 0;
            }
            put(key, entry);
        }
    }

    /** 将Cache.Entry存入到指定的缓存文件中. 并在Map中记录<key,CacheHeader>. */
    @Override
    public synchronized void put(String key, Entry entry) {
        pruneIfNeeded(entry.data.length);
        File file = getFileForKey(key);
        try {
            BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(file));
            CacheHeader e = new CacheHeader(key, entry);
            boolean success = e.writeHeader(fos);
            if (!success) {
                fos.close();
                throw new IOException();
            }
            fos.write(entry.data);
            fos.close();
            putEntry(key, e);
            return;
        } catch (IOException e) {
            e.printStackTrace();
        }
        file.delete();
    }

    /** Disk缓存替换更新机制. */
    private void pruneIfNeeded(int neededSpace) {
        if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) {
            return;
        }

        Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, CacheHeader> entry = iterator.next();
            CacheHeader e = entry.getValue();
            boolean deleted = getFileForKey(e.key).delete();
            if (deleted) {
                mTotalSize -= e.size;
            }
            iterator.remove();

            if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
                break;
            }
        }
    }

    /** 获取存储当前key对应value的文件句柄. */
    private File getFileForKey(String key) {
        return new File(mRootDirectory, getFilenameForKey(key));
    }

    /** 根据key的hash值生成对应的存储文件名称. */
    private String getFilenameForKey(String key) {
        int firstHalfLength = key.length() / 2;
        String localFilename = String.valueOf(key.substring(0, firstHalfLength).hashCode());
        localFilename += String.valueOf(key.substring(firstHalfLength).hashCode());
        return localFilename;
    }

    /** 将key和CacheHeader存入到Map对象中.并更新当前占用的总字节数. */
    private void putEntry(String key, CacheHeader entry) {
        if (!mEntries.containsKey(key)) {
            mTotalSize += entry.size;
        } else {
            CacheHeader oldEntry = mEntries.get(key);
            mTotalSize += (entry.size - oldEntry.size);
        }

        mEntries.put(key, entry);
    }

    @Override
    public synchronized void remove(String key) {
        boolean deleted = getFileForKey(key).delete();
        removeEntry(key);
        if (!deleted) {
            Log.e("Volley", "没能删除key=" + key + ", 文件名=" + getFilenameForKey(key) + "缓存.");
        }
    }

    /** 从Map对象中删除key对应的键值对. */
    private void removeEntry(String key) {
        CacheHeader entry = mEntries.get(key);
        if (entry != null) {
            mTotalSize -= entry.size;
            mEntries.remove(key);
        }
    }

    /** 抽象出来的缓存文件摘要信息.
     * 与Cache.Entry类几乎相同,但是只存储了响应体的大小,没保存响应体的内容.
     */
    static class CacheHeader {
        /** HTTP响应头(header)和响应体(body)的整体大小.也就是Disk缓存系统中对应缓存文件的大小. */
        public long size;

        public String key;

        /** HTTP响应首部中用于缓存新鲜度验证的ETag. */
        public String etag;

        /** HTTP响应时间. */
        public long serverDate;

        /** 缓存内容最后一次修改的时间. */
        public long lastModified;

        /** Request的http缓存过期时间. */
        public long ttl;

        /** Request的http缓存新鲜时间. */
        public long softTtl;

        /** HTTP的响应headers. */
        public Map<String, String> responseHeaders;

        private CacheHeader(){}

        /**
         * Instantiates a new CacheHeader object
         * @param key The key that indentifies the cache entry
         * @param entry The cache entry
         */
        public CacheHeader(String key, Entry entry) {
            this.key = key;
            this.size = entry.data.length;
            this.etag = entry.etag;
            this.serverDate = entry.serverDate;
            this.lastModified = entry.lastModified;
            this.ttl = entry.ttl;
            this.softTtl = entry.softTtl;
            this.responseHeaders = entry.responseHeaders;
        }

        /** 从InputStream中构造CacheHeader对象.其实就是实现对象的反序列化. */
        public static CacheHeader readHeader(InputStream is) throws IOException {
            CacheHeader entry = new CacheHeader();
            // 以CACHE_NUMBER作为读取一个对象的开始
            int magic = readInt(is);
            if (magic != CACHE_MAGIC) {
                throw new IOException();
            }
            entry.key = readString(is);
            entry.etag = readString(is);
            if (entry.etag.equals("")) {
                entry.etag = null;
            }
            entry.serverDate = readLong(is);
            entry.lastModified = readLong(is);
            entry.ttl = readLong(is);
            entry.softTtl = readLong(is);
            entry.responseHeaders = readStringStringMap(is);

            return entry;
        }

        /** 通过传入的data数组构造一个Cache.Entry对象. */
        public Entry toCacheEntry(byte[] data) {
            Entry e = new Entry();
            e.data = data;
            e.etag = etag;
            e.serverDate = serverDate;
            e.lastModified = lastModified;
            e.ttl = ttl;
            e.softTtl = softTtl;
            e.responseHeaders = responseHeaders;
            return e;
        }

        /** 将CacheHeader对象序列化. */
        public boolean writeHeader(OutputStream os) {
            try {
                writeInt(os, CACHE_MAGIC);
                writeString(os, key);
                writeString(os, etag == null ? "" : etag);
                writeLong(os, serverDate);
                writeLong(os, lastModified);
                writeLong(os, ttl);
                writeLong(os, softTtl);
                writeStringStringMap(responseHeaders, os);
                os.flush();
                return true;
            } catch (IOException e) {
                e.printStackTrace();
                return false;
            }
        }
    }

    static void writeString(OutputStream os, String s) throws IOException {
        byte[] b = s.getBytes("UTF-8");
        writeLong(os, b.length);
        os.write(b, 0, b.length);
    }

    /** InputStream中读取字符串的方法是:
     *  1. 读取字符串长度n.
     *  2. 读取n个字节保存在字符数组中.
     *  3. 将字符数组转换成字符串.
     */
    private static String readString(InputStream is) throws IOException {
        int n = (int)readLong(is);
        byte[] b = streamToBytes(is, n);
        return new String(b, "UTF-8");
    }

    private static byte[] streamToBytes(InputStream in, int length) throws IOException {
        byte[] bytes = new byte[length];
        int count;
        int pos = 0;
        // 这里调用的是InputStream的read(byte[] b, int off, int len)方法.作用是:
        // 从输入流中最多读取len个数据字节到byte数组中,并将读取的第一个字节存储在byte[pos]位置上.
        // 由于,每次读取的字节数count可能小于len,所以需要循环读取.
        while (pos < length && ((count = in.read(bytes, pos, length - pos)) != -1)) {
            pos += count;
        }
        if (pos != length) {
            throw new IOException("Expected " + length + " bytes, read " + pos + " bytes");
        }
        return bytes;
    }

    static void writeLong(OutputStream os, long n) throws IOException {
        os.write((byte)(n));
        os.write((byte)(n >>> 8));
        os.write((byte)(n >>> 16));
        os.write((byte)(n >>> 24));
        os.write((byte)(n >>> 32));
        os.write((byte)(n >>> 40));
        os.write((byte)(n >>> 48));
        os.write((byte)(n >>> 56));
    }

    private static long readLong(InputStream is) throws IOException {
        long n = 0;
        n |= ((read(is) & 0xFFL));
        n |= ((read(is) & 0xFFL) << 8);
        n |= ((read(is) & 0xFFL) << 16);
        n |= ((read(is) & 0xFFL) << 24);
        n |= ((read(is) & 0xFFL) << 32);
        n |= ((read(is) & 0xFFL) << 40);
        n |= ((read(is) & 0xFFL) << 48);
        n |= ((read(is) & 0xFFL) << 56);
        return n;
    }

    private static void writeInt(OutputStream os, int n) throws IOException {
        os.write((n) & 0xff);
        os.write((n >> 8) & 0xff);
        os.write((n >> 16) & 0xff);
        os.write((n >> 24) & 0xff);
    }

    private static int readInt(InputStream is) throws IOException {
        int n = 0;
        n |= (read(is));
        n |= (read(is) << 8);
        n |= (read(is) << 16);
        n |= (read(is) << 24);

        return n;
    }

    private static int read(InputStream is) throws IOException {
        int b = is.read();
        if (b == -1) {
            throw new EOFException();
        }
        return b;
    }

    static void writeStringStringMap(Map<String, String> map, OutputStream os) throws IOException {
        if (map != null) {
            writeInt(os, map.size());
            for (Map.Entry<String, String> entry : map.entrySet()) {
                writeString(os, entry.getKey());
                writeString(os, entry.getValue());
            }
        } else {
            writeInt(os, 0);
        }
    }


    /**
     * 从输入流中读取Map对象.读取方法如下:
     * 1. 读取Map对象的数量size.
     * 2. 然后循环读取size次,每次先读一个String作为key,再读一个String作为Value.
     */
    private static Map<String, String> readStringStringMap(InputStream is) throws IOException {
        int size = readInt(is);
        Map<String, String> result = (size == 0) ? Collections.<String, String>emptyMap()
                : new HashMap<String, String>(size);
        for (int i = 0; i < size; i ++) {
            String key = readString(is).intern();
            String value = readString(is).intern();
            result.put(key, value);
        }

        return result;
    }

    /** 继承FilterInputStream,增加记录读取总字节数的功能. */
    private static class CountingInputStream extends FilterInputStream{
        private int bytesRead = 0;

        private CountingInputStream(InputStream in) {
            super(in);
        }

        @Override
        public int read() throws IOException {
            int result = super.read();
            if (result != -1) {
                bytesRead ++;
            }
            return result;
        }

        @Override
        public int read(@NonNull byte[] buffer, int byteOffset, int byteCount) throws IOException {
            int result = super.read(buffer, byteOffset, byteCount);
            if (result != -1) {
                bytesRead += result;
            }
            return result;
        }
    }
}

有了DiskBasedCache类,我们就可以看一下Volley是如何对缓存进行存储的了. 回到RequestQueue类中,我们看一下跟缓存相关的代码实现.

CacheDispatcher.java

在RequestQueue的start方法里,有如下代码:

    public void start() {
        // 关闭所有正在运行的缓存线程和网络请求线程.
        stop();
        // 开启缓存线程.
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();
    }

从上面代码,可以看到,Volley是启动了一个线程来实现缓存功能.我们再学习CacheDispatcherd的实现之前,可以来思考一下,如果让我们来实现CacheDispatcher,我们的思路是什么呢? 我的思路如下:

  1. 在当前DiskBasedCache缓存系统中,查找是否已经缓存过该Request.
  2. 如果已经缓存过,且没有过期,则直接返回缓存系统中的内容.
  3. 如果没有缓存,或者缓存已经过期,则走网络请求,并且网络请求之后的结果记录到DiskBasedCache缓存系统中.

接下来,我们来看一下CacheDispatcher的源码,看看它是不是这么操作的:

/** 线程,用来调度可以走缓存的Request请求. */
public class CacheDispatcher extends Thread{
    /** 可以走Disk缓存的request请求队列. */
    private final BlockingQueue<Request<?>> mCacheQueue;

    /** 需要走网络的request请求队列. */
    private final BlockingQueue<Request<?>> mNetworkQueue;

    /** DiskBasedCache缓存实现类. */
    private final Cache mCache;

    /** 网络请求结果传递类. */
    private final ResponseDelivery mDelivery;

    /** 用来停止线程的标志位. */
    private volatile boolean mQuit = false;

    public CacheDispatcher(
            BlockingQueue<Request<?>> cacheQueue, BlockingQueue<Request<?>> networkQueue,
            Cache cache, ResponseDelivery delivery) {
        mCacheQueue = cacheQueue;
        mNetworkQueue = networkQueue;
        mCache = cache;
        mDelivery = delivery;
    }

    /** 通过标记位机制强行停止CacheDispatcher线程. */
    public void quit() {
        mQuit = true;
        interrupt();
    }

    @Override
    public void run() {
        android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

        // 初始化DiskBasedCache缓存类.
        mCache.initialize();

        while (true) {
            try {
                // 从缓存队列中获取request请求.(缓存队列实现了生产者-消费者队列模型)
                final Request<?> request = mCacheQueue.take();

                // 判断请求是否被取消
                if (request.isCanceled()) {
                    request.finish("cache-discard-canceled");
                    continue;
                }

                // 从缓存系统中获取request请求结果Cache.Entry.
                Cache.Entry entry = mCache.get(request.getCacheKey());
                if (entry == null) {
                    // 如果缓存系统中没有该缓存请求,则将request加入到网络请求队列中.
                    // 由于NetworkQueue跟NetworkDispatcher线程关联,并且也是生产者-消费者队列,
                    // 所以这里添加request请求就相当于将request执行网络请求.
                    mNetworkQueue.put(request);
                    continue;
                }

                // 判断缓存结果是否过期.
                if (entry.isExpired()) {
                    request.setCacheEntry(entry);
                    // 过期的缓存需要重新执行request请求.
                    mNetworkQueue.put(request);
                    continue;
                }

                // We have a cache hit; parse its data for delivery back to the request.
                Response<?> response = request.parseNetworkResponse(new NetworkResponse(entry.data,
                        entry.responseHeaders));

                // 判断Request请求结果是否新鲜?
                if (!entry.refreshNeeded()) {
                    // 请求结果新鲜,则直接将请求结果分发,进行异步回调用户接口.
                    mDelivery.postResponse(request, response);
                } else {
                    // 请求结果不新鲜,但是同样还是将缓存结果返回给用户,并且同时执行网络请求,刷新Request网络结果缓存.
                    request.setCacheEntry(entry);

                    response.intermediate = true;

                    mDelivery.postResponse(request, response, new Runnable() {
                        @Override
                        public void run() {
                            try {
                                mNetworkQueue.put(request);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    });
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                if (mQuit) {
                    return;
                }
            }
        }
    }
}

从源码中可以看出,CacheDispatcher的执行流程和我们设想的基本一致,但是当缓存内容不存在时,如何将网络拉取的最新内容存储在Cache缓存中却没有在CacheDispatcher类中体现.这是因为: NetworkDispatcher代码中,所有进行网络请求的request默认都会进行缓存存储,所以这里CacheDispatcher就不需要重复操作了.

之前介绍RequestQueue的时候,我们只介绍了不进行缓存的Request请求是如何被调度的,那这里我们继续看一下,默认情况下,Request都是需要进行缓存的,那缓存是如何调度的呢? 来看一下RequestQueue完整的add方法源码:

    /** 将Request请求加入到调度队列中. */
    public <T> Request<?> 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);
        }

        // 分配request唯一的序列号.
        request.setSequence(getSequenceNumber());

        // request不允许缓存,则直接将request加入到mNetworkQueue当中
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            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)) {
                // 表示RequestQueue正在调度过该Request,因为后续相同的Request先入队列,排队等待执行.
                Queue<Request<?>> stageRequests = mWaitingRequests.get(cacheKey);
                if (stageRequests == null) {
                    stageRequests = new LinkedList<Request<?>>();
                }
                stageRequests.add(request);
                mWaitingRequests.put(cacheKey, stageRequests);
            } else {
                // 将Request加入到等待Map中,表示Request正在执行.
                mWaitingRequests.put(cacheKey, null);
                mCacheQueue.add(request);
            }
            return request;
        }
    }

add方法之前也介绍过,这里要特殊强调一下mWaitingRequests的妙用. 在应用的网络请求过程中,有时可能由于多线程或者后台Service更新等机制,导致同一个Url的Request被同一时间多次请求.这时,RequestQueue通过mWaitingRequests这个Map很好的控制了这种情况. 通过mWaitingRequests,同一时间相同Url的Request只能有一个再执行.我想大家可能会有疑问(至少我看这部分代码时存在这个疑问):从代码逻辑中,可以看出,相同的Request被加入到Map该url对应的队列中,但是后续什么时候执行呢?add方法中并没有体现. 那既然同一时间相同url的Request只能有一个在执行,那mWaitingRequests中url对应队列的Request当然是在上一个Request执行完毕后才会执行.Request执行完毕后会调用自身的finish方法. Request的finish调用时机肯定是ExecutorDelivery类将结果回调给用户接口时调用的,具体代码大家可以翻看之前的ExecutorDelivery类源码. Request的finish方法源码如下:

    /** 用于告知请求队列当前request已经结束. */
    void finish(final String tag) {
        if (mRequestQueue != null) {
            mRequestQueue.finish(this);
        }
    }

可以看到,Request的finish方法其实是通知RequestQueue,调用RequestQueue的finish方法来结束自己.继续跟踪RequestQueue的finish方法:

    /** 该方法的调用时机为:参数Request将请求结果回调给用户接口时,会调用该方法告知此Request已经结束. */
    <T> void finish(Request<T> request) {
        // 从正在执行的Request队列中删除指定的request.
        synchronized (mCurrentRequests) {
            mCurrentRequests.remove(request);
        }

        // 观察者模式,通知Observer该request请求结束.
        synchronized (mFinishedListeners) {
            for (RequestFinishedListener<T> listener : mFinishedListeners) {
                listener.onRequestFinished(request);
            }
        }

        if (request.shouldCache()) {
            synchronized (mWaitingRequests) {
                // 因为当前Request已经正常结束,而且该request是可以缓存的,所以这时需要直接把正在等待的所有相同
                // url的Request全部加入到缓存队列中,从缓存系统读取结果后回调用户接口.
                String cacheKey = request.getCacheKey();
                Queue<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);
                if (waitingRequests != null) {
                    mCacheQueue.addAll(waitingRequests);
                }
            }
        }
    }

相信上面的注释足够让大家理解mWaitingRequests的妙用了.


Volley获取网络图片

本来想分析Universal Image Loader的源码,但是发现Volley已经实现了网络图片的加载功能.其实,网络图片的加载也是分几个步骤:

  1. 获取网络图片的url.
  2. 判断该url对应的图片是否有本地缓存.
  3. 有本地缓存,直接使用本地缓存图片,通过异步回调给ImageView进行设置.
  4. 无本地缓存,就先从网络拉取,保存在本地后,再通过异步回调给ImageView进行设置.

我们通过Volley源码,看一下Volley是否是按照这个步骤实现网络图片加载的.

ImageRequest.java

按照Volley的架构,我们首先需要构造一个网络图片请求,Volley帮我们封装了ImageRequest类,我们来看一下它的具体实现:

/** 网络图片请求类. */
@SuppressWarnings("unused")
public class ImageRequest extends Request<Bitmap> {
    /** 默认图片获取的超时时间(单位:毫秒) */
    public static final int DEFAULT_IMAGE_REQUEST_MS = 1000;

    /** 默认图片获取的重试次数. */
    public static final int DEFAULT_IMAGE_MAX_RETRIES = 2;

    private final Response.Listener<Bitmap> mListener;
    private final Bitmap.Config mDecodeConfig;
    private final int mMaxWidth;
    private final int mMaxHeight;
    private ImageView.ScaleType mScaleType;

    /** Bitmap解析同步锁,保证同一时间只有一个Bitmap被load到内存进行解析,防止OOM. */
    private static final Object sDecodeLock = new Object();

    /**
     * 构造一个网络图片请求.
     * @param url 图片的url地址.
     * @param listener 请求成功用户设置的回调接口.
     * @param maxWidth 图片的最大宽度.
     * @param maxHeight 图片的最大高度.
     * @param scaleType 图片缩放类型.
     * @param decodeConfig 解析bitmap的配置.
     * @param errorListener 请求失败用户设置的回调接口.
     */
    public ImageRequest(String url, Response.Listener<Bitmap> listener, int maxWidth, int maxHeight,
                        ImageView.ScaleType scaleType, Bitmap.Config decodeConfig,
                        Response.ErrorListener errorListener) {
        super(Method.GET, url, errorListener);
        mListener = listener;
        mDecodeConfig = decodeConfig;
        mMaxWidth = maxWidth;
        mMaxHeight = maxHeight;
        mScaleType = scaleType;
    }

    /** 设置网络图片请求的优先级. */
    @Override
    public Priority getPriority() {
        return Priority.LOW;
    }

    @Override
    protected Response<Bitmap> parseNetworkResponse(NetworkResponse response) {
        synchronized (sDecodeLock) {
            try {
                return doParse(response);
            } catch (OutOfMemoryError e) {
                return Response.error(new VolleyError(e));
            }
        }
    }

    private Response<Bitmap> doParse(NetworkResponse response) {
        byte[] data = response.data;
        BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
        Bitmap bitmap;
        if (mMaxWidth == 0 && mMaxHeight == 0) {
            decodeOptions.inPreferredConfig = mDecodeConfig;
            bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
        } else {
            // 获取网络图片的真实尺寸.
            decodeOptions.inJustDecodeBounds = true;
            BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
            int actualWidth = decodeOptions.outWidth;
            int actualHeight = decodeOptions.outHeight;

            int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,
                    actualWidth, actualHeight, mScaleType);
            int desireHeight = getResizedDimension(mMaxWidth, mMaxHeight,
                    actualWidth, actualHeight, mScaleType);

            decodeOptions.inJustDecodeBounds = false;
            decodeOptions.inSampleSize =
                    findBestSampleSize(actualWidth, actualHeight, desiredWidth, desireHeight);
            Bitmap tempBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);

            if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||
                    tempBitmap.getHeight() > desireHeight)) {
                bitmap = Bitmap.createScaledBitmap(tempBitmap, desiredWidth, desireHeight, true);
                tempBitmap.recycle();
            } else {
                bitmap = tempBitmap;
            }
        }

        if (bitmap == null) {
            return Response.error(new VolleyError(response));
        } else {
            return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));
        }
    }

    static int findBestSampleSize(
            int actualWidth, int actualHeight, int desiredWidth, int desireHeight) {
        double wr = (double) actualWidth / desiredWidth;
        double hr = (double) actualHeight / desireHeight;
        double ratio = Math.min(wr, hr);
        float n = 1.0f;
        while ((n * 2) <= ratio) {
            n *= 2;
        }
        return (int) n;
    }

    /** 根据ImageView的ScaleType设置图片的大小. */
    private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary,
                                           int actualSecondary, ImageView.ScaleType scaleType) {
        // 如果没有设置ImageView的最大值,则直接返回网络图片的真实大小.
        if ((maxPrimary == 0) && (maxSecondary == 0)) {
            return actualPrimary;
        }

        // 如果ImageView的ScaleType为FIX_XY,则将其设置为图片最值.
        if (scaleType == ImageView.ScaleType.FIT_XY) {
            if (maxPrimary == 0) {
                return actualPrimary;
            }
            return maxPrimary;
        }

        if (maxPrimary == 0) {
            double ratio = (double)maxSecondary / (double)actualSecondary;
            return (int)(actualPrimary * ratio);
        }

        if (maxSecondary == 0) {
            return maxPrimary;
        }

        double ratio = (double) actualSecondary / (double) actualPrimary;
        int resized = maxPrimary;

        if (scaleType == ImageView.ScaleType.CENTER_CROP) {
            if ((resized * ratio) < maxSecondary) {
                resized = (int)(maxSecondary / ratio);
            }
            return resized;
        }

        if ((resized * ratio) > maxSecondary) {
            resized = (int)(maxSecondary / ratio);
        }

        return resized;
    }


    @Override
    protected void deliverResponse(Bitmap response) {
        mListener.onResponse(response);
    }
}

因为Volley本身框架已经实现了对网络请求的本地缓存,所以ImageRequest做的主要事情就是解析字节流为Bitmap,再解析过程中,通过静态变量保证每次只解析一个Bitmap防止OOM,使用ScaleType和用户设置的MaxWidth和MaxHeight来设置图片大小. 总体来说,ImageRequest的实现非常简单,这里不做过多的讲解.ImageRequest的缺陷在于:

  1. 需要用户进行过多的设置,包括图片的大小的最大值.
  2. 没有图片的内存缓存,因为Volley的缓存是基于Disk的缓存,有对象反序列化的过程.

ImageLoader.java

鉴于以上两个缺点,Volley又提供了一个更牛逼的ImageLoader类.其中,最关键的就是增加了内存缓存. 再讲解ImageLoader的源码之前,需要先介绍一下ImageLoader的使用方法.和之前的Request请求不同,ImageLoader并不是new出来直接扔给RequestQueue进行调度,它的使用方法大体分为4步:

  • 创建一个RequestQueue对象.
RequestQueue queue = Volley.newRequestQueue(context);
  • 创建一个ImageLoader对象.ImageLoader构造函数接收两个参数,第一个是RequestQueue对象,第二个是ImageCache对象(也就是内存缓存类,我们先不给出具体实现,讲解完ImageLoader源码之后,我会提供一个利用LRU算法的ImageCache实现类)
ImageLoader imageLoader = new ImageLoader(queue, new ImageCache() {
    @Override
    public void putBitmap(String url, Bitmap bitmap) {}
    @Override
    public Bitmap getBitmap(String url) { return null; }
});
  • 获取一个ImageListener对象.
ImageListener listener = ImageLoader.getImageListener(imageView, R.drawable.default_imgage, R.drawable.failed_image);
  • 调用ImageLoader的get方法加载网络图片.
imageLoader.get(mImageUrl, listener, maxWidth, maxHeight, scaleType);

有了ImageLoader的使用方法,我们结合使用方法来看一下ImageLoader的源码:

@SuppressWarnings({"unused", "StringBufferReplaceableByString"})
public class ImageLoader {
    /**
     * 关联用来调用ImageLoader的RequestQueue.
     */
    private final RequestQueue mRequestQueue;

    /** 图片内存缓存接口实现类. */
    private final ImageCache mCache;

    /** 存储同一时间执行的相同CacheKey的BatchedImageRequest集合. */
    private final HashMap<String, BatchedImageRequest> mInFlightRequests =
            new HashMap<String, BatchedImageRequest>();

    private final HashMap<String, BatchedImageRequest> mBatchedResponses =
            new HashMap<String, BatchedImageRequest>();

    /** 获取主线程的Handler. */
    private final Handler mHandler = new Handler(Looper.getMainLooper());


    private Runnable mRunnable;

    /** 定义图片K1缓存接口,即将图片的内存缓存工作交给用户来实现. */
    public interface ImageCache {
        Bitmap getBitmap(String url);
        void putBitmap(String url, Bitmap bitmap);
    }

    /** 构造一个ImageLoader. */
    public ImageLoader(RequestQueue queue, ImageCache imageCache) {
        mRequestQueue = queue;
        mCache = imageCache;
    }

    /** 构造网络图片请求成功和失败的回调接口. */
    public static ImageListener getImageListener(final ImageView view, final int defaultImageResId,
                                                 final int errorImageResId) {
        return new ImageListener() {
            @Override
            public void onResponse(ImageContainer response, boolean isImmediate) {
                if (response.getBitmap() != null) {
                    view.setImageBitmap(response.getBitmap());
                } else if (defaultImageResId != 0) {
                    view.setImageResource(defaultImageResId);
                }
            }

            @Override
            public void onErrorResponse(VolleyError error) {
                if (errorImageResId != 0) {
                    view.setImageResource(errorImageResId);
                }
            }
        };
    }

    public ImageContainer get(String requestUrl, ImageListener imageListener,
                               int maxWidth, int maxHeight, ScaleType scaleType) {
        // 判断当前方法是否在UI线程中执行.如果不是,则抛出异常.
        throwIfNotOnMainThread();

        final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);

        // 从L1级缓存中根据key获取对应的Bitmap.
        Bitmap cacheBitmap = mCache.getBitmap(cacheKey);
        if (cacheBitmap != null) {
            // L1缓存命中,通过缓存命中的Bitmap构造ImageContainer,并调用imageListener的响应成功接口.
            ImageContainer container = new ImageContainer(cacheBitmap, requestUrl, null, null);
            // 注意:因为目前是在UI线程中,因此这里是调用onResponse方法,并非回调.
            imageListener.onResponse(container, true);
            return container;
        }

        ImageContainer imageContainer =
                new ImageContainer(null, requestUrl, cacheKey, imageListener);
        // L1缓存命中失败,则先需要对ImageView设置默认图片.然后通过子线程拉取网络图片,进行显示.
        imageListener.onResponse(imageContainer, true);

        // 检查cacheKey对应的ImageRequest请求是否正在运行.
        BatchedImageRequest request = mInFlightRequests.get(cacheKey);
        if (request != null) {
            // 相同的ImageRequest正在运行,不需要同时运行相同的ImageRequest.
            // 只需要将其对应的ImageContainer加入到BatchedImageRequest的mContainers集合中.
            // 当正在执行的ImageRequest结束后,会查看当前有多少正在阻塞的ImageRequest,
            // 然后对其mContainers集合进行回调.
            request.addContainer(imageContainer);
            return imageContainer;
        }

        // L1缓存没命中,还是需要构造ImageRequest,通过RequestQueue的调度来获取网络图片
        // 获取方法可能是:L2缓存(ps:Disk缓存)或者HTTP网络请求.
        Request<Bitmap> newRequest =
                makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType, cacheKey);
        mRequestQueue.add(newRequest);
        mInFlightRequests.put(cacheKey, new BatchedImageRequest(newRequest, imageContainer));

        return imageContainer;
    }

    /** 构造L1缓存的key值. */
    private String getCacheKey(String url, int maxWidth, int maxHeight, ScaleType scaleType) {
        return new StringBuilder(url.length() + 12).append("#W").append(maxWidth)
                .append("#H").append(maxHeight).append("#S").append(scaleType.ordinal()).append(url)
                .toString();
    }

    public boolean isCached(String requestUrl, int maxWidth, int maxHeight) {
        return isCached(requestUrl, maxWidth, maxHeight, ScaleType.CENTER_INSIDE);
    }

    private boolean isCached(String requestUrl, int maxWidth, int maxHeight, ScaleType scaleType) {
        throwIfNotOnMainThread();

        String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);
        return mCache.getBitmap(cacheKey) != null;
    }


    /** 当L1缓存没有命中时,构造ImageRequest,通过ImageRequest和RequestQueue获取图片. */
    protected Request<Bitmap> makeImageRequest(final String requestUrl, int maxWidth, int maxHeight,
                                               ScaleType scaleType, final String cacheKey) {
        return new ImageRequest(requestUrl, new Response.Listener<Bitmap>() {
            @Override
            public void onResponse(Bitmap response) {
                onGetImageSuccess(cacheKey, response);
            }
        }, maxWidth, maxHeight, scaleType, Bitmap.Config.RGB_565, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                onGetImageError(cacheKey, error);
            }
        });
    }

    /** 图片请求失败回调.运行在UI线程中. */
    private void onGetImageError(String cacheKey, VolleyError error) {
        BatchedImageRequest request = mInFlightRequests.remove(cacheKey);
        if (request != null) {
            request.setError(error);
            batchResponse(cacheKey, request);
        }
    }

    /** 图片请求成功回调.运行在UI线程中. */
    protected void onGetImageSuccess(String cacheKey, Bitmap response) {
        // 增加L1缓存的键值对.
        mCache.putBitmap(cacheKey, response);

        // 同一时间内最初的ImageRequest执行成功后,回调这段时间阻塞的相同ImageRequest对应的成功回调接口.
        BatchedImageRequest request = mInFlightRequests.remove(cacheKey);
        if (request != null) {
            request.mResponseBitmap = response;
            // 将阻塞的ImageRequest进行结果分发.
            batchResponse(cacheKey, request);
        }
    }

    private void batchResponse(String cacheKey, BatchedImageRequest request) {
        mBatchedResponses.put(cacheKey, request);
        if (mRunnable == null) {
            mRunnable = new Runnable() {
                @Override
                public void run() {
                    for (BatchedImageRequest bir :  mBatchedResponses.values()) {
                        for (ImageContainer container : bir.mContainers) {
                            if (container.mListener == null) {
                                continue;
                            }

                            if (bir.getError() == null) {
                                container.mBitmap = bir.mResponseBitmap;
                                container.mListener.onResponse(container, false);
                            } else {
                                container.mListener.onErrorResponse(bir.getError());
                            }
                        }
                    }
                    mBatchedResponses.clear();
                    mRunnable = null;
                }
            };
            // Post the runnable
            mHandler.postDelayed(mRunnable, 100);
        }
    }

    private void throwIfNotOnMainThread() {
        if (Looper.myLooper() != Looper.getMainLooper()) {
            throw new IllegalStateException("ImageLoader must be invoked from the main thread.");
        }
    }

    /** 抽象出请求成功和失败的回调接口.默认可以使用Volley提供的ImageListener. */
    public interface ImageListener extends Response.ErrorListener {
        void onResponse(ImageContainer response, boolean isImmediate);
    }

    /** 网络图片请求的承载对象. */
    public class ImageContainer {
        /** ImageView需要加载的Bitmap. */
        private Bitmap mBitmap;

        /** L1缓存的key */
        private final String mCacheKey;

        /** ImageRequest请求的url. */
        private final String mRequestUrl;

        /** 图片请求成功或失败的回调接口类. */
        private final ImageListener mListener;

        public ImageContainer(Bitmap bitmap, String requestUrl, String cacheKey,
                              ImageListener listener) {
            mBitmap = bitmap;
            mRequestUrl = requestUrl;
            mCacheKey = cacheKey;
            mListener = listener;

        }

        public void cancelRequest() {
            if (mListener == null) {
                return;
            }

            BatchedImageRequest request = mInFlightRequests.get(mCacheKey);
            if (request != null) {
                boolean canceled = request.removeContainerAndCancelIfNecessary(this);
                if (canceled) {
                    mInFlightRequests.remove(mCacheKey);
                }
            } else {
                request = mBatchedResponses.get(mCacheKey);
                if (request != null) {
                    request.removeContainerAndCancelIfNecessary(this);
                    if (request.mContainers.size() == 0) {
                        mBatchedResponses.remove(mCacheKey);
                    }
                }
            }
        }

        public Bitmap getBitmap() {
            return mBitmap;
        }

        public String getRequestUrl() {
            return mRequestUrl;
        }
    }

    /**
     * CacheKey相同的ImageRequest请求抽象类.
     * 判定两个ImageRequest相同包括:
     * 1. url相同.
     * 2. maxWidth和maxHeight相同.
     * 3. 显示的scaleType相同.
     * 同一时间可能有多个相同CacheKey的ImageRequest请求,由于需要返回的Bitmap都一样,所以用BatchedImageRequest
     * 来实现该功能.同一时间相同CacheKey的ImageRequest只能有一个.
     * 为什么不使用RequestQueue的mWaitingRequestQueue来实现该功能?
     * 答:是因为仅靠URL是没法判断两个ImageRequest相等的.
     */
    private class BatchedImageRequest {
        /** 对应的ImageRequest请求. */
        private final Request<?> mRequest;

        /** 请求结果的Bitmap对象. */
        private Bitmap mResponseBitmap;

        /** ImageRequest的错误. */
        private VolleyError mError;

        /** 所有相同ImageRequest请求结果的封装集合. */
        private final LinkedList<ImageContainer> mContainers = new LinkedList<ImageContainer>();

        public BatchedImageRequest(Request<?> request, ImageContainer container) {
            mRequest = request;
            mContainers.add(container);
        }

        public VolleyError getError() {
            return mError;
        }

        public void setError(VolleyError error) {
            mError = error;
        }

        public void addContainer(ImageContainer container) {
            mContainers.add(container);
        }

        public boolean removeContainerAndCancelIfNecessary(ImageContainer container) {
            mContainers.remove(container);
            if (mContainers.size() == 0) {
                mRequest.cancel();
                return true;
            }
            return false;
        }
    }
}

重大疑问

个人对Imageloader的源码有两个重大疑问?

  • batchResponse方法的实现.

我很奇怪,为什么ImageLoader类里面要有一个HashMap来保存BatchedImageRequest集合呢?

    private final HashMap<String, BatchedImageRequest> mBatchedResponses =
            new HashMap<String, BatchedImageRequest>();

毕竟batchResponse是在特定的ImageRequest执行成功的回调中被调用的,调用代码如下:

    protected void onGetImageSuccess(String cacheKey, Bitmap response) {
        // 增加L1缓存的键值对.
        mCache.putBitmap(cacheKey, response);

        // 同一时间内最初的ImageRequest执行成功后,回调这段时间阻塞的相同ImageRequest对应的成功回调接口.
        BatchedImageRequest request = mInFlightRequests.remove(cacheKey);
        if (request != null) {
            request.mResponseBitmap = response;
            // 将阻塞的ImageRequest进行结果分发.
            batchResponse(cacheKey, request);
        }
    }

从上述代码可以看出,ImageRequest请求成功后,已经从mInFlightRequests中获取了对应的BatchedImageRequest对象.而同一时间被阻塞的相同的ImageRequest对应的ImageContainer都在BatchedImageRequest的mContainers集合中. 那我认为,batchResponse方法只需要遍历对应BatchedImageRequest的mContainers集合即可. 但是,ImageLoader源码中,我认为多余的构造了一个HashMap对象mBatchedResponses来保存BatchedImageRequest集合,然后在batchResponse方法中又对集合进行两层for循环各种遍历,实在是非常诡异,求指导. 诡异代码如下:

    private void batchResponse(String cacheKey, BatchedImageRequest request) {
        mBatchedResponses.put(cacheKey, request);
        if (mRunnable == null) {
            mRunnable = new Runnable() {
                @Override
                public void run() {
                    for (BatchedImageRequest bir :  mBatchedResponses.values()) {
                        for (ImageContainer container : bir.mContainers) {
                            if (container.mListener == null) {
                                continue;
                            }

                            if (bir.getError() == null) {
                                container.mBitmap = bir.mResponseBitmap;
                                container.mListener.onResponse(container, false);
                            } else {
                                container.mListener.onErrorResponse(bir.getError());
                            }
                        }
                    }
                    mBatchedResponses.clear();
                    mRunnable = null;
                }
            };
            // Post the runnable
            mHandler.postDelayed(mRunnable, 100);
        }
    }

我认为的代码实现应该是:

    private void batchResponse(String cacheKey, BatchedImageRequest request) {
        if (mRunnable == null) {
            mRunnable = new Runnable() {
                @Override
                public void run() {
                    for (ImageContainer container : request.mContainers) {
                        if (container.mListener == null) {
                            continue;
                        }

                        if (request.getError() == null) {
                            container.mBitmap = request.mResponseBitmap;
                            container.mListener.onResponse(container, false);
                        } else {
                            container.mListener.onErrorResponse(request.getError());
                        }
                    }
                    mRunnable = null;
                }
            };
            // Post the runnable
            mHandler.postDelayed(mRunnable, 100);
        }
    }
  • 使用ImageLoader默认提供的ImageListener,我认为存在一个缺陷,即图片闪现问题.当为ListView的item设置图片时,需要增加TAG判断.因为对应的ImageView可能已经被回收利用了.

自定义L1缓存类

首先说明一下,所谓的L1和L2缓存分别指的是内存缓存和硬盘缓存. 实现L1缓存,我们可以使用Android提供的Lru缓存类,示例代码如下:

import android.graphics.Bitmap;
import android.support.v4.util.LruCache;

/** Lru算法的L1缓存实现类. */
@SuppressWarnings("unused")
public class ImageLruCache implements ImageLoader.ImageCache {
    private LruCache<String, Bitmap> mLruCache;

    public ImageLruCache() {
        this((int) Runtime.getRuntime().maxMemory() / 8);
    }

    public ImageLruCache(final int cacheSize) {
        createLruCache(cacheSize);
    }

    private void createLruCache(final int cacheSize) {
        mLruCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight();
            }
        };
    }

    @Override
    public Bitmap getBitmap(String url) {
        return mLruCache.get(url);
    }

    @Override
    public void putBitmap(String url, Bitmap bitmap) {
        mLruCache.put(url, bitmap);
    }
}

Volley框架概览

讲到这里,Volley的整体框架基本就算介绍完全了.相信坚持看到这里的同学,肯定对Volley框架也已经非常熟悉,这时候我们再来看一下Volley框架的整体架构,回顾一下之前所讲的知识: Volley_FRAME


补充

  1. Volley 缓存机制详解
  2. Volley 超时重试机制详解

问答

欢迎大家提出跟Volley架构相关的问题,我会挑选出某个问题进行具体解答.

  1. 为什么Volley适合频繁的网络请求,不适合文件上传等大数据请求呢?

答:Volley为什么适合频繁的网络请求,是因为:

  1. Volley有四个并发的线程,并有一个阻塞队列来对并发线程进行调度.
  2. Volley有自己的Disk缓存系统,相同url的Request再没过期前可以直接从Disk缓存系统中获取结果.
  3. Volley的RequestQueue类有一个mWaitingRequest的Map,用来存储相同url的request,key为url,value为request队列.保证同一时间相同url的request只有一个再执行,后续Request再第一个request结束后可直接从缓存系统中获取结果. 为什么不适合文件上传,是因为文件上传这种操作都是唯一的,用不到缓存,而且4个线程的并发似乎也有点少.

volley's People

Contributors

wangzhengyi avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.