卖钢材做哪个宣传网站,深圳网站开发外包公司,百度官方网页,网站导航栏固定Volley源码分析三部曲Volley 源码解析之网络请求Volley 源码解析之图片请求Volley 源码解析之缓存机制 Volley 是 Google 推出的一款网络通信框架#xff0c;非常适合数据量小、通信频繁的网络请求#xff0c;支持并发、缓存和容易扩展、调试等#xff1b;不过不太适合下载大…Volley源码分析三部曲Volley 源码解析之网络请求Volley 源码解析之图片请求Volley 源码解析之缓存机制 Volley 是 Google 推出的一款网络通信框架非常适合数据量小、通信频繁的网络请求支持并发、缓存和容易扩展、调试等不过不太适合下载大文件、大量数据的网络请求因为volley在解析期间将响应放到内存中我们可以使用okhttp或者系统提供的DownloadManager来下载文件。 一、简单使用 首先在工程引入volley的library dependencies {implementation com.android.volley:volley:1.1.1
}
复制代码然后需要我们打开网络权限我这里直接贴出官网简单请求的示例代码 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.ListenerString() {Overridepublic void onResponse(String response) {// Display the first 500 characters of the response string.mTextView.setText(Response is: response.substring(0,500));}
}, new Response.ErrorListener() {Overridepublic void onErrorResponse(VolleyError error) {mTextView.setText(That didnt 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会执行到创建BaseHttpStackBaseHttpStack是一个网络具体的处理请求Volley默认提供了基于HttpURLCollection的HurlStack和基于HttpClient的HttpClientStack。Android6.0移除了HttpClientGoogle官方推荐使用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_SIZE4个网络请求线程并运行一共就是5个线程在后台运行不断的等待网络请求的到来。 构造了RequestQueue之后我们调用add()方法将相应的Request传入就开始执行网络请求了我们看看这个方法 public T RequestT add(RequestT 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.EntryCache.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() {Overridepublic void run() {try {mNetworkQueue.put(request);} catch (InterruptedException e) {// Restore the interrupted statusThread.currentThread().interrupt();}}});} else {//这里第三个参数传递null不用再去分发因为已经有相同的请求已经在执行//直接添加到了等待请求的列表中然后返回的时候从已经执行的请求收到响应mDelivery.postResponse(request, response);}}
}
复制代码这部分主要是对请求的缓存判断是否过期以及需要刷新缓存。我们调用取消所有请求或者取消某个请求实质上就是对mCanceled这个变量赋值然后在缓存线程或者网络线程里面都回去判断这个值就完成了取消。上面的isExpired和refreshNeeded两个区别就是前者如果过期就直接请求最新的内容后者就是还在新鲜的时间内但是把内容返回给用户还是会发起请求两者一个与ttl值相比另一个与softTtl相比。 其中有一个WaitingRequestManager如果有相同的请求那么就需要一个暂存的地方这个类就是做的这个操作 private static class WaitingRequestManager implements Request.NetworkRequestCompleteListener {//所有等待请求的集合键是缓存的keyprivate final MapString, ListRequest? mWaitingRequests new HashMap();private final CacheDispatcher mCacheDispatcher;WaitingRequestManager(CacheDispatcher cacheDispatcher) {mCacheDispatcher cacheDispatcher;}//请求接受到一个有效的响应后面等待的相同请求就可以使用这个响应Overridepublic void onResponseReceived(Request? request, Response? response) {//如果缓存为空或者已经过期那么就释放等待的请求if (response.cacheEntry null || response.cacheEntry.isExpired()) {onNoUsableResponseReceived(request);return;}String cacheKey request.getCacheKey();//等待的请求的集合ListRequest? 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);}}}//没有收到相应则需要释放请求Overridepublic synchronized void onNoUsableResponseReceived(Request? request) {String cacheKey request.getCacheKey();ListRequest? 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(Couldnt 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.ListRequest? 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里面也会通过这个接口回调相应的值在这里执行最终比如在网络请求返回304、请求取消或者异常那么都会在这里来处理如果收到响应则会把值回调给用户后面的请求也不会再去请求如果无效的响应则会做一些释放等待的请求操作请求完成也会将后面相同的请求回调给用户三个方法都在不同的地方发挥作用。 我们接下来看看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;ListHeader 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值作对比一致则返回304MapString, 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);}// 将缓存头和响应头组合在一起一次响应就完成了ListHeader 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 ResponseString parseNetworkResponse(NetworkResponse response) {String parsed;try {parsed new String(response.data, HttpHeaderParser.parseCharset(response.headers));} catch (UnsupportedEncodingException e) {// Since minSdkVersion 8, we cant 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() {Overridepublic void execute(Runnable command) {handler.post(command);}};}public ExecutorDelivery(Executor executor) {mResponsePoster executor;}//这个方法就是我们NetworkDispatcher里面调用的方法调用下面这个三个参数的构造方法Overridepublic void postResponse(Request? request, Response? response) {postResponse(request, response, null);}Overridepublic void postResponse(Request? request, Response? response, Runnable runnable) {request.markDelivered();request.addMarker(post-response);//构造了一个ResponseDeliveryRunnable类传入execute现在这个runnable就是在主线程里执行mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));}Overridepublic 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)Overridepublic void run() {//请求取消那么就不分发给用户if (mRequest.isCanceled()) {mRequest.finish(canceled-at-delivery);return;}// 根据isSuccess这个值来提供相应的回调给用户调用Response会通过error的值是否为null来确定这个值//我们调用VolleyError这个构造函数的时候就为这个值就为falseif (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里首先是添加到缓存队列然后查看是否已经缓存如果有并且在有效期内的缓存直接回调给用户如果没有查找到那么则需要添加到网络请求队列重新请求并且解析响应、写入缓存在发送到主线程给用户回调。 参考以及相关链接 【第1250期】彻底理解浏览器的缓存机制Android Volley完全解析(四)带你从源码的角度理解VolleyVolley 源码解析Volley 源码解析转载于:https://juejin.im/post/5c1c58b35188251f1f320e70