Volley学习

作者: i冰点 | 来源:发表于2016-08-30 17:44 被阅读369次

1、基本使用

Volley适合轻量、高并发的网络请求,但如果大数据量的操作,比如上传下载文件,就不太适合了。使用Volley主要有以下几个步奏:

1、设置、启动请求队列RequestQueue
  • RequestQueue只有一个
  • 需要的Context是Application context,不是Activity context
  • 在调用newRequestQueue()方法的时候,内部已经调用了queue.start(),如果再调用这个方法,可能会有异常。
RequestQueue queue= Volley.newRequestQueue(context.getApplicationContext());

RequestQueue 管理若干个工作线程:缓存处理线程、网络请求处理线程(4)

2、新建请求对象 Request<T>

Request对象主要负责解析原始的响应数据(parseNetworkResponse())

Request<T> request=new Request<>(...);

volley默认提供了四种Request:StringRequest、JsonArrayRequest、JsonObjectRequest、ImageRequest。具体用法参考:Android Volley完全解析(一),初识Volley的基本用法Android Volley完全解析(二),使用Volley加载网络图片

3、将Request添加到请求队列中
queue.add(request);
4、取消请求
  • 给request设置tag:request.setTag(tag)
  • 然后可以在onstop()方法 取消对应的请求:queue.cancelAll(tag)、
queue.cancelAll(tag)、
5、在请求出错时,怎么使用缓存的数据?
    /**
     * 请求出错时,从缓存中取出数据
     * @param error
     */
    @Override
    public void deliverError(VolleyError error) {
        if (error instanceof NoConnectionError) {
            Cache.Entry entry = this.getCacheEntry();
            if(entry != null) {
                Response<T> response = parseNetworkResponse(new NetworkResponse(entry.data, entry.responseHeaders));
                deliverResponse(response.result);
                return;
            }
        }
        super.deliverError(error);
    }
6、发送post请求

重写getParams()方法,因为在发送post数据的时候,会调用 request.getBody()--->request.getParams()方法

    @Override
    protected Map<String, String> getParams() throws AuthFailureError {
        return map;
    }
7、超时重试

开发时,如果遇到一个request(尤其是post),被发送了多次。。。
可以使用DefaultRetryPolicy,重新设置这个请求的超时时间

