Volley是Google推出的异步网络请求和异步图片加载框架,Volley适合数据量小网络请求频繁的操作。
Volley的特点:
1,代码扩展性强,很多类都是基于接口设计的,自己可以重写
2,内部做了缓存处理,可以重写缓存策略
3,网络请求引擎主要用HttpURLConnection、HttpClient
4,可以实现小文件和图片加载
Android Developer上google对于volley框架的核心流程图
对于这个图大致解释下流程吧
Request添加到RequestQueue中,RequestQueue内部维护了两个线程调度一个缓存线程CacheDispatcher,对request请求在缓存中查找Response,如果有结果就解析成相应的数据类型然后通过ResponseDelivery将结果派发到主线程中,如果缓存中没有Response,那么通过NetworkDispatcher线程通过httpstack这个引擎去服务器请求数据,得到结果后进行解析,并且将结果写入到缓存中,最后通过ResponseDelivery将信息派发到主线程中。
接下来就直接上代码吧,跟着代码走可能比较好,先介绍一下volley的基本用法。
RequestQueue mQueue=Volley.newRequestQueue(context);
StringRequest request=new StringRequest(url, new Response.Listener<String>() {
@Override
public void onResponse(String s) {
//todo
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
//todo
}
});
mQueue.add(request);
这个就是Volley的最基本用法,是不是特别简单,在回调接口Listener中对结果做相应的业务处理就行了。虽然看似简单的几行代码,实际上这中间的过程还是挺复杂的,接下来就开始从源码角度慢慢深入去学习Volley的整个过程了。
RequestQueue mQueue=Volley.newRequestQueue(context);
接下来看Volley.java中的源码
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, (HttpStack)null);
}
继续调用下面的重载构造函数
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
File cacheDir = new File(context.getCacheDir(), "volley");
String userAgent = "volley/0";
try {
String network = context.getPackageName();
PackageInfo queue = context.getPackageManager().getPackageInfo(network, 0);
userAgent = network + "/" + queue.versionCode;
} catch (NameNotFoundException var6) {
;
}
if(stack == null) {
if(VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
BasicNetwork network1 = new BasicNetwork((HttpStack)stack);
RequestQueue queue1 = new RequestQueue(new DiskBasedCache(cacheDir), network1);
queue1.start();
return queue1;
}
构造RequestQueue的时候传进去了一个context,默认会将HttpStack设置为null,然后会构建一个缓存文件,路径是getCacheDir(),接下来根据Android版本构造httpstack,HurlStack,HttpClientStack,具体可以去看源码,前面那个是用HttpURLConnection,后面那个用HttpClient,然后将httpstack封装到BasicNetwork中,BasicNetwork是个Network,来看下Network这个接口,因为这个很重要,是做网络请求用的
Network.java interface
public interface Network {
NetworkResponse performRequest(Request<?> var1) throws VolleyError;
}
实现这个接口的类都要实现performRequest这个方法,看名字就知道根据request去服务器请求数据,返回NetworkResponse
后面会说到这个网络请求类的,现在继续往下看,接下来就构造一个RequestQueue了,将刚才构造的Network传到构造函数中
RequestQueue queue1 = new RequestQueue(new DiskBasedCache(cacheDir), network1);
queue1.start();
看这两句代码,queue的构造函数中有个DiskBasedCache实例,看名字应该知道这是个本地缓存的类,看源码吧
DiskBasedCache.java
这个类是实现Cache接口的,而Cache接口内部维护了一个静态内部类Entry
看下Cache接口里面的Entry内部类吧
public static class Entry {
public byte[] data;
public String etag;
public long serverDate;
public long ttl;
public long softTtl;
public Map<String, String> responseHeaders = Collections.emptyMap();
。。。。
}
这里的data实际上就存了Response的数据,这里是byte类型。
接下来看下DiskBaseeCache类的构造函数吧,
public DiskBasedCache(File rootDirectory) {
this(rootDirectory, 5242880);
}
public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
this.mEntries = new LinkedHashMap(16, 0.75F, true);
this.mTotalSize = 0L;
this.mRootDirectory = rootDirectory;
this.mMaxCacheSizeInBytes = maxCacheSizeInBytes;
}
这里说明了缓存文件大小默认是5M
接下来看下RequestQueue的构造函数
RequestQueue.java
public RequestQueue(Cache cache, Network network) {
this(cache, network, 4);
}
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) {
this.mSequenceGenerator = new AtomicInteger();
this.mWaitingRequests = new HashMap();
this.mCurrentRequests = new HashSet();
this.mCacheQueue = new PriorityBlockingQueue();
this.mNetworkQueue = new PriorityBlockingQueue();
this.mCache = cache;
this.mNetwork = network;
this.mDispatchers = new NetworkDispatcher[threadPoolSize];
this.mDelivery = delivery;
}
RequestQueue构造函数指定了线程池大小为4,构造了两个队列,mCacheQueue缓存队列,mNetworkQueue网络请求队列,mCache就是之前那个DiskBasedCache实例,同时有个很重要的NetworkDispatcher类型的数组,这个NetworkDispatcher继承了Thread,所以他是个线程,不断从网络请求队列中获取request进行网络请求,如果队列为空就阻塞等待,这个线程很重要,后面会具体分析他是怎么工作的,mDelivery就是一个Response消息派发类,可以去看它的源码,里面有个handler,可以通过这个handler将消息派发到主线程的Runnable的run方法里处理。构造好RequestQueue后看下面的代码,就是那句queue1.start();我们继续RequestQueue的start函数吧
public void start() {
this.stop();
this.mCacheDispatcher = new CacheDispatcher(this.mCacheQueue, this.mNetworkQueue, this.mCache, this.mDelivery);
this.mCacheDispatcher.start();
for(int i = 0; i < this.mDispatchers.length; ++i) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(this.mNetworkQueue, this.mNetwork, this.mCache, this.mDelivery);
this.mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
这个start函数首先去执行stop函数,去停止正在执行的线程,主要有缓存线程和网络请求线程,看源码
public void stop() {
if(this.mCacheDispatcher != null) {
this.mCacheDispatcher.quit();
}
for(int i = 0; i < this.mDispatchers.length; ++i) {
if(this.mDispatchers[i] != null) {
this.mDispatchers[i].quit();
}
}
}
start函数主要做的事情其实很简单,就是将一些必要的信息传进到CacheDispatcher和NetworkDispatcher这两个线程调度类里面,然后start这两个线程去执行request的请求数据操作。这个RequestQueue实例完成后,就等着add进来request了,所以接下来就new一个request然后加到这个RequestQueue里面去。
接下来看下request的构造了,Volley提供了Request这个抽象类,Volley里面的请求都要是实现了Request这个抽象类才行,框架给我们实现了StringRequest,JsonRequest,ImageRequest等等,具体可以看源码,当然可以自己继承Request重写一些方法。这里暂时以StringRequest为例子,new StringRequest的时候传入了一个url和回调接口,具体源码后面我们再看,这里我们只要知道这是StringRequest类型请求内部有个回调接口就行,然后执行mQueue.add(request);这一步,我们看源码
public Request add(Request request) {
request.setRequestQueue(this);
Set var2 = this.mCurrentRequests;
synchronized(this.mCurrentRequests) {
this.mCurrentRequests.add(request);
}
request.setSequence(this.getSequenceNumber());
request.addMarker("add-to-queue");
if(!request.shouldCache()) {
this.mNetworkQueue.add(request);
return request;
} else {
Map var7 = this.mWaitingRequests;
synchronized(this.mWaitingRequests) {
String cacheKey = request.getCacheKey();
if(this.mWaitingRequests.containsKey(cacheKey)) {
Object stagedRequests = (Queue)this.mWaitingRequests.get(cacheKey);
if(stagedRequests == null) {
stagedRequests = new LinkedList();
}
((Queue)stagedRequests).add(request);
this.mWaitingRequests.put(cacheKey, stagedRequests);
if(VolleyLog.DEBUG) {
VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", new Object[]{cacheKey});
}
} else {
this.mWaitingRequests.put(cacheKey, (Object)null);
this.mCacheQueue.add(request);
}
return request;
}
}
}
这个代码有点长,我们慢慢分析,对将当前的requesQueue设置到request中去,将当前的request添加到mCurrentRequest里,这个mCurrentRequst是个set集合,保存当前的request用的。然后这里有句判断语句
if(!request.shouldCache()) {
this.mNetworkQueue.add(request);
return request;
} else {。。。}
就是说如果request不需要缓存那么就直接将这个request添加到mNetworkQueue队列里面,这个mNetworkQueue是个AbstractQueue类型,线程NetworkDispatcher就是从这个队列取request的。这里我们不执行if里面的语句,因为request的shouldCache方法默认返回的true,所以执行else里面的代码,我们看else里面的源码。这个逻辑其实很简单,mWaitingRequests是个hashmap,key=url,value是个list类型,list里面存的就是request,如果mWaitingRequests里面有request对于的url且list为空表示当前request已经放入请求队列但是还没有finish完成,如果list不为空说明当前request还没有放入请求队列且重复请求该request了,如果mWaitingRequests里面没有该request的url说明这是第一次请求,直接将request放入线程调度的那个队列里面随时等待线程获取request。那么线程是如何获取队列里面的request然后又如何进行request请求,最后又是如何将结果返回给当初request里面设置的回调接口呢,接下来的是最最最核心的东西了。我们在前面的生成RequestQueue的时候调用了RequestQueue的start方法,还记得吗,里面就是启动CacheDispatcher和NetworkDispatcher这两个线程,我们先以CacheDispatcher这个线程为例,看下他的源码
CacheDispatcher.java
我们知道Thread.start()方法后回去执行线程的run()方法里面的程序,所以我们直接看这个类的run方法就行了
public void run() {
...省略一些代码了
Process.setThreadPriority(10);
this.mCache.initialize();
/*这里的mCache就是我们之前在new RequestQueue的时候传的
DiskBasedCache实例,初始化获取缓存文件的一些头部信息
以便后面用到,比如是否已经过期之类的*/
下面的代码比较多省略了一些主要看关键的代码
....省略很多代码
while(true) {
final Request e = (Request)this.mCacheQueue.take();
...
if(e.isCanceled()) {
e.finish("cache-discard-canceled");}
else {
Entry entry = this.mCache.get(e.getCacheKey());
if(entry == null) {
e.addMarker("cache-miss");
this.mNetworkQueue.put(e);
}
else if(entry.isExpired()) {
e.addMarker("cache-hit-expired");
e.setCacheEntry(entry);
this.mNetworkQueue.put(e); }
else {
e.addMarker("cache-hit");
Response response =e.parseNetworkResponse(new NetworkResponse(entry.data, entry.responseHeaders));
e.addMarker("cache-hit-parsed");
if(entry.refreshNeeded()) {
e.addMarker("cache-hit-refresh-needed");
e.setCacheEntry(entry);
response.intermediate = true;
this.mDelivery.postResponse(e, response, new Runnable() {
public void run() {
try {
CacheDispatcher.this.mNetworkQueue.put(e);
} catch (InterruptedException var2) {}
}
});else {
this.mDelivery.postResponse(e, response);
}
}
}
}
看上面的if else代码确实有点头疼,但是逻辑思路其实不复杂的。从mCacheQueue中取出request,然后判断该request是否取消了,如果取消了那么就会去调用RequestQueue的finish方法,该方法就是从mCurrentRequests删除,然后再去mWaitingRequests中查下该还有没有该url对应的request,如果有的就再次把这个request请求添加到mCacheQueue中,准备再次请求,所以就是说request 的取消操作只是针对该request而已。如果没有取消request那么就会先从缓存文件夹里面去获取是否存在该request的response,前面说了response被封装在Entry这个类里面,Cache里面有个内部类Entry用来存response的头部信息和主要信息的,如果这个entry为空说明缓存里面是没有的,那么就将该request添加到mNetworkQueue队列里面,让NetworkDispatcher线程去执行网络请求操作了,如果entry不为空,那么说明缓存里面有该request的response,那么这个时候就判断该response是否过期了,这个过期判断一般是个时间判断,默认的是根据头部信息的max-Age的值乘以1000s来算的,如果过期了那么也将request放到mNetworkQueue里面去执行网络请求操作,如果没有过期那么就会使用缓存里面的response,执行下面的关键代码
Response response = e.parseNetworkResponse(new NetworkResponse(entry.data, entry.responseHeaders));
调用request里面的parseNetworkResponse方法,这个方法作用是将NetworkResponse类型转成Response类型,NetworkResponse类型就是将response的data和头部信息封装起来了,我们看下StringRequest的相关方法
StringRequest.java
protected Response<String> parseNetworkResponse(NetworkResponse response) {
String parsed;
try {
parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
} catch (UnsupportedEncodingException var4) {
parsed = new String(response.data);
}
return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}
public static <T> Response<T> success(T result, Entry cacheEntry) {
return new Response(result, cacheEntry);
}
public static <T> Response<T> error(VolleyError error) {
return new Response(error);
}
private Response(T result, Entry cacheEntry) {
this.result = result;
this.cacheEntry = cacheEntry;
this.error = null;
}
通过StringRequest的解析Response之后返回一个Response类型的实例,里面存了我们所需要的数据类型T和数据的头部信息,而这个数据类型T可以自己定义比如String,Json,Image,XML等等,目前我们是String类型作为例子的。我们继续关注CacheDispatcher类run里面的方法,得到response之后,判断entry是否需要更新,如果需要更新那么将entry添加到当前request中去,更新的意思就是说目前的response暂时用缓存里面的response,但是可能需要去做网络请求从服务器上重新获得信息,着重看下这个代码
this.mDelivery.postResponse(e, response, new Runnable() {
public void run() {
try { CacheDispatcher.this.mNetworkQueue.put(e);
} catch (InterruptedException var2) {} }
});
mDelivery这个类型还记得吗,是ResponseDelivery类型,做response消息派发的,这个是在我们new一个RequestQueue的构造函数的时候实例化的,且传进去了一个拥有主线程looper的handler,这就说明这个类使会将数据post到主线程的message队列里面,在主线程完成回调接口的,这也符合当时我们new StringRequest的时候new的一个回调接口确实在主线程啊。我们看下实现了ResponseDelivery接口的ExecutorDelivery的postResponse方法源码
ExecutorDelivery.java
/*先看下ExecutorDelivery的构造函数mResponsePoster 是个Executor类型,重写了execute方法,执行handler的post方法,这个handler我之前博客里面说过,将runnable类型对象发到message队列,然后looper取出来执行runnable对象的run方法*/
public ExecutorDelivery(final Handler handler) {
this.mResponsePoster = new Executor() {
public void execute(Runnable command) {
handler.post(command);
}
};
}
public void postResponse(Request<?> request, Response<?> response) {
this.postResponse(request, response, (Runnable)null);
}
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
request.markDelivered();
request.addMarker("post-response");
this.mResponsePoster.execute(new ExecutorDelivery.ResponseDeliveryRunnable(request, response, runnable));
}
/*我们看下ResponseDeliveryRunnable这个内部类的定义*/
private class ResponseDeliveryRunnable implements Runnable {
....省略一些代码关键看run这个方法
public void run() {
if(this.mRequest.isCanceled()) {
this.mRequest.finish("canceled-at-delivery");
} else {
if(this.mResponse.isSuccess()) {
this.mRequest.deliverResponse(this.mResponse.result);
} else {
this.mRequest.deliverError(this.mResponse.error);
}
if(this.mResponse.intermediate) {
this.mRequest.addMarker("intermediate-response");
} else {
this.mRequest.finish("done");
}
if(this.mRunnable != null) {
this.mRunnable.run();
}
}
}
}
上面的代码贴出来是更好的看,我继续从CacheDispatcher里面说到的entry需要更新开始,然后它就会调用mDelivery.postResponse这个方法,传进去了request,response,还有一个runnable,接着会调用ExecutorDelivery类里面的Executor类型的mResponsePoster的execute方法,就会将ResponseDeliveryRunnable 类型的对象作为参数通过handler的post方法传到主线程,接下来在主线程中通过looper获取该message,找到该handler然后执行handler里面的runnable类型对象的run方法了,这里就会在主线程执行ResponseDeliveryRunnable 的run里面的代码了,看下面代码
/*如果response成功了,就执行request的deliverResponse方法,将response的result类型是个泛型T,跟我们请求类型有关,这里是String,可以去看Response源码,不成功就执行deliverError方法,逻辑比较简单*/
if(this.mResponse.isSuccess()) {
this.mRequest.deliverResponse(this.mResponse.result);
} else {
this.mRequest.deliverError(this.mResponse.error);
}
/*因为deliverError这个方法是在Request里面的,我们先去看下request的方法你就很清楚了*/
StringRequest.java
protected void deliverResponse(String response) {
this.mListener.onResponse(response);//是不是很眼熟,就是我们在构建StringRequest的时候new的回调接口
}
Request.java
public void deliverError(VolleyError error) {
if(this.mErrorListener != null) {
this.mErrorListener.onErrorResponse(error);//这个也是设置的回调接口啊
}
}
这样代码就会执行到我们在一开始的时候设置的回调接口中了,我们可以将response取出来做相应的逻辑处理了。我们再回过头看ResponseDeliveryRunnable 这个类的run方法最后还有一句 if(this.mRunnable != null) {
this.mRunnable.run();
}
这个mRunnable就是postResponse的时候的第三个参数,如果设置了就要执行该参数的run方法,不设置就不管了,而刚才调这里的时候是传了一个参数的,当时的代码
this.mDelivery.postResponse(e, response, new Runnable() {
public void run() { try { CacheDispatcher.this.mNetworkQueue.put(e);}
catch (InterruptedException var2) {}
}});
这里还要去执行CacheDispatcher.this.mNetworkQueue.put(e)这句
将request添加进mNetworkQueue队列取做网络请求操作。
这样关于一个request加入到一个RequestQueue然后从缓存文件中找到response然后进行解析成Response类型,最后通过ResponseDelivery类将response派发到主线程,调用回调接口进行我们的逻辑处理,这个整个过程已经讲完了,这里只讲了缓存线程的过程,还没有涉及到网络线程的东西,下一篇着重讲解NetworkDispatcher和httpstack的工作原理