临近毕业,各种事情各种忙。我也没有认真专注写过博客,最近仔细看了Volley框架的使用及其源码,思前想后,想挑战一下自己,还是写一篇博客来分享,如有错误,欢迎吐槽。
Volley简介
网络请求是一个App很重要的一部分,android系统只是提供了一个平台,而android应用则是基于这个平台上进行展示数据,起到与用户进行交互的作用,数据来源于服务端,而二者之间必须通过互联网进行传输数据,在Android系统发布初期,很多开发者都是在Apache协会的Http协议的基础上进行网络请求方法的封装,当然有小白和大神,难免会出现一些意想不到的问题。谷歌也是考虑到了这一点,在2013年I/O大会上发布了一个基于HttpURLConnection的网络请求框架–Volley。
Volley把AsyncHttpClient和Universal-Image-Loader的优点集于了一身,既可以像AsyncHttpClient一样非常简单地进行HTTP通信,也可以像Universal-Image-Loader一样轻松加载网络上的图片。
Volley适用于请求一些数据量不大且频繁的数据,不适合请求一些大数据。
Volley的使用方法可以看郭神的博客 ,在此我们就直接分析源码了。
Volley源码分析
我们知道Volley所有的请求都是基于Request这个抽象类,我们来看看这个类中的主要方法:
/**
* Creates a new request with the given method (one of the values from {@link Method}),
* URL, and error listener. Note that the normal response listener is not provided here as
* delivery of responses is provided by subclasses, who have a better idea of how to deliver
* an already-parsed response.
*/
public Request(int method, String url, Response.ErrorListener listener) {
mMethod = method;
mUrl = url;
mErrorListener = listener;
setRetryPolicy(new DefaultRetryPolicy());
mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
}
这里Request的构造方法,没什么特别,就是把传入的参数传递给对应的对象,以及设置了一个请求的优先级,这里为默认。
abstract protected Response<T> parseNetworkResponse(NetworkResponse response);
这是Request中最主要的两个方法之一,每个继承的类都必须具体实现这两个方法。parseNetworkResponse方法中response是最后服务端返回的字节码数据之后在子线程中被调用,进行解析数据,我们可以将这个字节码数据转换成我们想要的类型,比如StringRequest中,parseNetworkResponse方法的实现如下:
@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字符串。
abstract protected void deliverResponse(T response);
看着方法名就可以知道,这个是将最后解析过的请求数据交付给主线程,让主线程进行处理,还记得我们在构造函数中传入一个Listener的对象吗,对,就是它。我们可以通过接口回调的方式将最终数据返回给主线程使用。还是一样,我们看看StringRequest中的实现:
@Override
protected void deliverResponse(String response) {
mListener.onResponse(response);
}
Request抽象类中还有一些其他的属性,比如:
public Request<?> setTag(Object tag) {
mTag = tag;
return this;
}
给每个Request附加一个标签,便于管理。
public Request<?> setRetryPolicy(RetryPolicy retryPolicy) {
mRetryPolicy = retryPolicy;
return this;
}
设置一个自定义的在请求失败的情况下重新请求的规则。
public void addMarker(String tag) {
if (MarkerLog.ENABLED) {
mEventLog.add(tag, Thread.currentThread().getId());
} else if (mRequestBirthTime == 0) {
mRequestBirthTime = SystemClock.elapsedRealtime();
}
}
这个方法主要是打印日志的方式展示请求的过程进度。
/**
* Returns the URL of this request.
*/
public String getUrl() {
return mUrl;
}
/**
* Returns the cache key for this request. By default, this is the URL.
*/
public String getCacheKey() {
return getUrl();
}
这里要注意的是把url作为一个缓存的Key。
我们知道我们需要把Request添加到RequestQueue中才能完成网络请求,我们一般通过静态方法
RequestQueue mQueue = Volley.newRequestQueue(MainActivity.this);
创建一个RequestQueue对象,那么我们看看RequestQueue的代码:
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
//获取缓存位置
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
String userAgent = "volley/0";
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
}
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
//sdk版本高于9时,创建一个HrlStack对象,里面是网络请求的具体逻辑
stack = new HurlStack();
} else {
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
//创建一个Network对象,里面进行了网络请求数据方法的二次封装
Network network = new BasicNetwork(stack);
//传入一个缓存对象以及netWork对象
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
//启动队列
queue.start();
return queue;
}
可以看出,在这个方法中,最后在创建RequestQueue的时候,传入了一个缓存对象以及封装好的网络请求方法的对象,最后调用一个start方法,现在我们还没有看它的具体实现,但是我们知道现在是万事俱备,只欠东风了。就像一台榨汁机接通了电源并且开启了开关,我们只需要扔进水果就行了。当然,我们这里不能扔进水果…我们需要做的就是:
requestQueue.add(request);
把Request对象加入进去,让它自己去跑,我们已经在Request中两个抽象方法中写好了解析以及最终数据的交付逻辑。我们来看看RequestQueue.add()
中的代码:
public <T> Request<T> add(Request<T> request) {
// 在此request设置一个标记,指明此request属于的RequestQueue
request.setRequestQueue(this);
synchronized (mCurrentRequests) {
//将请求添加到队列中
mCurrentRequests.add(request);
}
// 处理请求按顺序添加
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");
// 如果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)) {
// 等待缓存的队列中已经有了此请求
Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
if (stagedRequests == null) {
stagedRequests = new LinkedList<Request<?>>();
}
stagedRequests.add(request);
//将新增加的request的集合对象覆盖进去
mWaitingRequests.put(cacheKey, stagedRequests);
if (VolleyLog.DEBUG) {
VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
}
} else {
//如果等待缓存队列中只有一个请求,那么value的值就为null
mWaitingRequests.put(cacheKey, null);
//将request添加到缓存工作队列中
mCacheQueue.add(request);
}
return request;
}
}
可能一部分读者看到这里就有点晕了,怎么这么多的队列。我们一个个解释。
mCurrentRequests,它的对象声明是这样的:
private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();
Set集合中存放的是无序且不重复的子对象。mCurrentRequests 在这里包含了所有我们通过requestQueue.add(request);
方法添加的request,这个集合主要起到管理request的作用,在request请求在NetworkDispatcher线程中处理结束之后,无论请求成功或者失败,都会调用Request的finish方法:
void finish(final String tag) {
if (mRequestQueue != null) {
mRequestQueue.finish(this);
}
很明显,Request中调用了mRequestQueue的finish方法,让我们看下代码:
<T> void finish(Request<T> request) {
// Remove from the set of requests currently being processed.
synchronized (mCurrentRequests) {
mCurrentRequests.remove(request);
}
从这里我们可以看到,在request响应结束之后,我们将request从mCurrentRequests移除,这里我只贴出了部分代码。
mNetworkQueue和mCacheQueue,它们的声明是这样的:
private final PriorityBlockingQueue<Request<?>> mNetworkQueue =new PriorityBlockingQueue<Request<?>>();
private final PriorityBlockingQueue<Request<?>> mCacheQueue =new PriorityBlockingQueue<Request<?>>();
先来说下PriorityBlockingQueue这个类吧,这个类实现了implements BlockingQueue<E>
这个接口,BlockingQueue是jdk中Concurrent包中新增的用于解决了多线程中,如何高效安全“传输”数据的问题,并且它能保证线程安全。所以将mNetworkQueue和mCacheQueue传入子线程中,让子线程从中获取request进行处理。
mWaitingRequests:
private final Map<String, Queue<Request<?>>> mWaitingRequests =
new HashMap<String, Queue<Request<?>>>();
mWaitingRequests是一个Map集合,里面保存着键值对,以request的url为key。
接着往下我们看`queue.start();`这个方法中的代码:
/**
* Starts the dispatchers in this queue.
*/
public void start() {
stop(); // 如果队列在开启状态,关闭
//创建一个缓存分发线程,并且启动它
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
// 创建多个网络请求的线程,并且启动它们
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
NetworkDispatcher和CacheDispatcher都是继承于Thread类。从上面的源码可知,我们将默认启动是4个子线程,这4个子线程一直在跑,执行RequestQueue中的Request。而mCacheDispatcher只开启一个线程,用于缓存响应数据。
接着我们来看在NetworkDispatcher线程中是如何处理请求的:
public class NetworkDispatcher extends Thread {
/** 需要处理的请求队列 */
private final BlockingQueue<Request<?>> mQueue;
/** 封装好了的用来处理网络请求的接口 */
private final Network mNetwork;
/** 用于写入缓存的对象 */
private final Cache mCache;
/** 用来请求之后的接口回调 */
private final ResponseDelivery mDelivery;
/** 用于退出此线程 */
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();
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
private void addTrafficStatsTag(Request<?> request) {
//Api>=14以上调用此方法(if API >= 14)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { TrafficStats.setThreadStatsTag(
request.getTrafficStatsTag());
}
}
@Override
public void run() {
//设置线程为后台线程
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//全真循环,不断获取mQueue中的request进行处理
while (true) {
long startTimeMs = SystemClock.elapsedRealtime();
Request<?> request;
try {
// 获取请求
request = mQueue.take();
} catch (InterruptedException e) {
// 请求超时的时候,结束线程
if (mQuit) {
return;
}
continue;
}
try {
request.addMarker("network-queue-take");
// 请求被取消
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
continue;
}
addTrafficStatsTag(request);
// 执行请求
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
// 如果我们已经递交过同样的响应数据,那么不会再次递交
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
continue;
}
// 在子线程中解析字节码为我们想要的数据类型
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
// request需要缓存,并且response的缓存入口不为空
if (request.shouldCache() && response.cacheEntry != null) {
//以request的url为key,以response的缓存入口为value进行缓存
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
//标志已经递交,请求结束
request.markDelivered();
//接口回调
mDelivery.postResponse(request, response);
} catch (VolleyError volleyError) {
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
parseAndDeliverNetworkError(request, volleyError);
} catch (Exception e) {
VolleyLog.e(e, "Unhandled exception %s", e.toString());
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);
}
}
Volley默认是开启四个NetworkDispatcher这个线程,用于不断从mQueue中获取request进行处理请求。以mNetwork为处理器,并且在获取成功之后回调在request中parseNetworkResponse方法进行解析成我们想要的数据。这个方法是不是很熟悉,没错,它就是我们在自定义Request中所要重新的抽象方法之一。在解析失败时也会有相应的接口回调。
在请求需要进行缓存并且响应数据缓存入口不为空的情况下,我们将以request的url为key,以response的缓存入口为value进行缓存。
最后将解析好的数据通过 mDelivery.postResponse(request, response);
方法抛给主线程处理。
CacheDispatcher是一个缓存线程,让我们看看它的代码实现:
public class CacheDispatcher extends Thread {
private static final boolean DEBUG = VolleyLog.DEBUG;
/** 缓存队列*/
private final BlockingQueue<Request<?>> mCacheQueue;
/** 网络请求队列*/
private final BlockingQueue<Request<?>> mNetworkQueue;
/** 缓存对象 */
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;
}
/**
* 结束线程
*/
public void quit() {
mQuit = true;
interrupt();
}
@Override
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher"); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// 初始化线程对象
mCache.initialize();
//全真循环,不断获取mCacheQueue中的request进行处理
while (true) {
try {
//从mCacheQueue中获取request对象,如果mCacheQueue为空,那么会堵塞暂停,直至mCacheQueue添加了对象
final Request<?> request = mCacheQueue.take();
request.addMarker("cache-queue-take");
// 取消了缓存要求
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}
// 获取缓存的条目
Cache.Entry entry =mCache.get(request.getCacheKey());
if (entry == null) {
request.addMarker("cache-miss");
// 如果缓存中找不到缓存条目,那么把请求扔mNetworkQueue,让它去通过网络请求数据
mNetworkQueue.put(request);
continue;
}
//如果缓存条目过期的话,那么把request加入mNetworkQueue让它去通过网络重新请求数据
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
//有了有效的缓存对象,将缓存对象通过Request中定义的解析方法解析成我们想要的数据类型
request.addMarker("cache-hit");
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
if (!entry.refreshNeeded()) {
// 不需要刷新的数据,可以直接递交给主线程使用了
mDelivery.postResponse(request, response);
} else {
//需要刷新的数据,将它它去通过网络重新请求数据
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
// 因为只是响应数据需要刷新,所以可以把response作为媒介,标记这个为true
response.intermediate = true;
mDelivery.postResponse(request, response, new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(request);
} catch (InterruptedException e) {
// Not much we can do about this.
}
}
});
}
} catch (InterruptedException e) {
if (mQuit) {
return;
}
continue;
}
}
}
}
Volley官网的框架结构流程图:
总结一下:Volley框架中封装了网络请求的具体方法在HurlStack类中,它就相当于一个加载器,BasicNetwork类对它进行了二次封装。CacheDispatcher的目的就是从mCacheQueue中获取request,然后再从缓存中判断是否有对应的缓存,如果没有或者过期等原因,会将此request抛给mNetworkQueue队列,让mNetworkQueue在NetworkDispatcher中,而在NetworkDispatcher中通过获取mNetworkQueue的每个request,使用默认的HurlStack加载器(当然也可以自定义)向服务端请求数据,最后将请求成功的响应数据再进行缓存。
至此。Volley框架的源码算是解析完毕。那么如何在实际开发项目中正确使用它呢,我会在下一篇中叙述。
这是我第一次花这么多心思写博客,花了一下午和一晚上的时间,希望大家能有所收获。