// 设置超时时间。要确保最大重试次数为1,以保证超时后不重新请求
request.setRetryPolicy(new DefaultRetryPolicy(20 * 1000, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
8、当然使用Volley,需要先引入Volley:

你可以将它下载到本地:

git clone https://android.googlesource.com/platform/frameworks/volley

或者使用gradle

compile 'com.android.volley:volley:1.0.0'

最后不要忘了权限

<uses-permission android:name="android.permission.INTERNET"/>

2、几个概念

1、状态码---204、304

服务器都没有返回响应的主体
当判定缓存过期后, 会向源服务器确认资源的有效性。

2、Expires

代表资源的失效日期
Cache-Control 的max-age 指令和 Expires都可以代表资源的失效日期,当它们同时存在时会优先处理 max-age 指令。

3、HttpURLConnection的使用

1、建立HttpURLConnection 对象

HttpURLConnection connection = (HttpURLConnection) url.openConnection();

2、设置连接\请求属性

  • 设置连接服务器超时、从服务器读取数据超时
connection.setConnectTimeout(timeoutMs);
connection.setReadTimeout(timeoutMs);

如果不设置超时(timeout),在网络异常的情况下,可能会导致程序不继续往下执行

  • 不使用缓存
connection.setUseCaches(false);
  • 允许从连接中读取数据,默认为true
connection.setDoInput(true);
  • 允许向连接中写入数据,默认为false,post的时候要用
 connection.setDoOutput(true);
  • 设置请求的方式,post
 connection.setRequestMethod("POST");
  • 设置请求属性
 connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

如上,connection的配置必须要在connect之前完成

3、连接

connection.connect(); 
或者
connection.getOutputStream()
或者
connection.getInputStream()

HttpURLConnection的connect()函数,实际上只是建立了一个与服务器的tcp连接,并没有实际发送http请求。 无论是post还是get,http请求实际上直到HttpURLConnection的getInputStream()这个函数里面才正式发送出去。

4、发送post请求

DataOutputStream out = new DataOutputStream(connection.getOutputStream());
out.write(body);
out.close();

在http头后面紧跟着的是http请求的正文,正文的内容是通过outputStream流写入的, 实际上outputStream不是一个网络流,充其量是个字符串流,往里面写入的东西不会立即发送到网络, 而是存在于内存缓冲区中,待outputStream流关闭时,根据输入的内容生成http正文。
至此,http请求的东西已经全部准备就绪。在getInputStream()函数调用的时候,就会把准备好的http请求 正式发送到服务器了,然后返回一个输入流,用于读取服务器对于此次http请求的返回信息。由于http 请求在getInputStream的时候已经发送出去了(包括http头和正文),因此在getInputStream()函数之后对connection对象进行设置(对http头的信息进行修改)或者写入outputStream(对正文进行修改)都是没有意义的了,执行这些操作会导致异常的发生。

5、获取响应

connection.getResponseCode();
connection.getResponseMessage();
connection.getInputStream();
connection.getHeaderFields();

参考:HttpURLConnection用法详解

3、分析

life of request
1、网络请求

//Volley是如何设置请求报文(报文首部、报文主体)?响应报文是如何被缓存的?

Volley的网络请求从NetworkDispatcher开始,经过mNetwork(BasicNetwork)的performRequest,最终由HttpStack(HurlStack)的performRequest执行具体的网络请求并处理响应。然后通过request.parseNetworkResponse(),解析响应的数据。最后调用mDelivery,将结果分发到主线程。在这个过程中会根据条件,决定是否解析和分发数据,是否缓存数据

1、NetworkDispatcher主要做了下面几件事:

  • 从mQueue中取出一个请求,通过mNetwork执行网络请求
    • 如果服务器返回的是304,同时response已经被缓存处理程序分发过了,不分发数据
  • 通过request.parseNetworkResponse(),解析服务器返回的响应 ,
    • 同时,如果满足条件,调用mCache缓存响应(请求的响应需要缓存,同时response.cacheEntry != null)
  • 调用mDelivery,将结果分发到主线程

public class NetworkDispatcher extends Thread {

    ...

    @Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        while (true) {
            Request<?> request = mQueue.take();
            // Perform the network 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.
            //在新建networkResponse时,为notModified赋值
            if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                request.finish("not-modified");
                continue;
            }

            // Parse the response here on the worker thread.
            Response<?> response = request.parseNetworkResponse(networkResponse);

            // Write to cache if applicable.
            // TODO: Only update cache metadata instead of entire record for 304s.
            //在自定义的request里赋值的
            if (request.shouldCache() && response.cacheEntry != null) {
                mCache.put(request.getCacheKey(), response.cacheEntry);
            }

            // Post the response back.
            request.markDelivered();
            // 分发 响应和错误到主线程
            mDelivery.postResponse(request, response);
        }
    }
}

2、mNetwork(BasicNetwork)的performRequest

  • 通过HttpStack(HurlStack)的performRequest,执行具体的网络请求(2)
  • 根据状态码(204/304/...),返回不同的NetworkResponse
public class BasicNetwork implements Network {

    ...

