Volley 是 Google 官方推出的一套 Android 网络请求库,特别适用于通信频繁、数据量较小的网络请求。Volley 能够根据当前手机版本选择 HttpClient (2.3 以下) 或者 HttpUrlConnection。

除了 Volley,Android 常用的网络加载库还有 OkHttp,Retrofit 等,关于这几个的区别请移步 stormzhong 的ANDROID开源项目推荐之「网络请求哪家强]

Volley 框架扩展性很强,其源码值得我们好好学习。

先了解一些 Http 协议

Http 协议规范了客户端和服务端数据请求的一套规则。Http 协议规范了 请求(Request)和响应(Response)。

请求包含请求行,请求头(Header),body(可选)。如下图所示(图片来源于网络):


Http Request

请求行必须指定请求方式,常见的有 GET,POST等。

响应和请求类似。也包含三部分,状态行(含响应码),响应头(Header),body(可选)。如下图所示(图片来源于网络):


Http Response

关于 Http 协议大致了解这些即可。

Volley 对 Http Request 和 Response 的封装。

请求封装

Http 请求包含三部分,请求行,请求头,body。Volley 将其封装成 Request,Request 有几个实现类,如 StringRequest,JsonObjectRequest 等。Request 部分源码如下:

public abstract class Request implements Comparable> {    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;    }    // 对应请求头中的 Method    private final int mMethod;    // 对应请求头中的 URL    private final String mUrl;    // 对应请求头    public Map getHeaders() throws AuthFailureError {        return Collections.emptyMap();    }    // 对应请求实体    public byte[] getBody() throws AuthFailureError {        Map params = getParams();        if (params != null && params.size() > 0) {            return encodeParameters(params, getParamsEncoding());        }        return null;    }}

响应封装

Http 响应包含三部分,状态行,响应头,body。
Volley 对 Http 响应用 NetworkResponse 封装,NetworkResponse 部分源码如下:

public class NetworkResponse {    /** 状态行中的响应码 */    public final int statusCode;    /** body */    public final byte[] data;    /** 响应头 */    public final Map headers;    /** True if the server returned a 304 (Not Modified). */    public final boolean notModified;    /** Network roundtrip time in milliseconds. */    public final long networkTimeMs;}

NetworkResponse 直观的表示 Http 响应返回的数据。但是我们的应用程序不能直接使用其中的 body(字节数组),需要解析成某个 Bean 对象,这样程序就可以直接使用 Bean 对象。因此还需要对响应做进一步的封装。显然这个 Bean 的数据类型不可知,可以使用 java 中的泛型。

Volley 对响应进一步封装成 Response,Response 部分源码码如下:

public class Response {      /** Parsed response, or null in the case of error. */    public final T result;    /** Cache metadata for this response, or null in the case of error. */    public final Cache.Entry cacheEntry;    /** Detailed error information if errorCode != OK. */    public final VolleyError error;    }

由 NetworkResponse 到 Response 需要一个方法将 body 解析成某个对象。Volley 将这个方法定义在 Request 中,而且是一个抽象方法。看下 StringRequest 对这个方法的实现:

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

其实就是对返回的字节数组(body)解析成某个对象,StringRequest 则解析成 String,JsonObjectRequest 则解析成 JsonObject 等。我们可以定义一个自己的 Request,在 parseNetworkResponse 方法中将字节数组转成 String,再用 Gson 解析成我们的对象。

发起一个请求

Volley 的简单使用如下:

RequestQueue mQueue = Volley.newRequestQueue(context);StringRequest stringRequest = new StringRequest("http://www.baidu.com",        new Response.Listener() {            @Override            public void onResponse(String response) {                Log.d("TAG", response);            }        }, new Response.ErrorListener() {            @Override            public void onErrorResponse(VolleyError error) {                Log.e("TAG", error.getMessage(), error);            }        });mQueue.add(stringRequest);

Volley.newRequestQueue(context) 会创建一个 RequestQueue,并调用RequestQueue #start()

RequestQueue 表示请求队列,是 Volley 框架的核心类。RequestQueue 包含一个网络队列 mNetworkQueue 和一个缓存队列 mCacheQueue ,作为其成员变量。

RequestQueue 部分源码如下:

