Volley
源码分析三部曲
Volley 源码解析之网络请求
Volley 源码解析之图片请求
Volley 源码解析之缓存机制java
Volley 是 Google 推出的一款网络通讯框架,很是适合数据量小、通讯频繁的网络请求,支持并发、缓存和容易扩展、调试等;不过不太适合下载大文件、大量数据的网络请求,由于volley在解析期间将响应放到内存中,咱们可使用okhttp或者系统提供的
DownloadManager
来下载文件。android
首先在工程引入volley的library:git
dependencies {
implementation 'com.android.volley:volley:1.1.1'
}
复制代码
而后须要咱们打开网络权限,我这里直接贴出官网简单请求的示例代码:github
final TextView mTextView = (TextView) findViewById(R.id.text);
// ...
// Instantiate the RequestQueue.
RequestQueue queue = Volley.newRequestQueue(this);
String url ="http://www.google.com";
// Request a string response from the provided URL.
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// Display the first 500 characters of the response string.
mTextView.setText("Response is: "+ response.substring(0,500));
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
mTextView.setText("That didn't work!");
}
});
// Add the request to the RequestQueue.
queue.add(stringRequest);
复制代码
使用相对简单,回调直接在主线程,咱们取消某个请求直接这样操做:数组
定义一个标记添加到requests中浏览器
public static final String TAG = "MyTag";
StringRequest stringRequest; // Assume this exists.
RequestQueue mRequestQueue; // Assume this exists.
// Set the tag on the request.
stringRequest.setTag(TAG);
// Add the request to the RequestQueue.
mRequestQueue.add(stringRequest);
复制代码
而后咱们能够在 onStop() 中取消全部标记的请求缓存
@Override
protected void onStop () {
super.onStop();
if (mRequestQueue != null) {
mRequestQueue.cancelAll(TAG);
}
}
复制代码
咱们先从Volley这个类入手:安全
public static RequestQueue newRequestQueue(Context context, BaseHttpStack stack) {
BasicNetwork network;
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
network = new BasicNetwork(new HurlStack());
} else {
String userAgent = "volley/0";
try {
String packageName = context.getPackageName();
PackageInfo info =
context.getPackageManager().getPackageInfo(packageName, /* flags= */ 0);
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
}
network =
new BasicNetwork(
new HttpClientStack(AndroidHttpClient.newInstance(userAgent)));
}
} else {
network = new BasicNetwork(stack);
}
return newRequestQueue(context, network);
}
private static RequestQueue newRequestQueue(Context context, Network network) {
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, (BaseHttpStack) null);
}
复制代码
当咱们传递一个Context
的时候,首先为BaseHttpStack
为null,会执行到建立BaseHttpStack
,BaseHttpStack
是一个网络具体的处理请求,Volley
默认提供了基于HttpURLCollection
的HurlStack
和基于HttpClient
的HttpClientStack
。Android6.0移除了HttpClient
,Google官方推荐使用HttpURLCollection
类做为替换。因此这里在API大于9的版本是用的是HurlStack
,为何这样选择,详情可见这篇博客Android访问网络,使用HttpURLConnection仍是HttpClient?。咱们使用的是默认的构造,BaseHttpStack
传入为null,若是咱们想使用自定义的okhttp替换底层,咱们直接继承HttpStack
重写便可,也能够自定义Network
和RequestQueue
,Volley
的高扩展性充分体现。接下来则建立一个Network
对象,而后实例化RequestQueue
,首先建立了一个用于缓存的文件夹,而后建立了一个磁盘缓存,将文件缓存到指定目录的硬盘上,默认大小是5M,可是大小能够配置。接下来调用RequestQueue
的start()
方法进行启动,咱们进入这个方法查看一下:服务器
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();
}
}
复制代码
开始启动的时候先中止全部的请求线程和网络缓存线程,而后实例化一个缓存线程并运行,而后一个循环开启DEFAULT_NETWORK_THREAD_POOL_SIZE
(4)个网络请求线程并运行,一共就是5个线程在后台运行,不断的等待网络请求的到来。网络
构造了RequestQueue
以后,咱们调用add()
方法将相应的Request
传入就开始执行网络请求了,咱们看看这个方法:
public <T> Request<T> add(Request<T> request) {
//将请求队列和请求关联起来
request.setRequestQueue(this);
//添加到正在请求中可是还未完成的集合中
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
//设置请求的一个序列号,经过原子变量的incrementAndGet方法,
//以原子方式给当前值加1并获取新值实现请求的优先级
request.setSequence(getSequenceNumber());
//添加一个调试信息
request.addMarker("add-to-queue");
//若是不须要缓存则直接加到网络的请求队列,默认每个请求都是缓存的,
//若是不须要缓存须要调用Request的setShouldCache方法来修改
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
//加到缓存的请求队列
mCacheQueue.add(request);
return request;
}
复制代码
关键地方都写了注释,主要做用就是将请求加到请求队列,执行网络请求或者从缓存中获取结果。网络和缓存的请求都是一个优先级阻塞队列,按照优先级出队。上面几个关键步骤,添加到请求集合里面还有设置优先级以及添加到缓存和请求队列都是线程安全的,要么加锁,要么使用线程安全的队列或者原子操做。
接下来咱们看看添加到CacheDispatcher
缓存请求队列的run
方法:
@Override
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher");
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//初始化DiskBasedCache的缓存类
mCache.initialize();
while (true) {
try {
processRequest();
} catch (InterruptedException e) {
if (mQuit) {
Thread.currentThread().interrupt();
return;
}
VolleyLog.e(
"Ignoring spurious interrupt of CacheDispatcher thread; "
+ "use quit() to terminate it");
}
}
}
复制代码
接下来的重点是看看processRequest()
这个方法:
private void processRequest() throws InterruptedException {
//从缓存队列取出请求
final Request<?> request = mCacheQueue.take();
processRequest(request);
}
@VisibleForTesting
void processRequest(final Request<?> request) throws InterruptedException {
request.addMarker("cache-queue-take");
// 若是请求被取消,咱们能够经过RequestQueue的回调接口来监听
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
return;
}
// 从缓存中获取Cache.Entry
Cache.Entry entry = mCache.get(request.getCacheKey());
//没有取到缓存
if (entry == null) {
request.addMarker("cache-miss");
// 缓存未命中,对于可缓存的请求先去检查是否有相同的请求是否已经在运行中,
//若是有的话先加入请求等待队列,等待请求完成,返回true;若是返回false则表示第一次请求
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
//加入到网络请求的阻塞队列
mNetworkQueue.put(request);
}
return;
}
// 若是缓存彻底过时,处理过程跟上面相似
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
//设置请求缓存的entry到这个request中
request.setCacheEntry(entry);
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
mNetworkQueue.put(request);
}
return;
}
//缓存命中,将数据解析并返回到request的抽象方法中
request.addMarker("cache-hit");
Response<?> response =
request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
//判断请求结果是否须要刷新
if (!entry.refreshNeeded()) {
// 未过时的缓存命中,经过ExecutorDelivery回调给咱们的request子类的接口中,
// 咱们在使用的时候就能够经过StringRequest、JsonRequest等拿到结果,
// 切换到主线程也是在这个类里执行的
mDelivery.postResponse(request, response);
} else {
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
// 将这个响应标记为中间值,即这个响应是新鲜的,那么第二个响应正在请求随时到来
response.intermediate = true;
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
//发起网络请求,这里为何直接调用上面的mNetworkQueue.put(request);呢,
//主要是为了添加一个已经分发的标记,在响应分发的时候再也不回调给用户,
//否则就回调了两次
mDelivery.postResponse(
request,
response,
new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(request);
} catch (InterruptedException e) {
// Restore the interrupted status
Thread.currentThread().interrupt();
}
}
});
} else {
//这里第三个参数传递null,不用再去分发,由于已经有相同的请求已经在执行,
//直接添加到了等待请求的列表中,而后返回的时候从已经执行的请求收到响应
mDelivery.postResponse(request, response);
}
}
}
复制代码
这部分主要是对请求的缓存判断,是否过时以及须要刷新缓存。咱们调用取消全部请求或者取消某个请求实质上就是对mCanceled
这个变量赋值,而后在缓存线程或者网络线程里面都回去判断这个值,就完成了取消。上面的isExpired
和refreshNeeded
,两个区别就是,前者若是过时就直接请求最新的内容,后者就是还在新鲜的时间内,可是把内容返回给用户仍是会发起请求,二者一个与ttl值相比,另外一个与softTtl相比。
其中有一个WaitingRequestManager,若是有相同的请求那么就须要一个暂存的地方,这个类就是作的这个操做
private static class WaitingRequestManager implements Request.NetworkRequestCompleteListener {
//全部等待请求的集合,键是缓存的key
private final Map<String, List<Request<?>>> mWaitingRequests = new HashMap<>();
private final CacheDispatcher mCacheDispatcher;
WaitingRequestManager(CacheDispatcher cacheDispatcher) {
mCacheDispatcher = cacheDispatcher;
}
//请求接受到一个有效的响应,后面等待的相同请求就可使用这个响应
@Override
public void onResponseReceived(Request<?> request, Response<?> response) {
//若是缓存为空或者已通过期,那么就释放等待的请求
if (response.cacheEntry == null || response.cacheEntry.isExpired()) {
onNoUsableResponseReceived(request);
return;
}
String cacheKey = request.getCacheKey();
//等待的请求的集合
List<Request<?>> waitingRequests;
synchronized (this) {
//从map里面移除这个请求的集合
waitingRequests = mWaitingRequests.remove(cacheKey);
}
if (waitingRequests != null) {
if (VolleyLog.DEBUG) {
VolleyLog.v(
"Releasing %d waiting requests for cacheKey=%s.",
waitingRequests.size(), cacheKey);
}
// 里面全部的请求都分发到相应的回调执行,下面会讲解
for (Request<?> waiting : waitingRequests) {
mCacheDispatcher.mDelivery.postResponse(waiting, response);
}
}
}
//没有收到相应,则须要释放请求
@Override
public synchronized void onNoUsableResponseReceived(Request<?> request) {
String cacheKey = request.getCacheKey();
List<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);
if (waitingRequests != null && !waitingRequests.isEmpty()) {
if (VolleyLog.DEBUG) {
VolleyLog.v(
"%d waiting requests for cacheKey=%s; resend to network",
waitingRequests.size(), cacheKey);
}
//下面这个请求执会从新执行,将这个移除添加到
Request<?> nextInLine = waitingRequests.remove(0);
//将剩下的请求放到等待请求的map中
mWaitingRequests.put(cacheKey, waitingRequests);
//在request里面注册一个回调接口,由于从新开始请求,须要从新注册一个监听,
//后面请求成功失败以及取消均可以收到回调
nextInLine.setNetworkRequestCompleteListener(this);
try {
//从上面if判断方法能够得出:waitingRequests != null && !waitingRequests.isEmpty()
//排除了第一次请求失败、取消的状况,后面的那个条件则表示这个等待请求队列必需要有一个请求,
//同时知足才会执行这里面的代码,通常只要这里面的请求执行成功一次后续全部的请求都会被移除,
//因此这里对多个请求的状况,失败一次,那么后续的请求会继续执行
mCacheDispatcher.mNetworkQueue.put(nextInLine);
} catch (InterruptedException iex) {
VolleyLog.e("Couldn't add request to queue. %s", iex.toString());
// Restore the interrupted status of the calling thread (i.e. NetworkDispatcher)
Thread.currentThread().interrupt();
// Quit the current CacheDispatcher thread.
mCacheDispatcher.quit();
}
}
}
//对于能够缓存的请求,相同缓存的请求已经在运行中就添加到一个发送队列,
//等待运行中的队列请求完成,返回true表示已经有请求在运行,false则是第一次执行
private synchronized boolean maybeAddToWaitingRequests(Request<?> request) {
String cacheKey = request.getCacheKey();
// 存在相同的请求则把请求加入到相同缓存键的集合中
if (mWaitingRequests.containsKey(cacheKey)) {
// There is already a request in flight. Queue up.
List<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
//若是包含相同的请求可是有多是第二次请求,前面第一次请求插入null了
if (stagedRequests == null) {
stagedRequests = new ArrayList<>();
}
request.addMarker("waiting-for-response");
stagedRequests.add(request);
mWaitingRequests.put(cacheKey, stagedRequests);
if (VolleyLog.DEBUG) {
VolleyLog.d("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
}
return true;
} else {
//第一次请求那么则插入一个null,表示当前有一个请求正在运行
mWaitingRequests.put(cacheKey, null);
//注册一个接口监听
request.setNetworkRequestCompleteListener(this);
if (VolleyLog.DEBUG) {
VolleyLog.d("new request, sending to network %s", cacheKey);
}
return false;
}
}
}
复制代码
这个类主要是避免相同的请求屡次请求,并且在NetworkDispatcher
里面也会经过这个接口回调相应的值在这里执行,最终好比在网络请求返回30四、请求取消或者异常那么都会在这里来处理,若是收到响应则会把值回调给用户,后面的请求也不会再去请求,若是无效的响应则会作一些释放等待的请求操做,请求完成也会将后面相同的请求回调给用户,三个方法都在不一样的地方发挥做用。
咱们接下来看看NetworkDispatcher
网络请求队列的run
方法中的processRequest
方法:
@VisibleForTesting
void processRequest(Request<?> request) {
long startTimeMs = SystemClock.elapsedRealtime();
try {
request.addMarker("network-queue-take");
// 请求被取消了,就不执行网络请求,
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
request.notifyListenerResponseNotUsable();
return;
}
addTrafficStatsTag(request);
// 这里就是执行网络请求的地方
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
// 若是服务器返回304响应,即没有修改过,
//缓存依然是有效的而且是在须要刷新的有效期内,那么则不须要解析响应
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
//没有收到来自网络的有效响应,释放请求
request.notifyListenerResponseNotUsable();
return;
}
// 在工做线程中解析这些响应
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
// 将缓存写入到应用
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
// 标记此请求已将分发
request.markDelivered();
//将请求的响应回调给用户
mDelivery.postResponse(request, response);
//请求接受到了一个响应,其余相同的请求可使用这个响应
request.notifyListenerResponseReceived(response);
} catch (VolleyError volleyError) {
...
}
}
复制代码
这里才是网络请求的真正执行以及解析分发的地方,重点看两个地方的代码,执行和解析,咱们先看看执行网络请求这个代码,执行的地方是BasicNetwork.performRequest
,下面看看这个方法:
@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
long requestStart = SystemClock.elapsedRealtime();
while (true) {
HttpResponse httpResponse = null;
byte[] responseContents = null;
List<Header> responseHeaders = Collections.emptyList();
try {
// 构造缓存的头部,添加If-None-Match和If-Modified-Since,都是http/1.1中控制协商缓存的两个字段,
// If-None-Match:客服端再次发起请求时,携带上次请求返回的惟一标识Etag值,
//服务端用携带的值和最后修改的值做对比,最后修改时间大于携带的字段值则返回200,不然304;
// If-Modified-Since:客服端再次发起请求时,携带上次请求返回的Last-Modified值,
//服务端用携带的值和服务器的Etag值做对比,一致则返回304
Map<String, String> additionalRequestHeaders =
getCacheHeaders(request.getCacheEntry());
//由于如今通常的sdk都是大于9的,那么这里执行的就是HurlStack的executeRequest方法,
//执行网络请求,和咱们平时使用HttpURLConnection请求网络大体相同
httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders);
int statusCode = httpResponse.getStatusCode();
responseHeaders = httpResponse.getHeaders();
// 服务端返回304时,那么就表示资源无更新,能够继续使用缓存的值
if (statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
Entry entry = request.getCacheEntry();
if (entry == null) {
return new NetworkResponse(
HttpURLConnection.HTTP_NOT_MODIFIED,
/* data= */ null,
/* notModified= */ true,
SystemClock.elapsedRealtime() - requestStart,
responseHeaders);
}
// 将缓存头和响应头组合在一块儿,一次响应就完成了
List<Header> combinedHeaders = combineHeaders(responseHeaders, entry);
return new NetworkResponse(
HttpURLConnection.HTTP_NOT_MODIFIED,
entry.data,
/* notModified= */ true,
SystemClock.elapsedRealtime() - requestStart,
combinedHeaders);
}
// 若是返回204,执行成功,没有数据,这里须要检查
InputStream inputStream = httpResponse.getContent();
if (inputStream != null) {
responseContents =
inputStreamToBytes(inputStream, httpResponse.getContentLength());
} else {
//返回204,就返回一个空的byte数组
responseContents = new byte[0];
}
// if the request is slow, log it.
long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
logSlowRequests(requestLifetime, request, responseContents, statusCode);
if (statusCode < 200 || statusCode > 299) {
throw new IOException();
}
return new NetworkResponse(
statusCode,
responseContents,
/* notModified= */ false,
SystemClock.elapsedRealtime() - requestStart,
responseHeaders);
} catch (SocketTimeoutException e) {
//异常进行从新请求等...
}
}
}
复制代码
这里主要执行了添加缓存头并发起网络请求,而后将返回值组装成一个NetworkResponse
值返回,接下来咱们看看是如何解析这个值的,解析是由Request
的子类去实现的,咱们就看系统提供的StringRequest
:
@Override
@SuppressWarnings("DefaultCharset")
protected Response<String> parseNetworkResponse(NetworkResponse response) {
String parsed;
try {
parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
} catch (UnsupportedEncodingException e) {
// Since minSdkVersion = 8, we can't call
// new String(response.data, Charset.defaultCharset())
// So suppress the warning instead.
parsed = new String(response.data);
}
return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}
复制代码
咱们能够看到将值组装成一个String,而后组装成一个Response
返回,接下来看看这里如何将这个值回调给用户的这个方法mDelivery.postResponse(request, response)
,这里咱们先重点看看这个类ExecutorDelivery
:
public class ExecutorDelivery implements ResponseDelivery {
//构造执行已提交的Runnable任务对象
private final Executor mResponsePoster;
//这里在RequestQueue构造参数中初始化,new ExecutorDelivery(new Handler(Looper.getMainLooper())),
//那么这里runnable就经过绑定主线程的Looper的Handler对象投递到主线程中执行
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);
}
};
}
public ExecutorDelivery(Executor executor) {
mResponsePoster = executor;
}
//这个方法就是咱们NetworkDispatcher里面调用的方法,调用下面这个三个参数的构造方法
@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");
//构造了一个ResponseDeliveryRunnable类,传入execute,如今这个runnable就是在主线程里执行
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));
}
/** A Runnable used for delivering network responses to a listener on the main thread. */
@SuppressWarnings("rawtypes")
private static 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;
}
@SuppressWarnings("unchecked")
@Override
public void run() {
//请求取消,那么就不分发给用户
if (mRequest.isCanceled()) {
mRequest.finish("canceled-at-delivery");
return;
}
// 根据isSuccess这个值来提供相应的回调给用户,调用Response会经过error的值是否为null来肯定这个值,
//咱们调用VolleyError这个构造函数的时候就为这个值就为false
if (mResponse.isSuccess()) {
mRequest.deliverResponse(mResponse.result);
} else {
mRequest.deliverError(mResponse.error);
}
// 若是这是一个在新鲜的时间内的请求的响应,就添加一个标记,不然就结束
if (mResponse.intermediate) {
mRequest.addMarker("intermediate-response");
} else {
mRequest.finish("done");
}
// 在CacheDispatcher里面当请求第一次请求时直接调用三个参数的构造方法,经过这个runnable就执行run方法
if (mRunnable != null) {
mRunnable.run();
}
}
}
}
复制代码
上面方法主要是将值回调给用户,那么整个网络请求大体就完成了,其中还涉及不少细节的东西,可是大体流程是走通了,不得不说这个库有不少值得咱们学习的地方。
如今咱们看官网的一张图,总结一下整个流程:
咱们能够看到首先是请求添加到RequestQueue
里,首先是添加到缓存队列,而后查看是否已经缓存,若是有而且在有效期内的缓存直接回调给用户,若是没有查找到,那么则须要添加到网络请求队列从新请求而且解析响应、写入缓存在发送到主线程给用户回调。