    @Override
    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        while (true) {
            HttpResponse httpResponse = null;
            byte[] responseContents = null;
            Map<String, String> responseHeaders = Collections.emptyMap();

            // 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. 304
            if (statusCode == HttpStatus.SC_NOT_MODIFIED) {

                Entry entry = request.getCacheEntry();
                if (entry == null) {
                    return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,responseHeaders, true, SystemClock.elapsedRealtime() - requestStart);       
                }

                // A HTTP 304 response does not have all header fields. We
                // have to use the header fields from the cache entry plus
                // the new ones from the response.
                // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
                entry.responseHeaders.putAll(responseHeaders);
                return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
                        entry.responseHeaders, true,
                        SystemClock.elapsedRealtime() - requestStart);
            }

            // 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];
            }
           
            return new NetworkResponse(statusCode, responseContents, responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);

        }
    }

}

3、HttpStack(HurlStack)的performRequest

  • 创建HttpURLConnection并设置连接/请求参数,
  • 连接,
  • 初始化HttpResponse对象
    • 设置responseStatus
    • 设置Entity
    • 设置Header

public class HurlStack implements HttpStack {

    ...

    @Override
    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
    {
        String url = request.getUrl();
        HashMap<String, String> map = new HashMap<String, String>();
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        map.putAll(request.getHeaders());
        map.putAll(additionalHeaders);
       
        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 responseCode = connection.getResponseCode();
        
        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 (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;
    }

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

    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);
        // 允许从 URL connection 中读取
        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;
    }

    static void setConnectionParametersForRequest(HttpURLConnection connection,
            Request<?> request) throws IOException, AuthFailureError {
        switch (request.getMethod()) {
            
            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.POST:
                connection.setRequestMethod("POST");
                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) {
            addBody(connection, request, body);
        }
    }

    // todo 添加post请求 报文主体部分 (参数部分)
    private static void addBody(HttpURLConnection connection, Request<?> request, byte[] body)
            throws IOException, AuthFailureError {
        // 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.
        // 允许向 URL connection 中写入
        connection.setDoOutput(true);
        //设定传送的内容类型是可序列化的java对象
        connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getBodyContentType());
        //getOutputStream会隐含的进行connect,连接
        DataOutputStream out = new DataOutputStream(connection.getOutputStream());
        out.write(body);
        out.close();
        //在调用下边的getInputStream()函数时才把准备好的http请求正式发送到服务器
    }
}

2、缓存
  • 从缓存队列里取出一个请求
  • 根据这个请求获取它的缓存
    • 如果缓存为空或者缓存过期,将请求放到网络请求队列里
  • 解析缓存
  • 调用mDelivery,将结果分发到主线程

public class CacheDispatcher extends Thread {

    @Override
    public void run() {
        // Make a blocking call to initialize the cache.
        mCache.initialize();

        while (true) {
            // Get a request from the cache triage queue, blocking until at least one is available.
                final Request<?> request = mCacheQueue.take();

                // Attempt to retrieve this item from cache.
                Cache.Entry entry = mCache.get(request.getCacheKey());
                if (entry == null) {
                    // Cache miss; send off to the network dispatcher.
                    mNetworkQueue.put(request);
                    continue;
                }

                // If it is completely expired, just send it to the network.
                if (entry.isExpired()) {
                    request.setCacheEntry(entry);
                    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));

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

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

                    // Post the intermediate response back to the user and have
                    // the delivery then forward the request along to the network.
                    mDelivery.postResponse(request, response, new Runnable() {
                        @Override
                        public void run() {
                            mNetworkQueue.put(request);
                        }
                    });
                }
        }
    }
}

3、这个 Cache.Entry是在什么地方初始化的呢?

在NetworkDispatcher中,如果允许缓存

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

response是在request 的 parseNetworkResponse中赋值的

