转载请标明出处:【顾林海的博客】
个人开发的微信小程序,目前功能是书籍推荐,后续会完善一些新功能,希望大家多多支持!
Volley是Google推出的网络请求库,包含的特性有JSON、图像等的异步下载、网络请求的排序(scheduling)、网络请求的优先级处理、缓存、多级别取消请求、和Activity和生命周期的联动(Activity结束时同时取消所有网络请求),文章会先将Volley的基本使用,最后会从全局者的角度讲解Volley框架的具体流程以及缓存的相关知识。
StringRequest是Request的子类,用于向服务器请求字符串的操作,定义StringRequest之前需要定义请求队列RequestQueue,RequestQueue内部会保存所有的请求,并以相应的算法并发的执行,因此RequestQueue全局定义一个就可以了,避免资源的消耗。这里我把RequestQueue的初始化放在Application中。
public class MyApplication extends Application {
//Volley的全局请求队列
public static RequestQueue sRequestQueue;
/**
* @return Volley全局请求队列
*/
public static RequestQueue getRequestQueue() {
return sRequestQueue;
}
@Override
public void onCreate() {
super.onCreate();
//实例化Volley全局请求队列
sRequestQueue = Volley.newRequestQueue(getApplicationContext());
}
}
public class MainActivity extends AppCompatActivity {
StringRequest request;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
request = new StringRequest(Request.Method.GET, "http://www.sojson.com/open/api/weather/json.shtml?city=北京",
new Response.Listener() {
@Override
public void onResponse(String s) {
Logger.json(s);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
}
});
}
public void getWeather(View view) {
MyApplication.getRequestQueue().add(request);
}
}
request = new StringRequest(Request.Method.POST, "url",
new Response.Listener() {
@Override
public void onResponse(String s) {
Logger.json(s);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
}
}) {
@Override
protected Map getParams() throws AuthFailureError {
HashMap params = new HashMap<>();
params.put("params", "value");
return params;
}
};
public class GsonRequest extends Request {
private final Response.Listener mListener;
private Gson mGson;
private Class mClass;
public GsonRequest(int method, String url, Class clazz, Response.Listener listener,
Response.ErrorListener errorListener) {
super(method, url, errorListener);
mGson = new Gson();
mClass = clazz;
mListener = listener;
}
public GsonRequest(String url, Class clazz, Response.Listener listener,
Response.ErrorListener errorListener) {
this(Method.GET, url, clazz, listener, errorListener);
}
@Override
protected Response parseNetworkResponse(NetworkResponse response) {
try {
String jsonString = new String(response.data,
HttpHeaderParser.parseCharset(response.headers));
return Response.success(mGson.fromJson(jsonString, mClass),
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
}
}
@Override
protected void deliverResponse(T response) {
mListener.onResponse(response);
}
}
request = new GsonRequest(Request.Method.GET, "http://www.sojson.com/open/api/weather/json.shtml?city=北京", WeatherResp.class,
new Response.Listener() {
@Override
public void onResponse(WeatherResp response) {
Toast.makeText(MainActivity.this, response.city, Toast.LENGTH_SHORT).show();
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
}
});
com.android.volley.RequestQueue:
/**
* 重复请求集合(当前请求需要缓存数据时,如果当前mWaitingRequests集合中已经存在该请求,
* 就将此次请求添加到mWaitingRequests中key为此次请求地址的队列中,等待下次请求)
*/
private final Map>> mWaitingRequests =
new HashMap>>();
/**
* 当前请求的队列
*/
private final Set> mCurrentRequests = new HashSet>();
/**
* 缓存队列(存放在该队列中,优先执行缓存调度线程)
* 无界的阻塞队列,按任务优先级
*/
private final PriorityBlockingQueue> mCacheQueue =
new PriorityBlockingQueue>();
/**
* 网络队列
*/
private final PriorityBlockingQueue> mNetworkQueue =
new PriorityBlockingQueue>();
(HashMap>>)
,以请求(Request)的url为key,value是一个队列,内部存放需要缓存数据的请求,并且该请求已经被在缓存队列中。打个比方,我们创建了一个需要缓存数据的Request,第一次添加时会被存放到缓存队列中并交由缓存调度线程执行,如果此次请求没有结束,后续请求同一个url的Request会被存放到mWaitingRequest中以该url为key的队列中等待下一次执行。PriorityBlockingQueue>
,队列中存放的Request类必须实现Comparable接口,并通过该接口的copmareTo方法对缓存队列进行排序(按优先级排序,如果优先级相同,按序列号排序)。add(Request request)
方法添加。com.android.volley.Volley:
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
//缓存目录
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
//AndroidHttpClient实例时的http请求消息头
String userAgent = "volley/0";
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode;//packageName/version
} catch (NameNotFoundException e) {
}
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {//2.3.2
stack = new HurlStack();//HttpURLConnection实现类
} else {
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));//HttpClient实现类
}
}
Network network = new BasicNetwork(stack);
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}
/**
* 线程数
*/
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
/**
* 网络调度线程组
*/
private NetworkDispatcher[] mDispatchers;
/**
* 缓存调度线程
*/
private CacheDispatcher mCacheDispatcher;
public interface ResponseDelivery {
public void postResponse(Request> request, Response> response);
public void postResponse(Request> request, Response> response, Runnable runnable);
public void postError(Request> request, VolleyError error);
}
com.android.volley.ExecutorDelivery:
private final Executor mResponsePoster;
@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));
}
com.android.volley.ExecutorDelivery:
private final Executor mResponsePoster;
public ExecutorDelivery(final Handler handler) {
mResponsePoster = new Executor() {
@Override
public void execute(Runnable command) {
handler.post(command);
}
};
}
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;//null
}
@SuppressWarnings("unchecked")
@Override
public void run() {
if (mRequest.isCanceled()) {
mRequest.finish("canceled-at-delivery");
return;
}
//响应结果
if (mResponse.isSuccess()) {
//通过Request的deliverResponse方法发请求结果
mRequest.deliverResponse(mResponse.result);
} else {
//通过Request的deliverError方法分发请求结果
mRequest.deliverError(mResponse.error);
}
//判断当前Request是否结束
if (mResponse.intermediate) {
mRequest.addMarker("intermediate-response");
} else {
mRequest.finish("done");
}
if (mRunnable != null) {
mRunnable.run();
}
}
}
com.android.volley.toolbox.StringRequest:
@Override
protected void deliverResponse(String response) {
mListener.onResponse(response);
}
com.android.volley.Request:
public void deliverError(VolleyError error) {
if (mErrorListener != null) {
mErrorListener.onErrorResponse(error);
}
}
com.android.volley.CacheDispatcher:
run()方法:
while (true) {
try {
//1、从请求队列中获取一个Request
final Request> request = mCacheQueue.take();
request.addMarker("cache-queue-take");
//2、判断当前Request是否被设置为取消状态
if (request.isCanceled()) {
//2.1结束请求
request.finish("cache-discard-canceled");
continue;
}
//3、从磁盘中获取缓存
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
//3.1、本地缓存空,将Request添加到网络请求队列中,重新执行网络请求
request.addMarker("cache-miss");
mNetworkQueue.put(request);
continue;
}
//4、判断缓存是否过期(根据服务器响应头中获取设置的)
if (entry.isExpired()) {
//4.1、如果缓存已经过期了,将Request添加到网络请求队列中,重新执行网络请求
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
request.addMarker("cache-hit");
//5、磁盘中获取的数据会通过Request的parserNetworkResponse方法包装成Response
Response> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
//6、判断当缓存数据是否需要刷新(根据服务器响应头中获取设置的)
if (!entry.refreshNeeded()) {
//6.1、不需要刷新原始数据,调用ExecutorDelivery响应传递类的postResponse方法
// 进行结果的分发
mDelivery.postResponse(request, response);
} else {
request.addMarker("cache-hit-refresh-needed");
//6.2、会将老的数据呈现给用户,再进行网络请求,这样优化用户体验
request.setCacheEntry(entry);
//6.3、将Request添加到网络请求队列中,重新执行网络请求
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;
}
}
CacheDispatcher继承自Thread,也就是说缓存数据获取是在子线程中操作的,通过上图,总结缓存调度线程的执行逻辑:
com.android.volley.NetworkDispatcher:
@Override
public void run() {
//设置当前线程的优先级为后台线程
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Request> request;
while (true) {
try {
//1、从请求队列中获取一个Request
request = mQueue.take();
} catch (InterruptedException e) {
if (mQuit) {
return;
}
continue;
}
try {
request.addMarker("network-queue-take");
//2、判断当前Request是否被设置为取消状态
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
continue;
}
//添加流量监控
addTrafficStatsTag(request);
//3、网络请求
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
//4、服务器返回304(资源没有被修改),并且请求已交付
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
continue;
}
//5、网络请求获取的数据会通过Request的parserNetworkResponse方法包装成Response
Response> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
//6、判断Request是否需要缓存数据并且数据不为空
if (request.shouldCache() && response.cacheEntry != null) {
//6.1、如果Request需要缓存数据并且请求数据不为空,
// 调用DiskBasedCache类的put方法将我们的请求结果保存在磁盘上
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
//7、请求交付
request.markDelivered();
//8、分发数据
mDelivery.postResponse(request, response);
} catch (VolleyError volleyError) {
//8.1请求错误,分发错误信息
parseAndDeliverNetworkError(request, volleyError);
} catch (Exception e) {
//8.2发送异常,分发异常信息
mDelivery.postError(request, new VolleyError(e));
}
}
}
private void parseAndDeliverNetworkError(Request> request, VolleyError error) {
error = request.parseNetworkError(error);
mDelivery.postError(request, error);
}
com.android.volley.Request:
/**
* 如果设置成false,说明每次请求都会进行网络请求,否则走缓存调度线程。
* @return This Request object to allow for chaining.
*/
public final Request> setShouldCache(boolean shouldCache) {
mShouldCache = shouldCache;
return this;
}
public final boolean shouldCache() {
return mShouldCache;
}
//3、网络请求
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
//4、服务器返回304(资源没有被修改),并且请求已交付
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
continue;
}
//5、网络请求获取的数据会通过Request的parserNetworkResponse方法包装成Response
Response> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
//6、判断Request是否需要缓存数据并且数据不为空
if (request.shouldCache() && response.cacheEntry != null) {
//6.1、如果Request需要缓存数据并且请求数据不为空,
// 调用DiskBasedCache类的put方法将我们 的请求结果保存在磁盘上
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
performRequest(request,headers)方法发起网络请求并返回HttpResponse,后面就是构造NetworkResponse实例,构造实例分两种情况。
/**
* @param statusCode http状态码
* @param data 相应体
* @param headers 返回的响应头或者为null
* @param notModified 如果服务器返回了304,数据已在缓存中,则为true,否则false
*/
public NetworkResponse(int statusCode, byte[] data, Map headers,
boolean notModified) {
this.statusCode = statusCode;
this.data = data;
this.headers = headers;
this.notModified = notModified;
}
//5、网络请求获取的数据会通过Request的parserNetworkResponse方法包装成Response
Response> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
//6、判断Request是否需要缓存数据并且数据不为空
if (request.shouldCache() && response.cacheEntry != null) {
//6.1、如果Request需要缓存数据并且请求数据不为空,
// 调用DiskBasedCache类的put方法将我们 的请求结果保存在磁盘上
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
@Override
protected Response parseNetworkResponse(NetworkResponse response) {
try {
String jsonString = new String(response.data,
HttpHeaderParser.parseCharset(response.headers));
return Response.success(mGson.fromJson(jsonString, mClass),
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
}
}
private Response(T result, Cache.Entry cacheEntry) {
this.result = result;
this.cacheEntry = cacheEntry;
this.error = null;
}
result指最终解析类型;cacheEntry保存的是返回数据,包括Http的头信息;error指的是错误信息。
Response的组成部分我们已经知道了,继续回到之前的网络调度线程代码:
//6、判断Request是否需要缓存数据并且数据不为空
if (request.shouldCache() && response.cacheEntry != null) {
//6.1、如果Request需要缓存数据并且请求数据不为空,
// 调用DiskBasedCache类的put方法将我们 的请求结果保存在磁盘上
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
@Override
public synchronized void put(String key, Entry entry) {
//判断缓存的数据是否超过最大缓存,如果超过最大缓存,就遍历删除文件,直到小于最大缓存数。
pruneIfNeeded(entry.data.length);
//创建缓存文件(文件头命名规则:url一半字符的哈希值与url后半段字符的哈希值进行拼接)
File file = getFileForKey(key);
try {
FileOutputStream fos = new FileOutputStream(file);
CacheHeader e = new CacheHeader(key, entry);
//写入相关信息
boolean success = e.writeHeader(fos);
if (!success) {
fos.close();
VolleyLog.d("Failed to write header for %s", file.getAbsolutePath());
throw new IOException();
}
//写入请求数据
fos.write(entry.data);
fos.close();
//内存缓存(不包含请求数据)
putEntry(key, e);
return;
} catch (IOException e) {
}
boolean deleted = file.delete();
if (!deleted) {
VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
}
}
private void putEntry(String key, CacheHeader entry) {
if (!mEntries.containsKey(key)) {
//不包含key,总长度累加
mTotalSize += entry.size;
} else {
//包含key,刷新总长度
CacheHeader oldEntry = mEntries.get(key);
mTotalSize += (entry.size - oldEntry.size);
}
//保存(entry不包含请求数据data[])
mEntries.put(key, entry);
}
/**
* 读取缓存文件的相关头信息
*/
@Override
public synchronized void initialize() {
////创建缓存目录,默认当前应用缓冲目录下的volley
if (!mRootDirectory.exists()) {
if (!mRootDirectory.mkdirs()) {
VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath());
}
return;
}
//获取缓存目录下的所有文件
File[] files = mRootDirectory.listFiles();
if (files == null) {
return;
}
for (File file : files) {
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
//读取相关Http头信息
CacheHeader entry = CacheHeader.readHeader(fis);
entry.size = file.length();
//将磁盘缓存的文件信息保存在mEntries中
putEntry(entry.key, entry);
} catch (IOException e) {
if (file != null) {
file.delete();
}
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException ignored) {
}
}
}
}
private final Map mEntries =
new LinkedHashMap(16, .75f, true);
//3、从磁盘中获取缓存
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
//3.1、本地缓存空,将Request添加到网络请求队列中,重新执行网络请求
request.addMarker("cache-miss");
mNetworkQueue.put(request);
continue;
}
//4、判断缓存是否过期(根据服务器响应头中获取设置的)
if (entry.isExpired()) {
//4.1、如果缓存已经过期了,将Request添加到网络请求队列中,重新执行网络请求
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
request.addMarker("cache-hit");
//5、磁盘中获取的数据会通过Request的parserNetworkResponse方法包装成Response
Response> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
//6、判断当缓存数据是否需要刷新(根据服务器响应头中获取设置的)
if (!entry.refreshNeeded()) {
//6.1、不需要刷新原始数据,调用ExecutorDelivery响应传递类的postResponse方法
// 进行结果的分发
mDelivery.postResponse(request, response);
} else {
request.addMarker("cache-hit-refresh-needed");
//6.2、会将老的数据呈现给用户,再进行网络请求,这样优化用户体验
request.setCacheEntry(entry);
//6.3、将Request添加到网络请求队列中,重新执行网络请求
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.
}
}
});
}
private void addCacheHeaders(Map headers, Cache.Entry entry) {
if (entry == null) {
return;
}
if (entry.etag != null) {
headers.put("If-None-Match", entry.etag);
}
if (entry.serverDate > 0) {
Date refTime = new Date(entry.serverDate);
headers.put("If-Modified-Since", DateUtils.formatDate(refTime));
}
}
If-Modified-Since,和 Last-Modified 一样都是用于记录页面最后修改时间的 HTTP 头信息,只是 Last-Modified 是由服务器往客户端发送的 HTTP 头,而 If-Modified-Since 则是由客户端往服务器发送的头,可 以看到,再次请求本地存在的 cache 页面时,客户端会通过 If-Modified-Since 头将先前服务器端发过来的 Last-Modified 最后修改时间戳发送回去,这是为了让服务器端进行验证,通过这个时间戳判断客户端的页面是否是最新的,如果不是最新的,则返回新的内容,如果是最新的,则 返回 304 告诉客户端其本地 cache 的页面是最新的,于是客户端就可以直接从本地加载页面了,这样在网络上传输的数据就会大大减少,同时也减轻了服务器的负担。
If-None-Match,它和ETags(HTTP协议规格说明定义ETag为“被请求变量的实体值”,或者是一个可以与Web资源关联的记号)常用来判断当前请求资源是否改变。类似于Last-Modified和HTTP-IF-MODIFIED-SINCE。但是有所不同的是Last-Modified和HTTP-IF-MODIFIED-SINCE只判断资源的最后修改时间,而ETags和If-None-Match可以是资源任何的任何属性,不如资源的MD5等。
ETags和If-None-Match的工作原理是在HTTP Response中添加ETags信息。当客户端再次请求该资源时,将在HTTP Request中加入If-None-Match信息(ETags的值)。如果服务器验证资源的ETags没有改变(该资源没有改变),将返回一个304状态;否则,服务器将返回200状态,并返回该资源和新的ETags。
ETag如何帮助提升性能?
聪明的服务器开发者会把ETags和GET请求的“If-None-Match”头一起使用,这样可利用客户端(例如浏览器)的缓存。因为服务器首先产生ETag,服务器可在稍后使用它来判断页面是否已经被修改。本质上,客户端通过将该记号传回服务器要求服务器验证其(客户端)缓存。
其过程如下:
1.客户端请求一个页面(A)。
2.服务器返回页面A,并在给A加上一个ETag。
3.客户端展现该页面,并将页面连同ETag一起缓存。
4.客户再次请求页面A,并将上次请求时服务器返回的ETag一起传递给服务器。
5.服务器检查该ETag,并判断出该页面自上次客户端请求之后还未被修改,直接返回响应304(未修改——Not Modified)和一个空的响应体。