Android 网络框架 Volley 源码解析

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

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

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

先了解一些 Http 协议

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

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


Android 网络框架 Volley 源码解析_第1张图片
Http Request

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

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


Android 网络框架 Volley 源码解析_第2张图片
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 处理。流程如下:


Android 网络框架 Volley 源码解析_第3张图片
这里写图片描述

缓存如何处理?

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 的网络操作建议直接阅读源码。

你可能感兴趣的:(Android 网络框架 Volley 源码解析)