public class StringRequest extends Request<String> {
    ...
    @Override
    protected void deliverResponse(String response) {
        if (mListener != null) {
            mListener.onResponse(response);
        }
    }

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

通过HttpHeaderParser.parseCacheHeaders(response),可以得到Cache.Entry。

当Cache-Control是no-cache或者no-store时,不缓存响应;当Cache-Control是max-age时,缓存的过期时间softTtl/ttl= now + maxAge * 1000(优先);当没有设置Cache-Control,设置了资源的失效期Expires时,缓存的过期时间softTtl/ttl= now + (serverExpires - serverDate);


public class HttpHeaderParser {
    public static Cache.Entry parseCacheHeaders(NetworkResponse response) {
        long now = System.currentTimeMillis();

        Map<String, String> headers = response.headers;

        long serverDate = 0;
        long lastModified = 0;

        long serverExpires = 0;
        long softExpire = 0;
        long finalExpire = 0;
        long maxAge = 0;
        long staleWhileRevalidate = 0;
        boolean hasCacheControl = false;
        boolean mustRevalidate = false;

        String serverEtag = null;
        String headerValue;

        // 创建报文的日期
        headerValue = headers.get("Date");
        if (headerValue != null) {
            serverDate = parseDateAsEpoch(headerValue);
        }

        //Cache-Control 控制缓存行为
        //如果是no-cache或者no-store,不进行缓存
        // 如果是max-age,取出max-age值,
        headerValue = headers.get("Cache-Control");
        if (headerValue != null) {
            hasCacheControl = true;
            String[] tokens = headerValue.split(",");
            for (int i = 0; i < tokens.length; i++) {
                String token = tokens[i].trim();
                if (token.equals("no-cache") || token.equals("no-store")) {
                    return null;
                } else if (token.startsWith("max-age=")) {
                    try {
                        maxAge = Long.parseLong(token.substring(8));
                    } catch (Exception e) {
                    }
                } else if (token.startsWith("stale-while-revalidate=")) {
                    try {
                        staleWhileRevalidate = Long.parseLong(token.substring(23));
                    } catch (Exception e) {
                    }
                } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
                    mustRevalidate = true;
                }
            }
        }

        // 资源的失效期
        headerValue = headers.get("Expires");
        if (headerValue != null) {
            serverExpires = parseDateAsEpoch(headerValue);
        }

        //资源上一次修改的时间
        headerValue = headers.get("Last-Modified");
        if (headerValue != null) {
            lastModified = parseDateAsEpoch(headerValue);
        }

        // 资源唯一标识
        serverEtag = headers.get("ETag");

        // Cache-Control takes precedence over an Expires header, even if both exist and Expires
        // is more restrictive.
        if (hasCacheControl) {
            softExpire = now + maxAge * 1000;
            finalExpire = mustRevalidate
                    ? softExpire
                    : softExpire + staleWhileRevalidate * 1000;
        } else if (serverDate > 0 && serverExpires >= serverDate) {
            // Default semantic for Expire header in HTTP specification is softExpire.
            softExpire = now + (serverExpires - serverDate);
            finalExpire = softExpire;
        }

        Cache.Entry entry = new Cache.Entry();
        entry.data = response.data;
        entry.etag = serverEtag;
        entry.softTtl = softExpire;
        entry.ttl = finalExpire;
        entry.serverDate = serverDate;
        entry.lastModified = lastModified;
        entry.responseHeaders = headers;

        return entry;
    }
}

public interface Cache {
    
    class Entry {
        /** The data returned from cache. */
        public byte[] data;

        /** ETag for cache coherency. */
        public String etag;

        /** Date of this response as reported by the server. */
        public long serverDate;

        /** The last modified date for the requested object. */
        public long lastModified;

        /** TTL for this record. */
        public long ttl;

        /** Soft TTL for this record. */
        public long softTtl;

        /** Immutable response headers as received from server; must be non-null. */
        public Map<String, String> responseHeaders = Collections.emptyMap();

        /** True if the entry is expired. */
        boolean isExpired() {
            return this.ttl < System.currentTimeMillis();
        }

        /** True if a refresh is needed from the original data source. */
        boolean refreshNeeded() {
            return this.softTtl < System.currentTimeMillis();
        }
    }
}

4、取消请求

将对应的request标记为canceled,在进行分发之前会判断,如果当前request已经被取消了,就不进行分发