public class RequestQueue {    /** 缓存队列 */    private final PriorityBlockingQueue> mCacheQueue =        new PriorityBlockingQueue>();    /** 网络队列 */    private final PriorityBlockingQueue> mNetworkQueue =        new PriorityBlockingQueue>();              public void start() {        stop();  // Make sure any currently running dispatchers are stopped.        // Create the cache dispatcher and start it.        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);        mCacheDispatcher.start();        // Create network dispatchers (and corresponding threads) up to the pool size.        for (int i = 0; i < mDispatchers.length; i++) {            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,                    mCache, mDelivery);            mDispatchers[i] = networkDispatcher;            networkDispatcher.start();        }    }}

在 start 方法中,开启一个缓存调度线程 CacheDispatcher,用来处理缓存。默认开启四个网络调度线程 NetworkDispatcher,用来处理网络请求。CacheDispatcher 不断的从 mCacheQueue 中取走 Request 并进行分发处理。NetworkDispatcher 不断的从 mNetworkQueue 取走 Request 并进行分发处理。如果队列为空,则阻塞等等,这些线程都是常驻内存随时待命的。显然一个程序中最好只能有一个 RequestQueue,如果采用多个,应该在适当的时候调用 RequestQueue#stop()销毁线程释放内存。

当我们向 RequestQueue 添加一个 Request 时,如果 Request 可缓存则添加到 mCacheQueue ,否则添加到 mNetworkQueue 。CacheDispatcher 拿到这个 Request,如果缓存存在并且还没过期,则解析数据并提及给主线程,否则添加到 mNetworkQueue 交给 NetworkDispatcher 处理。流程如下:


这里写图片描述

缓存如何处理?

app 网络数据缓存

Cache 的实现类为 DiskBasedCache。DiskBasedCache 采用磁盘缓存和内存缓存,但两者缓存的数据不一样。内存缓存只缓存 CacheHeader,而磁盘缓存的是 Entry,只不过是将 Entry 中的数据按一定规则写到文件中,读取缓存时再按照同样的规则读取到 Entry 中。另外,Entry 比 CacheHeader 多了一个字节数组,显然这是比较占内存的,因此内存缓存并没有缓存 Entry。

当缓存满了之后如何处理呢?

DiskBasedCache 中有个方法是pruneIfNeeded(int neededSpace),每次执行 put 的时都会先调用该方法。这个方法就会删除较早的缓存。内存缓存保存在 mEntries 中。我们看下这个成员变量:

    /** Map of the Key, CacheHeader pairs */    private final Map mEntries =            new LinkedHashMap(16, .75f, true);

LinkedHashMap 有个构造函数为 LinkedHashMap( int initialCapacity, float loadFactor, boolean accessOrder)最后一个参数 accessOrder 就表示是否按照访问顺序排列。当 accessOrder 为 true,最后执行 get 或者 put 的元素会在 LinkedHashMap 的尾部。

这样 pruneIfNeeded 方法就很容易找到较早的缓存并将其删除。

服务端缓存

当客户端发起一个 Http 请求,如果服务端返回 304,表示请求的资源缓存仍能有效。这样就能减少数据传输。当然,这种方式需要客户端携带额外的头信息,Volley 已经帮我们做了这部分。直接看相应的源码:

public class BasicNetwork implements Network {    @Override    public NetworkResponse performRequest(Request<?> request) throws VolleyError {        // 省略...        Map headers = new HashMap();        addCacheHeaders(headers, request.getCacheEntry());        // before request        httpResponse = mHttpStack.performRequest(request, headers);        StatusLine statusLine = httpResponse.getStatusLine();        int statusCode = statusLine.getStatusCode();        responseHeaders = convertHeaders(httpResponse.getAllHeaders());        // Http 304        if (statusCode == HttpStatus.SC_NOT_MODIFIED) {        }    }    private void addCacheHeaders(Map headers, Cache.Entry entry) {        // If there's no cache entry, we're done.        if (entry == null) {            return;        }        if (entry.etag != null) {            headers.put("If-None-Match", entry.etag);        }        if (entry.lastModified > 0) {            Date refTime = new Date(entry.lastModified);            headers.put("If-Modified-Since", DateUtils.formatDate(refTime));        }    }}

从上可以看到 Volley 在请求头添加了一个 etag 和 lastModified,这些数据来自上次请求的响应头中,Volley对其做了缓存,并且在下一次请求时添加到请求头中。这样服务端就能比较客户端发送的 etag 和自己的 etag,如果相等,说明请求的资源未发生变化,服务端返回304。客户端则对响应码做判断,如果为 304,说明本地缓存有效。

主线程回调

CacheDispatcher 和 NetworkDispatcher 都是运行在非主线程当中的,而我们的 UI 必须在主线程中更新。Volley 采用的是 Handler 来通知主线程更新 UI。

Request 中有一个 deliverResponse 和 deliverError,一个是成功回调,另一个是失败回调。那么这两个方法是什么时候被执行的呢?

看下 NetworkDispatcher 部分源码(省略部分代码)

public class NetworkDispatcher extends Thread {    public void run() {        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);        while (true) {            try {                request = mQueue.take();            } catch (InterruptedException e) {            }            try{                // 发起网络请求                NetworkResponse networkResponse = mNetwork.performRequest(request);                // 解析                Response<?> response = request.parseNetworkResponse(networkResponse);                // 写缓存                if (request.shouldCache() && response.cacheEntry != null) {                    mCache.put(request.getCacheKey(), response.cacheEntry);                }                // 分发结果,通知主线程                mDelivery.postResponse(request, response);            } catch (VolleyError volleyError) {                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);            }        }    }}

和主线程相关的就是 mDelivery.postResponse(request, response); 和 mDelivery.postError(request, volleyError)

我们看下 NetworkDispatcher 中 mDelivery 是如何创建的。看下 NetworkDispatcher 源码发现是在构造函数,于是去 RequestQueue 找 NetworkDispatcher 对象的创建过程。在 start 方法中会创建 NetworkDispatcher ,并传入一个 mDelivery 对象,而 mDelivery 在 RequestQueue 的构造函数中已经完成了初始化,看下相关源码:

