volley官网
手撕 Volley
Volley全方位解析,带你从源码的角度彻底理解
Android Volley完全解析(四),带你从源码的角度理解Volley
Android 面试之常用开源库
HTTP权威指南笔记
Java并发编程:阻塞队列
Volley 是 Google 推出的轻量级 Android 异步网络请求框架和图片加载框架。在 Google I/O 2013 大会上发布。其适用场景是数据量小,通信频繁的网络操作。Volley名称的由来: a burst or emission of many things or a large amount at once。
volley中为了提高请求处理的速度,采用了字节数组byte[]缓冲池ByteArrayPool进行内存中的数据存储的,如果下载大量的数据,这个存储空间就会溢出,所以不适合大量的数据。但是由于他的这个存储空间是内存中分配的,当存储的时候优是从ByteArrayPool中取出一块已经分配的内存区域, 不必每次存数据都要进行内存分配,而是先查找缓冲池中有无适合的内存区域,如果有,直接拿来用,从而减少内存分配的次数 ,所以他比较适合轻量级频次高的网络数据交互情况。源码请看3.2的(2)中的ByteArrayPool。
volley使用及其简单,我们只需要创建一个RequestQueue请求队列,然后往队列里面扔http请求即可,volley会不断从队列里面取出请求然后交给一堆工作线程处理。这里的http请求是通过Request类来封装的,我们只用创建Request对象,然后提供诸如url之类的参数即可。网络操作全部是在子线程中处理的,我们不必担心阻塞UI线程。网络请求的结果会异步返回给我们,我们只需要处理Request的回调即可。
(1)添加Volley.jar包,添加权限.
<uses-permission android:name="android.permission.INTERNET" />
(2)使用代码如下:
private void fetchData() {
String url = "http://apis.juhe.cn/cook/query.php";
// 1.获取到一个RequestQueue对象
RequestQueue mQueue = Volley.newRequestQueue(getApplicationContext());
// 2.创建一个StringRequest对象
StringRequest stringRequest = new StringRequest(Request.Method.POST, url,
new Response.Listener<String>() {
@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);
}
}) {
@Override
protected Map<String, String> getParams() throws AuthFailureError {
Map<String,String> map = new HashMap();
map.put("menu","龙眼");
map.put("key","8fac966b379367b0e6f0527d634324ee");
map.put("dtype","");
map.put("pn","");
map.put("rn","");
map.put("albums","");
return map;
}
};
stringRequest.setTag("MAIN_ACTIVITY");
// 3.将这个StringRequest对象添加到RequestQueue
mQueue.add(stringRequest);
}
(3)结果如下:
{
"resultcode": "200",
"reason": "Success",
"result": {
"data": [
{
"id": "1916",
"title": "龙眼茶碗蒸",
"tags": "儿童;创意菜;甜品;小吃;早餐;日本料理",
}
]
}
}
/**
* 有时候需要为Request添加请求头,这时候可以去重写Request的getHeaders方法。
*/
JsonObjectRequest request = new JsonObjectRequest(url, null,resplistener,errlistener) {
@Override
public Map getHeaders() throws AuthFailureError {
Map map = new HashMap();
map.put("header1","header1_val");
map.put("header2","header2_val");
return map;
}
};
/**
* 当Activity销毁时,我们可能需要去取消一些网络请求,这时候可以通过如下方式
* 为属于该Activity的请求全部加上Tag,然后需要销毁的时候调用cancelAll传入tag即可
*/
@Override
protected void onDestroy() {
mQueue.cancelAll("MAIN_ACTIVITY");
super.onDestroy();
}
RequestQueue没有必要每个Activity里面都创建,全局保有一个即可。这时候自然想到使用Application了。我们可以在Application里面创建RequestQueue,并向外暴露get方法。
/**
* 定制XMLRequest
*/
public class XMLRequest extends Request<XmlPullParser> {
private Response.Listener mListener;
public XMLRequest(int method, String url, Response.Listener listener,
Response.ErrorListener errorListener) {
super(method, url, errorListener);
mListener = listener;
}
public XMLRequest(String url, Response.Listener listener,
Response.ErrorListener errorListener) {
this(Method.GET, url, listener, errorListener);
}
@Override
protected Response parseNetworkResponse(NetworkResponse response) {
try {
String xmlString = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
XmlPullParser parser = Xml.newPullParser();
parser.setInput(new StringReader(xmlString));//将返回数据设置给解析器
return Response.success(parser, HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new VolleyError(e));
} catch (XmlPullParserException e) {
return Response.error(new VolleyError(e));
}
}
@Override
protected void deliverResponse(XmlPullParser response) {
mListener.onResponse(response);
}
}
/**
* 使用例子
*/
private void fetchXml() {
RequestQueue mQueue = Volley.newRequestQueue(getApplicationContext());
String url = "http://flash.weather.com.cn/wmaps/xml/china.xml";
XMLRequest xmlRequest = new XMLRequest(url,
new Response.Listener() {
@Override
public void onResponse(XmlPullParser response) {
try {
int eventType = response.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
switch (eventType) {
case XmlPullParser.START_TAG:
String nodeName = response.getName();
if ("city".equals(nodeName)) {
String pName = response.getAttributeValue(0);
Log.d("TAG", "pName is " + pName);
}
break;
case XmlPullParser.END_TAG:
break;
default:
break;
}
eventType = response.next();
}
} catch (XmlPullParserException | IOException e) {
e.printStackTrace();
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e("TAG", error.getMessage(), error);
}
});
mQueue.add(xmlRequest);
}
使用Volley的第一步是通过Volley的newRequestQueue方法得到 一个RequestQueue 队列。指定了硬盘缓存的位置为data/data/package_name/cache/volley/…,Network类(具体实现类是BasicNetwork)封装了请求方式,并且根据当前API版本来选用不同的http工具。最后启动了请求队列。
public class Volley {
private static final String DEFAULT_CACHE_DIR = "volley";
public Volley() {
}
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
// 1.在磁盘上创建一块文件
File cacheDir = new File(context.getCacheDir(), "volley");
String userAgent = "volley/0";
try {
// 2.设置UserAgent,不知道什么是UserAgent去看HTTP协议
String network = context.getPackageName();
PackageInfo queue = context.getPackageManager().getPackageInfo(network, 0);
userAgent = network + "/" + queue.versionCode;
} catch (NameNotFoundException var6) {
;
}
// 3.根据SDK版本的不同初始化HTTPStack
if(stack == null) {
if(VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
// 4.用HTTPStack初始化BasicNetwork
BasicNetwork network1 = new BasicNetwork((HttpStack)stack);
// 5.DiskBasedCache(cacheDir):用第1步创建的文件初始化磁盘缓存
// 6.用磁盘缓存和NetWork创建我们的请求队列RequestQueue
RequestQueue queue1 = new RequestQueue(new DiskBasedCache(cacheDir), network1);
// 7.调用RequestQueue的start方法
queue1.start();
return queue1;
}
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, (HttpStack)null);
}
}
(1) HttpStack、HurlStack、HttpClientStack
HttpStack是一个接口并且只有一个 performRequest 方法。而HurlStack和HttpClientStack分别是基于HttpUrlConnection和HttpClient对HttpStack的实现,是真正用来访问网络的类。
public interface HttpStack {
HttpResponse performRequest(Request> var1, Map var2) throws IOException, AuthFailureError;
}
public class HurlStack implements HttpStack {
public HttpResponse performRequest(Request> request, Map additionalHeaders) throws IOException, AuthFailureError {
return response;
}
}
}
public class HttpClientStack implements HttpStack {
public HttpResponse performRequest(Request> request, Map additionalHeaders) throws IOException, AuthFailureError {
return this.mClient.execute(httpRequest);
}
}
(2) NetWork和BasicNetWork
同样是接口与实现类的关系,内部封装了一个 HttpStack 用来是想网络请求。接口的方法是一样的。
public interface Network {
NetworkResponse performRequest(Request> var1) throws VolleyError;
}
public class BasicNetwork implements Network {
protected final HttpStack mHttpStack;
// volley定义了一个byte[]缓冲池,即ByteArrayPool
protected final ByteArrayPool mPool;
public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) {
mHttpStack = httpStack;
mBaseHttpStack = new AdaptedHttpStack(httpStack);
mPool = pool;
}
public BasicNetwork(HttpStack httpStack) {
this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE));
}
public NetworkResponse performRequest(Request> request) throws VolleyError {
while(true) {
HttpResponse httpResponse = null;
Object responseContents = null;
HashMap responseHeaders = new HashMap();
try {
HashMap e = new HashMap();
this.addCacheHeaders(e, request.getCacheEntry());
httpResponse = this.mHttpStack.performRequest(request, e);
StatusLine statusCode2 = httpResponse.getStatusLine();
int networkResponse1 = statusCode2.getStatusCode();
Map responseHeaders1 = convertHeaders(httpResponse.getAllHeaders());
byte[] responseContents1 = this.entityToBytes(httpResponse.getEntity());
return new NetworkResponse(networkResponse1, responseContents1, responseHeaders1, false);
}
}
}
private byte[] inputStreamToBytes(InputStream in, int contentLength)
throws IOException, ServerError {
// PoolingByteArrayOutputStream其实就是一个输出流,只不过系统的输出流在使用byte[]时,如果大小不够,会自动扩大byte[]的大小。
// 而PoolingByteArrayOutputStream则是使用了上面的字节数组缓冲池,从池中获取byte[],使用完毕后再归还。
PoolingByteArrayOutputStream bytes =
new PoolingByteArrayOutputStream(mPool, contentLength);
byte[] buffer = null;
try {
if (in == null) {
throw new ServerError();
}
buffer = mPool.getBuf(1024);
int count;
while ((count = in.read(buffer)) != -1) {
bytes.write(buffer, 0, count);
}
return bytes.toByteArray();
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException e) {
VolleyLog.v("Error occurred when closing InputStream");
}
mPool.returnBuf(buffer);
bytes.close();
}
}
}
/**
* 字节数组byte[]缓冲池:
* https://blog.csdn.net/u010410408/article/details/52037838
* byte[]的缓存池,可以指定最大的缓存的byte数目。当缓存的byte数目超过指定的最大值时,以FIFO策略对最早添加的数据进行回收。
* 主要通过一个元素长度从小到大排序的ArrayList作为byte[]的缓存,另有一个按使用时间先后排序的ArrayList属性用于缓存满时清理元素。
*/
public class ByteArrayPool {
// 按使用的先后时间顺序排序
private List<byte[]> mBuffersByLastUse = new LinkedList<byte[]>();
// 按大小顺序排序
private List<byte[]> mBuffersBySize = new ArrayList<byte[]>(64);
// 池中所有byte[]的长度之和
private int mCurrentSize = 0;
// 池中单个byte[]的最大长度
private final int mSizeLimit;
/** Compares buffers by size 比较器,用于排序,按byte[]的字节长度进行排序*/
protected static final Comparator<byte[]> BUF_COMPARATOR = new Comparator<byte[]>() {
@Override
public int compare(byte[] lhs, byte[] rhs) {
return lhs.length - rhs.length;
}
};
/**
* 创建byte[]缓冲池,并设定池中单个byte[]的最大长度
*/
public ByteArrayPool(int sizeLimit) {
mSizeLimit = sizeLimit;
}
/**
* 从池中获取一个可用的byte[],如果没有,就创建一个。参数为想要获取多大长度的byte[]
*/
public synchronized byte[] getBuf(int len) {
//遍历按长度排序的池
for (int i = 0; i < mBuffersBySize.size(); i++) {
byte[] buf = mBuffersBySize.get(i);
//如果当前的byte[]的长度大于给定的长度,就返回该byte[]
if (buf.length >= len) {
//池中所有byte[]的长度之和减去返回出去的byte[]的长度
mCurrentSize -= buf.length;
//从按顺序排列的池中移除该byte[]
mBuffersBySize.remove(i);
//从按使用顺序排列的池中移除该byte[]表示该byte[]正在使用中,其他不能再使用该byte[]
mBuffersByLastUse.remove(buf);
return buf;
}
}
//创建一个新的byte[]
return new byte[len];
}
/**
* 当使用完一个byte[]后,将该byte[]返回到池中
*/
public synchronized void returnBuf(byte[] buf) {
//如果为空或者超过了设定的单个byte[]的最大长度 那么就不再池中保存该byte[]
if (buf == null || buf.length > mSizeLimit) {
return;
}
//在按使用时间顺序排序的池中添加该byte[]
mBuffersByLastUse.add(buf);
//利用二分查找法,找出在按大小排序的池中该byte[]应该存放的位置
int pos = Collections.binarySearch(mBuffersBySize, buf, BUF_COMPARATOR);
//如果找不到,则返回-1
if (pos < 0) {
//当找不到时,也就是在0位置添加
pos = -pos - 1;
}
mBuffersBySize.add(pos, buf);
//增加最大长度
mCurrentSize += buf.length;
//清理,不能超过字节总数最大值
trim();
}
/**
* 当现有字节总数超过了设定的界限,那么需要清理
*/
private synchronized void trim() {
while (mCurrentSize > mSizeLimit) {
//按照使用的先后顺序来倾全力,最新使用的最先被清除
byte[] buf = mBuffersByLastUse.remove(0);
//同样在该池中也清除
mBuffersBySize.remove(buf);
//减小现有字节最大长度
mCurrentSize -= buf.length;
}
}
}
(3)DiskBasedCache
接口Cache里面封装了一个静态内部类Entry,Entry里面定义的这些成员变量跟响应报文headers(消息报头)里面关于缓存的标签是一样的。其中还维护了一个map用来保存消息报头中的key/value,data来保存entity消息实体。
DiskBasedCache默认大小为5M,但是可以自己配置,内部用了一个LinkedHashMap来保存request的CacheHeader,CacheHeader是为了存储Entity中的header和data的size。LinkedHashMap是为了实现 FIFO的缓存替换策略,在空间不足时向HashMap中put数据,就需要删除一些内容用来保证最新put数据的成功。
public class DiskBasedCache implements Cache {
private final Map mEntries;
private long mTotalSize;
private final File mRootDirectory;
private final int mMaxCacheSizeInBytes;
private static final int DEFAULT_DISK_USAGE_BYTES = 5242880;
private static final float HYSTERESIS_FACTOR = 0.9F;
private static final int CACHE_VERSION = 2;
public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
this.mEntries = new LinkedHashMap(16, 0.75F, true);
this.mTotalSize = 0L;
this.mRootDirectory = rootDirectory;
this.mMaxCacheSizeInBytes = maxCacheSizeInBytes;
}
public synchronized void put(String key, Entry entry) {
// pruneIfNeeded()方法是在put方法的第一行执行的,做的就是这件事。
// Volley并没有使用最近最少使用算法(LRU),而是使用先进先出算法(FIFO)。
// DiskBasedCache类剩下的就是一些文件操作了,不需要挨着看了。
this.pruneIfNeeded(entry.data.length);
// .......
}
private void pruneIfNeeded(int neededSpace) {
if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) {
return;
}
long before = mTotalSize;
int prunedFiles = 0;
long startTime = SystemClock.elapsedRealtime();
Iterator> iterator = mEntries.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = iterator.next();
CacheHeader e = entry.getValue();
boolean deleted = getFileForKey(e.key).delete();
if (deleted) {
mTotalSize -= e.size;
} else {
//print log
}
iterator.remove();
prunedFiles++;
if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
break;
}
}
}
/**
* Returns a file object for the given cache key.
*/
public File getFileForKey(String key) {
return new File(mRootDirectory, getFilenameForKey(key));// 调用了getFilenameForKey()
}
/**
* 缓存文件名的计算 Creates a pseudo-unique filename for the specified cache key.
* @param key The key to generate a file name for.
* @return A pseudo-unique filename.
*/
private String getFilenameForKey(String key) {
int firstHalfLength = key.length() / 2;
String localFilename = String.valueOf(key.substring(0, firstHalfLength).hashCode());
localFilename += String.valueOf(key.substring(firstHalfLength).hashCode());
return localFilename;
}
private static class CacheHeader {
public long size;
public String key;
public String etag;
public long serverDate;
public long ttl;
public long softTtl;
public Map responseHeaders;
private CacheHeader() {
}
public CacheHeader(String key, Entry entry) {
this.key = key;
this.size = (long)entry.data.length;
this.etag = entry.etag;
this.serverDate = entry.serverDate;
this.ttl = entry.ttl;
this.softTtl = entry.softTtl;
this.responseHeaders = entry.responseHeaders;
}
}
}
public interface Cache {
Cache.Entry get(String var1);
void put(String var1, Cache.Entry var2);
void initialize();
void invalidate(String var1, boolean var2);
void remove(String var1);
void clear();
public static class Entry {
public byte[] data;
public String etag;
public long serverDate;
public long ttl;
public long softTtl;
public Map responseHeaders = Collections.emptyMap();
public Entry() {
}
public boolean isExpired() {
return this.ttl < System.currentTimeMillis();
}
public boolean refreshNeeded() {
return this.softTtl < System.currentTimeMillis();
}
}
}
(4)RequestQueue
RequestQueue中一共有五个主要的方法,分别是 start()、add()、stop()、cancel()、finish(),重点讲创建——–start()。
private AtomicInteger mSequenceGenerator;//序列号生成器
private final Map> mWaitingRequests;//hashmap通过method+url为key,重复request组成的queue为value,的等待队列
private final Set mCurrentRequests;//存储包括正在执行和等待所有的request
private final PriorityBlockingQueue mCacheQueue;//优先级阻塞缓存队列
private final PriorityBlockingQueue mNetworkQueue;//优先级阻塞网络请求队列
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;//网络请求线程池大小
private final Cache mCache;//接口,具体实现由构造器传入
private final Network mNetwork;//接口,具体实现由构造器传入
private final ResponseDelivery mDelivery;//结果分发器
private NetworkDispatcher[] mDispatchers;//网络调度线程数组
private CacheDispatcher mCacheDispatcher;//缓存调度线程
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;
}
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize, new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
public RequestQueue(Cache cache, Network network) {
this(cache, network, 4);
}
/**
* 启动此队列中的调度程序
*/
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();
}
}
/**
- Stops the cache and network dispatchers.
*/
public void stop() {
if (mCacheDispatcher != null) {
mCacheDispatcher.quit();
}
for (int i = 0; i < mDispatchers.length; i++) {
if (mDispatchers[i] != null) {
mDispatchers[i].quit();
}
}
}
请求的添加是通过RequestQueue的add完成的,add方法的逻辑是这样的:
/**
* 向请求队列添加请求。
* @param 请求服务请求
* @return 传入请求
* /
public Request add(Request request){
// 将请求request关联到当前RequestQueue
request.setRequestQueue(this);
// 同步操作将request添加到当前RequestQueue对象的mCurrentRequests中。
synchronized(mCurrentRequests){
mCurrentRequests.add(request);
}
// 设定request加入队列的次序
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");
// request是否需要添加缓存?
if(!request.shouldCache()){
// 如果请求不需要,直接添加到网络请求队列中
mNetworkQueue.add(request);
return request;
}
// 如果请求需要缓存
synchronized(mWaitingRequests) {
// 同步操作,根据cacheKey判断有无一样的请求在等待?
String cacheKey = request.getCacheKey();
if(mWaitingRequests.containsKey(cacheKey)) {
// 已经有等待的请求
Queue> stagedRequests = mWaitingRequests.get(cacheKey);
if(stagedRequests == null){
stagedRequests = new LinkedList >();
}
// 加入已有的队列,并存入mWaitingRequests中
stagedRequests.add(request);
mWaitingRequests.put(cacheKey,stagedRequests);
if(VolleyLog.DEBUG) {
VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
}
} else {
// 没有等待的请求(即是第一个),存入mWaitingRequests,value为null;同时添加请求到缓存队列
mWaitingRequests.put(cacheKey,null);
mCacheQueue.add(request);
}
return request;
}
}
通过这一方法,请求就被分发到两个队列中分别供CacheDispatcher和NetworkDispatcher处理。类图如下:
public abstract class Request<T> implements Comparable<Request<T>> {
private static final String DEFAULT_PARAMS_ENCODING = "UTF-8";
private final MarkerLog mEventLog;
private final int mMethod;
private final String mUrl;
private final int mDefaultTrafficStatsTag;
private final ErrorListener mErrorListener;
private Integer mSequence;
private RequestQueue mRequestQueue;
private boolean mShouldCache;
private boolean mCanceled;
private boolean mResponseDelivered;
private long mRequestBirthTime;
private static final long SLOW_REQUEST_THRESHOLD_MS = 3000L;
private RetryPolicy mRetryPolicy;
private Entry mCacheEntry;
private Object mTag;
public Request(int method, String url, ErrorListener listener) {
this.mEventLog = MarkerLog.ENABLED?new MarkerLog():null;
this.mShouldCache = true;
this.mCanceled = false;
this.mResponseDelivered = false;
this.mRequestBirthTime = 0L;
this.mCacheEntry = null;
this.mMethod = method;
this.mUrl = url;
this.mErrorListener = listener;
this.setRetryPolicy(new DefaultRetryPolicy());
this.mDefaultTrafficStatsTag = TextUtils.isEmpty(url)?0:Uri.parse(url).getHost().hashCode();
}
}
请求的处理是由CacheDispatcher和NetworkDispatcher来完成的,它们的run方法通过一个死循环不断去从各自的队列中取出请求,进行处理,并将结果交由ResponseDelivery。本节以缓存调度CacheDispacher为主,CacheDispacher继承了Thread,是一个线程类,他的run方法是一个 while true死循环,有一个标记位mQuit来退出循环。
public class CacheDispatcher extends Thread {
private static final boolean DEBUG;
private final BlockingQueue mCacheQueue;// 阻塞队列
private final BlockingQueue mNetworkQueue;// 阻塞队列
private final Cache mCache;
private final ResponseDelivery mDelivery;// 接口用来post response 或者 error
private volatile boolean mQuit = false;
static {
DEBUG = VolleyLog.DEBUG;
}
public CacheDispatcher(BlockingQueue cacheQueue, BlockingQueue networkQueue, Cache cache, ResponseDelivery delivery) {
this.mCacheQueue = cacheQueue;
this.mNetworkQueue = networkQueue;
this.mCache = cache;
this.mDelivery = delivery;
}
public void quit() {
this.mQuit = true;
this.interrupt();
}
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher");
// 设置进程优先级
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// 进行阻塞调用以初始化缓存
mCache.initialize();
Request> request;
// 开启while (true)死循环
while (true) {
// 释放先前的请求对象,以避免在排队时排除请求对象
request = null;
try {
// 从队列中获取请求
request = mCacheQueue.take();
} catch (InterruptedException e) {
// 如果中断取消,退出循环
if (mQuit) {
return;
}
// 如果没中断取消,只是退出一次循环
continue;
}
try {
request.addMarker("cache-queue-take");
// 如果请求已被取消,退出一次循环
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}
// 根据CacheKey查询缓存中有没有对应的Entry对象?
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
// 缓存未命中,发送到网络请求队列,退出一次循环
request.addMarker("cache-miss");
mNetworkQueue.put(request);
continue;
}
// 如果它完全过期,重新封装request,送到网络请求队列,退出一次循环
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
// OK------>我们有一个缓存命中
request.addMarker("cache-hit");
// 解析其数据得到Response,以传送回请求
Response> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
// 是否需要刷新entry?(这个需要请求的页面指定了Cache-Control或者Last-Modified/Expires等字段,并且Cache-Control的优先级比Expires更高。否则请求一定是过期的)
if (!entry.refreshNeeded()) {
// 完全未到达的缓存命中,不需要刷新entry。mDelivery分发结果将Response传递给request
mDelivery.postResponse(request, response);
} else {
// 软过期缓存命中,需要刷新entry。
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
// 将响应标记为中间(在ExecutorDelivery中处理)
response.intermediate = true;
// 先将Response先将结果返回到request,再将重新封装的请求发送到网络请求队列
final Request> finalRequest = request;
mDelivery.postResponse(request, response, new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(finalRequest);
} catch (InterruptedException e) {
// Not much we can do about this.
}
}
});
}
} catch (Exception e) {
VolleyLog.e(e, "Unhandled exception %s", e.toString());
}
}
}
}
public class NetworkDispatcher extends Thread {
private final BlockingQueue mQueue;// 阻塞队列
private final Network mNetwork;// NetWork接口
private final Cache mCache;// Cache接口
private final ResponseDelivery mDelivery;// 结果分发器
private volatile boolean mQuit = false;
public NetworkDispatcher(BlockingQueue queue, Network network, Cache cache, ResponseDelivery delivery) {
this.mQueue = queue;
this.mNetwork = network;
this.mCache = cache;
this.mDelivery = delivery;
}
public void quit() {
this.mQuit = true;
this.interrupt();
}
public void run() {
// 设置进程的优先级,声明一个请求
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Request> request;
// 开启while (true) 循环
while (true) {
long startTimeMs = SystemClock.elapsedRealtime();
// 释放先前的请求对象,以避免在排队时排除请求对象
request = null;
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()统计一下请求的流量
addTrafficStatsTag(request);
// 核心:调用Network的performRequest执行网络请求
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
// 是否返回304状态码,并且我们已经发送了一个响应?
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
// 是返回304,退出一次循环
request.finish("not-modified");
continue;
}
// request解析响应的networkResponse
Response> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
// request需要缓存,而且response不为空?
if (request.shouldCache() && response.cacheEntry != null) {
// 是,将response写入存入Cache
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
// 不是,将response传递给request,标记request为已交付。结束一次循环。
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);
}
}
}
}
这里的逻辑跟CacheDispatcher类似,也是构造Response对象,然后交由ResponseDelivery处理,但是这里的Response对象是通过NetworkResponse转化的,而这个NetworkResponse是从网络获取的,这里最核心的一行代码就是:
NetworkResponse networkResponse = mNetwork.performRequest(request);
public interface Network {
NetworkResponse performRequest(Request> var1) throws VolleyError;
}
public class BasicNetwork implements Network {
protected static final boolean DEBUG;
private static int SLOW_REQUEST_THRESHOLD_MS;// 最长请求时间
private static int DEFAULT_POOL_SIZE;// 线程池大小
protected final HttpStack mHttpStack;// HttpStack接口,真正执行网络请求的类
protected final ByteArrayPool mPool;// 二进制数组池,一个工具类
static {
DEBUG = VolleyLog.DEBUG;
SLOW_REQUEST_THRESHOLD_MS = 3000;
DEFAULT_POOL_SIZE = 4096;
}
public BasicNetwork(HttpStack httpStack) {
this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE));
}
public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) {
this.mHttpStack = httpStack;
this.mPool = pool;
}
public NetworkResponse performRequest(Request> request) throws VolleyError {
// 记录系统从启动到现在的时间
long requestStart = SystemClock.elapsedRealtime();
// 开启while (true)循环
while (true) {
// 声明变量httpResponse、responseContents、responseHeaders
HttpResponse httpResponse = null;
byte[] responseContents = null;
Map responseHeaders = Collections.emptyMap();
try {
// 从request中获取CacheEntry,封装成一个header
Map headers = new HashMap();
addCacheHeaders(headers, request.getCacheEntry());
// 核心:调用HttpStack的performRequest访问网络,获取httpResponse响应
httpResponse = mHttpStack.performRequest(request, headers);
// 获取状态码和响应Headers
StatusLine statusLine = httpResponse.getStatusLine();
int statusCode = statusLine.getStatusCode();
responseHeaders = convertHeaders(httpResponse.getAllHeaders());
// 状态码是否是304
if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
Entry entry = request.getCacheEntry();
if (entry == null) {
return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,
responseHeaders, true,
SystemClock.elapsedRealtime() - requestStart);
}
// HTTP 304响应没有所有标题字段。我们必须使用缓存条目中的头字段添加新的NetworkResponse。
entry.responseHeaders.putAll(responseHeaders);
// 是,请求完成,返回NetworkResponse
return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
entry.responseHeaders, true,
SystemClock.elapsedRealtime() - requestStart);
}
// 不是
// 处理修改的资源
if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
String newUrl = responseHeaders.get("Location");
request.setRedirectUrl(newUrl);
}
// 判断httpResponse有没有内容,204s没内容
if (httpResponse.getEntity() != null) {
responseContents = entityToBytes(httpResponse.getEntity());
} else {
// 添加0字节响应作为无内容请求
responseContents = new byte[0];
}
// 计算时间,打印log
long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
logSlowRequests(requestLifetime, request, responseContents, statusLine);
// 状态码statusCode < 200 || statusCode > 299?
if (statusCode < 200 || statusCode > 299) {
throw new IOException();// 是,抛出异常
}
// 不是,请求成功,返回最终的NetworkResponse
return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
SystemClock.elapsedRealtime() - requestStart);
} catch (SocketTimeoutException e) {
attemptRetryOnException("socket", request, new TimeoutError());
} catch (ConnectTimeoutException e) {
attemptRetryOnException("connection", request, new TimeoutError());
} catch (MalformedURLException e) {
throw new RuntimeException("Bad URL " + request.getUrl(), e);
} catch (IOException e) {
int statusCode = 0;
NetworkResponse networkResponse = null;
if (httpResponse != null) {
statusCode = httpResponse.getStatusLine().getStatusCode();
} else {
throw new NoConnectionError(e);
}
if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||
statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
VolleyLog.e("Request at %s has been redirected to %s", request.getOriginUrl(), request.getUrl());
} else {
VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
}
if (responseContents != null) {
networkResponse = new NetworkResponse(statusCode, responseContents,
responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);
if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
statusCode == HttpStatus.SC_FORBIDDEN) {
attemptRetryOnException("auth",
request, new AuthFailureError(networkResponse));
} else if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||
statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
attemptRetryOnException("redirect",
request, new RedirectError(networkResponse));
} else {
// TODO: Only throw ServerError for 5xx status codes.
throw new ServerError(networkResponse);
}
} else {
throw new NetworkError(e);
}
}
}
}
}
这里最核心的是这一句:
httpResponse = mHttpStack.performRequest(request, headers);
最终的网络执行还是在 HttpStack 中,而我们又知道,HttpStack 是一个接口,在 Volley 中他有两种实现:基于 HttpClient的HttpClientStack;基于 HttpURLConnection的HurlStack。
public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
}
public class HurlStack implements HttpStack {
private static final String HEADER_CONTENT_TYPE = "Content-Type";
private final HurlStack.UrlRewriter mUrlRewriter;
private final SSLSocketFactory mSslSocketFactory;
public HurlStack() {
this((HurlStack.UrlRewriter)null);
}
public HurlStack(HurlStack.UrlRewriter urlRewriter) {
this(urlRewriter, (SSLSocketFactory)null);
}
public HurlStack(HurlStack.UrlRewriter urlRewriter, SSLSocketFactory sslSocketFactory) {
this.mUrlRewriter = urlRewriter;
this.mSslSocketFactory = sslSocketFactory;
}
@Override
public HttpResponse performRequest(Request> request, Map additionalHeaders)
throws IOException, AuthFailureError {
// 从request中提取url
String url = request.getUrl();
HashMap map = new HashMap();
// 从request中提取headers(消息报头)与参数中给的additionalHeaders,存入到map中
map.putAll(request.getHeaders());
map.putAll(additionalHeaders);
if (mUrlRewriter != null) {
// mUrlRewriter不为空时,拦截替换url
String rewritten = mUrlRewriter.rewriteUrl(url);
if (rewritten == null) {
throw new IOException("URL blocked by rewriter: " + url);
}
url = rewritten;
}
// 用url创建一个URL对象
URL parsedUrl = new URL(url);
// 调用openConnection()建立连接
HttpURLConnection connection = openConnection(parsedUrl, request);
// 将map中的键值对依次添加到connection中
for (String headerName : map.keySet()) {
connection.addRequestProperty(headerName, map.get(headerName));
}
// 根据请求的method(get、post)设置connection的method和body(请求实体)
setConnectionParametersForRequest(connection, request);
// 使用HttpURLConnection中的数据初始化HttpResponse,默认是HTTP 1.1
ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
// 获取响应码
int responseCode = connection.getResponseCode();
if (responseCode == -1) {
// -1 向呼叫者发出连接发生错误的信号。抛出新的IOException(“无法从HttpUrlConnection检索响应代码”)
throw new IOException("Could not retrieve response code from HttpUrlConnection.");
}
StatusLine responseStatus = new BasicStatusLine(protocolVersion,
connection.getResponseCode(), connection.getResponseMessage());
// 获取response,设置消息实体
BasicHttpResponse response = new BasicHttpResponse(responseStatus);
if (hasResponseBody(request.getMethod(), responseStatus.getStatusCode())) {
response.setEntity(entityFromConnection(connection));
}
for (Entry> header : connection.getHeaderFields().entrySet()) {
if (header.getKey() != null) {
Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
// 添加消息报头
response.addHeader(h);
}
}
// 将HttpResponse的response返回
return response;
}
protected HttpURLConnection createConnection(URL url) throws IOException {
return (HttpURLConnection)url.openConnection();
}
private HttpURLConnection openConnection(URL url, Request> request) throws IOException {
HttpURLConnection connection = this.createConnection(url);
int timeoutMs = request.getTimeoutMs();
connection.setConnectTimeout(timeoutMs);
connection.setReadTimeout(timeoutMs);
connection.setUseCaches(false);
connection.setDoInput(true);
if("https".equals(url.getProtocol()) && this.mSslSocketFactory != null) {
((HttpsURLConnection)connection).setSSLSocketFactory(this.mSslSocketFactory);
}
return connection;
}
static void setConnectionParametersForRequest(HttpURLConnection connection, Request> request) throws IOException, AuthFailureError {
switch(request.getMethod()) {
case -1:
byte[] postBody = request.getPostBody();
if(postBody != null) {
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.addRequestProperty("Content-Type", request.getPostBodyContentType());
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
out.write(postBody);
out.close();
}
break;
case 0:
connection.setRequestMethod("GET");
break;
case 1:
connection.setRequestMethod("POST");
addBodyIfExists(connection, request);
break;
case 2:
connection.setRequestMethod("PUT");
addBodyIfExists(connection, request);
break;
case 3:
connection.setRequestMethod("DELETE");
break;
default:
throw new IllegalStateException("Unknown method type.");
}
}
}
请求结果的分发处理是由ResponseDelivery实现类ExecutorDelivery完成的,ExecutorDelivery是在RequestQueue的构造器中被创建的,并且绑定了UI线程的Looper。
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize,
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
public interface ResponseDelivery {
void postResponse(Request> var1, Response> var2);
void postResponse(Request> var1, Response> var2, Runnable var3);
void postError(Request> var1, VolleyError var2);
}
ExcutorDelivery对应的构造函数,内部有个自定义Executor,传入了一个主线程的Looper创建的 Handler,然后有一个Executor的成员变量,用来向UI线程传递 response。
public class ExecutorDelivery implements ResponseDelivery {
private final Executor mResponsePoster;
public ExecutorDelivery(final Handler handler) {
this.mResponsePoster = new Executor() {
public void execute(Runnable command) {
handler.post(command);
}
};
}
public ExecutorDelivery(Executor executor) {
this.mResponsePoster = executor;
}
/**
* ExcutorDelivery最重要的方法就是PostResponse了
*/
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));
}
public void postError(Request> request, VolleyError error) {
request.addMarker("post-error");
Response response = Response.error(error);
this.mResponsePoster.execute(new ExecutorDelivery.ResponseDeliveryRunnable(request, response, (Runnable)null));
}
/**
* 最终执行的是ResponseDeliveryRunnable这个Runnable
*/
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) {
this.mRequest = request;
this.mResponse = response;
this.mRunnable = runnable;
}
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);
}
// 处理响应需要刷新情况(CacheDispacher的run()方法的软过期缓存命中,需要刷新entry情况)
if (mResponse.intermediate) {
mRequest.addMarker("intermediate-response");
} else {
mRequest.finish("done");
}
// mRunnable不为空,则执行
if (mRunnable != null) {
mRunnable.run();
}
}
}
}
响应分发重担就传递到了request的身上:
public abstract class Request<T> implements Comparable<Request<T>> {
private final ErrorListener mErrorListener;
protected abstract void deliverResponse(T var1);
public void deliverError(VolleyError error) {
if(this.mErrorListener != null) {
this.mErrorListener.onErrorResponse(error);
}
}
}
deliverResponse是一个抽象类,StringRequest里面是这样实现的。Listener 是在我们使用的时候才创建传入到 request 中的,接口中的回调函数自然也是开发者来实现。也就是说我们添加到 requestqueue 中的请求经过一系列的处理得到最终的 response 或者 error,交给开发者来处理。到这里结果分发就分析结束了。
public class StringRequest extends Request<String> {
private final Listener mListener;
public StringRequest(int method, String url, Listener listener, ErrorListener errorListener) {
super(method, url, errorListener);
this.mListener = listener;
}
public StringRequest(String url, Listener listener, ErrorListener errorListener) {
this(0, url, listener, errorListener);
}
protected void deliverResponse(String response) {
this.mListener.onResponse(response);
}
protected Response 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));
}
}
都是调用的接口的方法,那谁来实现接口的方法呢。当然是使用 Volley 的我们,请看:
StringRequest stringRequest = new StringRequest("http://www.jianshu.com",
new Response.Listener<String>() {
@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);
}
});
调用RequestQueue#stop可以终止整个请求队列,并终止缓存请求线程与网络请求线程:
public void stop() {
if (mCacheDispatcher != null) {
mCacheDispatcher.quit();
}
for (int i = 0; i < mDispatchers.length; i++) {
if (mDispatchers[i] != null) {
mDispatchers[i].quit();
}
}
}
XXXDispatcher的quit方法会修改mQuit变量并调用interrupt使线程抛Interrupt异常,而Dispatcher捕获到异常后会判断mQuit变量最终while循环结束,线程退出。
public void run() {
Process.setThreadPriority(10);
while(true) {
Request request;
while(true) {
try {
request = (Request)this.mQueue.take();
break;
} catch (InterruptedException var4) {
if(this.mQuit) {
return;
}
}
}
}
调用Request的cancel可以取消一个请求。cancel方法很简单,仅将mCanceled变量置为true。而CacheDispatcher/NetworkDispatcher的run方法中在取到一个Request后会判断是否请求取消了:
public void run() {
Process.setThreadPriority(10);
while(true) {
Request request;
if(request.isCanceled()) {
request.finish("network-discard-cancelled");
} else {
}
}
}
如果请求取消就调用Request#finish,finish方法内部将调用与之绑定的请求队列的finish方法,该方法内部会将请求对象在队列中移除。
到这里 Volley 的主要流程就走了一遍了,继续加油。
参考了github上某个开源项目,在此基础上重新封装修改,实现了此类Volley网络请求框架,目前第一版本支持Json和String文本请求,支持扩展。
(1)使用方法,在 gradle 中引入:
compile 'com.guan.codelibs:volleyhttp:1.0.2'
public void login(View view) {
String url = "http://apis.juhe.cn/cook/query.php";
String APPKEY ="8fac966b379367b0e6f0527d634324ee";
Map<String,String> map = new HashMap();//请求参数
map.put("menu","龙眼");//需要查询的菜谱名
map.put("key",APPKEY);//应用APPKEY(应用详细页查询)
map.put("dtype","");
map.put("pn","");
map.put("rn","");
map.put("albums","");
Volley.sendRequest(map, url,
new Response.Listener<String>() {
@Override
public void onSuccess(String response) {
Log.e("tag", "onSuccess:" + response);
}
},
new Response.ErrorListener() {
@Override
public void onError(String error) {
Log.e("tag", "onError:" + error);
}
});
}
(2)源码地址
github项目地址:CodeLibs/lib-volleyhttp/
(3)类图分析
(5)设计模式
(1)使用方法
compile 'com.guan.codelibs:volleyhttp:1.0.2'
public void down(View view) {
DownFileRequest.downRequest("http://gdown.baidu.com/data/wisegame/8be18d2c0dc8a9c9/WPSOffice_177.apk", new IDownloadResponse() {
@Override
public void onDownloadStatusChanged(DownloadItemInfo downloadItemInfo) {
Log.e("tag", "下载状态:" + downloadItemInfo.getStatus());
}
@Override
public void onTotalLengthReceived(DownloadItemInfo downloadItemInfo) {
Log.e("tag", "下载接收");
}
@Override
public void onCurrentSizeChanged(DownloadItemInfo downloadItemInfo, double downLenth, long speed) {
Log.e("tag", "下载长度:" + downLenth + "-----速度:" + speed/1000 +"kb/s");
}
@Override
public void onDownloadSuccess(DownloadItemInfo downloadItemInfo) {
Log.e("tag", "下载成功" + "路径:" + downloadItemInfo.getFilePath() + "-----url:" + downloadItemInfo.getUrl());
}
@Override
public void onDownloadPause(DownloadItemInfo downloadItemInfo) {
Log.e("tag", "下载暂停:" + downloadItemInfo.getStatus());
}
@Override
public void onDownloadError(DownloadItemInfo downloadItemInfo, int var2, String var3) {
Log.e("tag", "下载出错");
}
});
}
(2)类图分析
Volley 会将每次通过网络请求到的数据,采用FileOutputStream,写入到本地的文件中。这个缓存文件,是声明在一个SD卡文件夹中的(也可以是getCacheFile())。如果不停的请求网络数据,这个缓存文件夹将无限制的增大,最终达到SD卡容量时,会发生无法写入的异常(因为存储空间满了)?
翻看代码 #3Volley源码解析#3.2请求队列(RequestQueue)的创建#(3)DiskBasedCache #put(String key, Entry entry)#pruneIfNeeded()。其中mMaxCacheSizeInBytes是构造方法传入的一个缓存文件夹的大小,如果不传默认是5M的大小。
private void pruneIfNeeded(int neededSpace) {
if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) {
return;
}
long before = mTotalSize;
int prunedFiles = 0;
long startTime = SystemClock.elapsedRealtime();
Iterator> iterator = mEntries.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = iterator.next();
CacheHeader e = entry.getValue();
// 调用了getFileForKey()
boolean deleted = getFileForKey(e.key).delete();
if (deleted) {
mTotalSize -= e.size;
} else {
//print log
}
iterator.remove();
prunedFiles++;
if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
break;
}
}
}
通过这个方法可以发现,每当被调用时会传入一个neededSpace,也就是需要申请的磁盘大小(即要新缓存的那个文件所需大小)。首先会判断如果这个neededSpace申请成功以后是否会超过最大可用容量,如果会超过,则通过遍历本地已经保存的缓存文件的header(header中包含了缓存文件的缓存有效期、占用大小等信息)去删除文件,直到可用容量不大于声明的缓存文件夹的大小。
如果让你去设计Volley的缓存功能,你要如何增大它的命中率?
还是上面的代码,在缓存内容可能超过缓存文件夹的大小时,删除的逻辑是直接遍历header删除。这个时候删除的文件有可能是我们上一次请求时刚刚保存下来的,屁股都还没坐稳呢,现在立即删掉,不好啊。如果遍历的时候,判断一下,首先删除超过缓存有效期的(过期缓存),其次按照LRU算法,删除最久未使用的,更合适。
为什么getFilenameForKey()会要把一个key分成两部分,分别求hashCode,最后又做拼接?
/**
* Returns a file object for the given cache key.
*/
public File getFileForKey(String key) {
return new File(mRootDirectory, getFilenameForKey(key));// 调用getFilenameForKey()
}
/**
* Creates a pseudo-unique filename for the specified cache key.
* @param key The key to generate a file name for.
* @return A pseudo-unique filename.
*/
private String getFilenameForKey(String key) {
int firstHalfLength = key.length() / 2;
String localFilename = String.valueOf(key.substring(0, firstHalfLength).hashCode());
localFilename += String.valueOf(key.substring(firstHalfLength).hashCode());
return localFilename;
}
先来看一下 String#hashCode() 的实现。String的hashcode是根据字符数组中每个位置的字母的int值再加上上次hash值乘以31,这种算法求出来的,至于为什么是31,我也不清楚。但是可以肯定一点,hashcode并不是唯一的。
@Override public int hashCode() {
int hash = hashCode;
if (hash == 0) {
if (count == 0) {
return 0;
}
final int end = count + offset;
final char[] chars = value;
for (int i = offset; i < end; ++i) {
hash = 31*hash + chars[i];
}
hashCode = hash;
}
return hash;
}
目的是为了尽可能避免hashcode重复造成的文件名重复(求两次hash两次都与另一个url重复的概率总要比一次重复的概率小)。不过概率小并不代表不存在,但是Java计算hashcode的速度是很快的,应该是在效率和安全性上取舍的结果。