RequestQueue

    /**
     * Cancels all requests in this queue for which the given filter applies.
     * @param filter The filtering function to use
     */
    public void cancelAll(RequestFilter filter) {
        synchronized (mCurrentRequests) {
            for (Request<?> request : mCurrentRequests) {
                if (filter.apply(request)) {
                    request.cancel();
                }
            }
        }
    }

    /**
     * Cancels all requests in this queue with the given tag. Tag must be non-null
     * and equality is by identity.
     */
    public void cancelAll(final Object tag) {
        if (tag == null) {
            throw new IllegalArgumentException("Cannot cancelAll with a null tag");
        }
        cancelAll(new RequestFilter() {
            @Override
            public boolean apply(Request<?> request) {
                return request.getTag() == tag;
            }
        });
    }

request

    /**
     * Mark this request as canceled.  No callback will be delivered.
     */
    public void cancel() {
        mCanceled = true;
    }

ExecutorDelivery

public class ExecutorDelivery implements ResponseDelivery {
    ...
    /**
     * A Runnable used for delivering network responses to a listener on the
     * main thread.
     */
    private class ResponseDeliveryRunnable implements Runnable {
        private final Request mRequest;
        private final Response mResponse;
        private final Runnable mRunnable;
        ...
        @Override
        public void run() {
            // If this request has canceled, finish it and don't deliver.
            if (mRequest.isCanceled()) {
                mRequest.finish("canceled-at-delivery");
                return;
            }
            ...
       }
    }
}

ExecutorDelivery

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

    public ExecutorDelivery(final Handler handler) {
        mResponsePoster = new Executor() {
            @Override
            public void execute(Runnable command) {
                handler.post(command);
            }
        };
    }

    @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.
     */
    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();
            }
       }
    }
}
5、超时重试机制

1、有关超时重试的一些异常

  • ConnectTimeoutException : 请求超时
    A timeout while connecting to an HTTP server or waiting for an available connection from an HttpConnectionManager.
    连接HTTP服务端超时或者等待HttpConnectionManager返回可用连接超时,俗称请求超时.
  • SocketTimeoutException : 响应超时
    Signals that a timeout has occurred on a socket read or accept.
    Socket通信超时,即从服务端读取数据时超时,俗称响应超时.

Volley就是通过捕捉这两个异常来进行超时重试的.

2、原理
如下图,Network类是一个死循环,只有请求成功或者抛出异常才能退出循环。因此如果捕捉到程序抛出SocketTimeoutException或者ConnectTimeoutException,并不会跳出循环,而是进入到attemptRetryOnException方法。如果attemptRetryOnException方法中没有抛出VolleyError异常,程序再次进入while循环,从而完成超时重试机制.

hurlStack,进行具体的网络请求 Network retry方法

参考:
HTTP请求中的缓存(cache)机制
Google网络框架Volley的使用,Cache-Control=no-cache时强制缓存的处理
Volley在没有网的情况下使用磁盘缓存的数据
Android中关于Volley的使用(八)缓存机制的深入认识volley超时重试机制

4、加载图片Request

  • ImageRequest
    适合请求图片比较少的情况
    ImageRequest imageRequest = new ImageRequest(url, new Response.Listener<Bitmap>() {
        @Override
        public void onResponse(Bitmap response) {
            imageView.setImageBitmap(response);
        }
    }, maxWidth, maxHeight, ImageView.ScaleType.CENTER_CROP, Bitmap.Config.RGB_565, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            imageView.setImageResource(R.drawable.ic_default_img);
        }
    });

注意:
其中maxWidth表示这个bitmap的最大宽度,0表示不设置,使用图片本来的宽度。