    public RequestQueue(Cache cache, Network network, int threadPoolSize) {        this(cache, network, threadPoolSize,                new ExecutorDelivery(new Handler(Looper.getMainLooper())));    }    public RequestQueue(Cache cache, Network network, int threadPoolSize,            ResponseDelivery delivery) {        mCache = cache;        mNetwork = network;        mDispatchers = new NetworkDispatcher[threadPoolSize];        mDelivery = delivery;    }

由此可见在 NetworkDispatcher 中 mDelivery 的实际类型是 ExecutorDelivery。ExecutorDelivery 的构造函数接收一个 Handler 用来往主线程发消息。

看下 ExecutorDelivery 部分源码:

public class ExecutorDelivery implements ResponseDelivery {    /** Used for posting responses, typically to the main thread. */    private final Executor mResponsePoster;    public ExecutorDelivery(final Handler handler) {        // Make an Executor that just wraps the handler.        mResponsePoster = new Executor() {            @Override            public void execute(Runnable command) {                handler.post(command);            }        };    }    @Override    public void postResponse(Request<?> request, Response<?> response) {        postResponse(request, response, null);    }    @Override    public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {        request.markDelivered();        request.addMarker("post-response");        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));    }    @Override    public void postError(Request<?> request, VolleyError error) {        request.addMarker("post-error");        Response<?> response = Response.error(error);        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null));    }}

postResponse 和 postError 最终都会提交 一个 Runnable 给 mResponsePoster 执行,而 mResponsePoster 则将这个 Runnable 提交给 Handler 去执行。Handler 接收到 Runnable 之后最终会执行 mRequest.deliverResponse(mResponse.result) 或者 mRequest.deliverError(mResponse.error)完成主线程的回调。

小结

关于 Volley 的源码大致先解读这些,重点在于理顺整个逻辑,其他的像 HttpClient,HttpUrlConnection 的网络操作建议直接阅读源码。

更多相关文章

  1. Android(安卓)ImageLoader(Android-Universal-Image-Loader)【1】
  2. Android开发–Intent-filter属性详解
  3. Android以请求参数来获取网络数据
  4. 使用RxAndroid处理异步任务
  5. Android调用WebService系列之请求调用
  6. Android异步加载访问网络图片-解析json
  7. fiddle android
  8. android中OkHttp的导入和get、post请求的简单教程
  9. Android——Retrofit2

随机推荐

  1. Android与WebWiew的同步和异步访问机制
  2. Android App模块化及工程扩展
  3. Android系统framework的base目录下编译生
  4. Android lrucache 实现与使用(Android内存
  5. android应用程序图标
  6. Android 密度转换
  7. Android仿人人客户端(v5.7.1)——对从服务
  8. android调用webservice方法,参数或返回值
  9. Android Studio:Android Studio 配置
  10. android 反编译 apk 分享 smali2java 1.0