还有一种更简单的获取图片的方式:

    ImageLoader  imageLoader=new ImageLoader(queue, new ImageLoader.ImageCache() {
        private final LruCache<String,Bitmap> lruCache=new LruCache<String,Bitmap>(8 * 1024 * 1024){
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes()*value.getHeight();
            }
        };
        @Override
        public Bitmap getBitmap(String url) {
            return lruCache.get(url);
        }
        @Override
        public void putBitmap(String url, Bitmap bitmap) {
            lruCache.put(url,bitmap);
        }
    });
    ImageLoader.ImageListener listener=ImageLoader.getImageListener(imageView, R.drawable.ic_default_img,R.drawable.ic_default_img);
    //maxWidth表示这个bitmap的最大宽度,0表示不设置,使用图片本来的宽度
    imageLoader.get(url,listener,maxWidth,maxHeight);

注意:
1、ImageLoader非常适合加载大量图片的情况,它在正常的硬盘缓存之前使用了内存缓存(传入ImageCache),可以避免内存闪烁,实现了图片缓存的功能,同时还可以过滤重复链接,避免重复发送请求。
2、使用ImageLoader.getImageListener()方法创建一个ImageListener实例后,在imageLoader.get()方法中加入此监听器和图片的url,即可加载网络图片.

当然更简单的一种是使用:NetworkImageView

    <com.android.volley.toolbox.NetworkImageView
        android:id="@+id/networkImageView"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_alignParentBottom="true"/>

---

    ImageLoader imageLoader = new ImageLoader(...);
    networkImageView.setDefaultImageResId(R.drawable.ic_default_img);
    networkImageView.setErrorImageResId(R.drawable.ic_default_img);
    networkImageView.setImageUrl(url, imageLoader);

注意:
NetworkImageView,在加载图片的时候它会自动获取自身的宽高,然后对比网络图片的宽度,再决定是否需要对图片进行压缩。也就是说,压缩过程是在内部完全自动化的,并不需要我们关心,NetworkImageView会始终呈现给我们一张大小刚刚好的网络图片,不会多占用任何一点内存。
当然了,如果你不想对图片进行压缩的话,其实也很简单,只需要在布局文件中把NetworkImageView的layout_width和layout_height都设置成wrap_content就可以了,这样NetworkImageView就会将该图片的原始大小展示出来,不会进行任何压缩。

参考:
Android Volley解析(一)之GET、POST请求篇
Android Volley完全解析
Android Volley框架的几种post提交请求方式
Android 文档

相关文章

  • Volley学习

    Volley学习 参考文章 Android Volley完全解析(一),初识Volley的基本用法Android ...

  • 每日一题:Volley源码问题分析

    每日一题:Volley源码问题分析 学习推荐_Volley源码解析 面试率: ★★★☆☆ 面试提醒 Volley是...

  • Volley学习

    1、基本使用 Volley适合轻量、高并发的网络请求,但如果大数据量的操作,比如上传下载文件,就不太适合了。使用V...

  • Android框架学习笔记04Volley框架

    上一篇中我们学习了Retrofit框架,这一篇我们学习另外一个网络请求框架——Volley。Volley框架是Go...

  • Android:Volley的使用和Volley源码分析

    Volley网络框架 Volley的简单使用 Volley源码分析 }

  • Volley

    Volley地址Volley, Volley是Google开源的一个网络框架 Demo 通过Volley.newR...

  • 1、volley 官方教程-简介、配置

    文章摘要1、Volley 简介2、Volley库配置 英文文献 Github Volley下载地址 Volley是...

  • Volley基础学习

    Volley非常适合去进行数据量不大,但通信频繁的网络操作。Volley提供了如下的便利功能:1.JSON数据和图...

  • 框架学习:Volley

    Volley 原理 架构

  • volley源码学习

    volley源码学习 之前一直对于源码学习抱着一种又爱又恨的心情。爱的是因为知道源码有一些特别好的设计思路,可以让...

网友评论

    本文标题:Volley学习

    本文链接:https://www.haomeiwen.com/subject/ivofettx.html