Volley的使用十分简单,我们从网络请求开始说起,你只需要做4件事。
1、新建RequestQueue
2、新建Request对象及数据回调
3、把Request对象添加进RequestQueue
4、RequestQueue.Start();
如果你已经有了一个RequestQueue,直接做2、3步即可
RequestQueue mQueue = Volley.newRequestQueue(context);
StringRequest stringRequest = new StringRequest("http://www.baidu.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);
}
});
mQueue.add(stringReqauest);
这样就完成了一次网络请求并处理获取的数据
让我们来看看内部是怎么实现的
首先是获取请求队列RequestQueue
一般来说我们会使用Volley.newRequestQueue(context);
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, null);
}
//同上
return newRequestQueue(context, stack, -1);
我们来看这一段:
public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
String userAgent = "volley/0";
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
}
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
// Prior to Gingerbread, HttpUrlConnection was unreliable.
// See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
Network network = new BasicNetwork(stack);
RequestQueue queue;
if (maxDiskCacheBytes <= -1)
{
// No maximum size specified
queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
}
else
{
// Disk cache size specified
queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);
}
queue.start();
return queue;
}
这一段里面根据实际手机的Android版本创建了一个HttpStack,HttpUrlConnection在Android2.2以前有个bug,调用 close() 函数会影响连接池,导致连接复用失效,所以在 Froyo 之前使用 HttpURLConnection 需要关闭 keepAlive,详细的可以看上面注释里给出的链接。谷歌认为HttpUrlConnection更适合安卓,有兴趣可以看看这个:http://android-developers.blogspot.com/2011/09/androids-http-clients.html
然后创建一个network对象,并用network和stack对象作为参数生成RequestQueue返回给我们。
由于我们没有指定线程池的大小,默认会生成1个NetWorkDsispatcher(extendsThread
)数组,这个数组大小为4,同时文件缓存被指定为5MB大小。
这里Volley选择自己来管理线程池,而不是使用java提供的ThreadPool。
现在让我们来看看Request对象。
在Volley里面有多种Request,StringRequest,JsonObjectRequest等,里面的处理逻辑只在parseNetworkResponse这个方法中有较大差别,在这里实现数据类型转换,也就是说,如果你有需要,也可以自己继承Request来写一个自己的转换,例如XML解析什么的。
在上面StringRequest对象生成中,我们调用了:
public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {
this(Method.GET, url, listener, errorListener);
}
这里额外指定了以GET方法进行网络请求
public StringRequest(int method, String url, Listener<String> listener,
ErrorListener errorListener) {
super(method, url, errorListener);
mListener = listener;
}
然后把参数交由父类处理,自己处理成功回调mListener
那我们来看看父类
public Request(int method, String url, Response.ErrorListener listener) {
mMethod = method;
mUrl = url;
mIdentifier = createIdentifier(method, url);
mErrorListener = listener;
setRetryPolicy(new DefaultRetryPolicy());
mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
}
这里生成了一个唯一ID,并且设置了一个网络请求失败重试策略,这里默认不重新请求
现在让我们来看一下requestQueue.add()
public <T> Request<T> add(Request<T> request) {
// Tag the request as belonging to this queue and add it to the set of current requests.
request.setRequestQueue(this);
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
// Process requests in the order they are added.
// 设置一个队列位置,可作为排序用
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");
// If the request is uncacheable, skip the cache queue and go straight to the network.
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
// Insert request into stage if there's already a request with the same cache key in flight.
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
if (mWaitingRequests.containsKey(cacheKey)) {
// There is already a request in flight. Queue up.
Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
if (stagedRequests == null) {
stagedRequests = new LinkedList<Request<?>>();
}
stagedRequests.add(request);
mWaitingRequests.put(cacheKey, stagedRequests);
if (VolleyLog.DEBUG) {
VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
}
} else {
// Insert 'null' queue for this cacheKey, indicating there is now a request in
// flight.
mWaitingRequests.put(cacheKey, null);
mCacheQueue.add(request);
}
return request;
}
}
这个方法里面首先为这个request设置一个队列位置标识,然后判断是否需要缓存(默认为需要),若不需要则直接添加进网络请求队列并返回此request。
若此请求需要缓存,则根据此request的key从等待队列中查找是否已经有被缓存,如果之前已经请求过,就把它加进key对应的队列中。如果没有就把key加进等待队列,同时把request加进缓存队列。
我们来看最后一步RequestQueue.start();
public void start() {
stop(); // Make sure any currently running dispatchers are stopped.
// Create the cache dispatcher and start it.
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
// Create network dispatchers (and corresponding threads) up to the pool size.
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
这里面做的第一件事是把mCacheDispatcher线程停掉,并把线程池全停掉
public void stop() {
if (mCacheDispatcher != null) {
mCacheDispatcher.quit();
}
for (int i = 0; i < mDispatchers.length; i++) {
if (mDispatchers[i] != null) {
mDispatchers[i].quit();
}
}
}
mDispatchers就是一开始初始化RequestQueue时生成的NetWorkDsispatcher数组,大小为4。
然后重新为mCacheDispatcher赋值,我们先来看看这个mCacheDispatcher都干了些什么:
CacheDispatcher继承了Thread,重写其run()方法:
@Override
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher");
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// Make a blocking call to initialize the cache.
mCache.initialize();
Request<?> request;
while (true) {
// release previous request object to avoid leaking request object when mQueue is drained.
request = null;
try {
// Take a request from the queue.
// 这里使用了BlockingQueue实现的生产者消费者模式,request在RequestQueue.add()被调用时生产,//若Dispatchers线程已经被启动,而CacheQueue中已经没有request就会被阻塞
request = mCacheQueue.take();
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}
try {
request.addMarker("cache-queue-take");
// If the request has been canceled, don't bother dispatching it.
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}
// Attempt to retrieve this item from cache.
// 这个mCache就是生成ReqiuestQueue时作为参数的DiskBasedCache,Volley只实现了文件一级缓存,//有需要的可以自己加一个内存缓存
//如果文件缓存不存在就把这个request加入网络请求队列,继续执行下一个request
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
request.addMarker("cache-miss");
// Cache miss; send off to the network dispatcher.
mNetworkQueue.put(request);
continue;
}
// If it is completely expired, just send it to the network.
//缓存已经过期,进行网络请求
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
// We have a cache hit; parse its data for delivery back to the request.
<span style="white-space:pre"> </span> //缓存可用
request.addMarker("cache-hit");
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
<span style="white-space:pre"> </span> //缓存无须刷新就直接发送
if (!entry.refreshNeeded()) {
// Completely unexpired cache hit. Just deliver the response.
mDelivery.postResponse(request, response);
} else {
// Soft-expired cache hit. We can deliver the cached response,
// but we need to also send the request to the network for
// refreshing.
<span style="white-space:pre"> </span>//
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
// Mark the response as intermediate.
response.intermediate = true;
<span style="white-space:pre"> </span>//如果需要刷新就发送请求结果给用户,并重新请求数据
// Post the intermediate response back to the user and have
// the delivery then forward the request along to the network.
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());
}
}
}
总的来说在进行网络请求之前会先根据文件缓存进行判断,判断文件缓存是否已经过期,过期则直接进入网络请求队列,如果没过期但是已经不新鲜则先返回结果,然后进入网络请求队列刷新缓存。
关于文件缓存策略这一块还有有挺多可以说的,我们留到下一次再说。
然后我们再来看看网络请求这一块,也就是RequestQueue.start()余下的部分
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
这里分别为4个线程进行初始化,并传入工厂队列,在里面进行request的消费。
我们也来看一下NetworkDispatcher的实现。NetworkDispatcher继承了Thread类,重写run()方法:
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
可以看到这也是一个消费者,不停地从网络请求队列中取出reqeust进行网络请求,我们来看看mNetwork.performRequest(request);这里面做了什么
@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
long requestStart = SystemClock.elapsedRealtime();
while (true) {
HttpResponse httpResponse = null;
byte[] responseContents = null;
Map<String, String> responseHeaders = Collections.emptyMap();
try {
// Gather headers.
Map<String, String> headers = new HashMap<String, String>();
addCacheHeaders(headers, request.getCacheEntry());
<span style="white-space:pre"> </span>//mHttpStack是之前判断Android版本后生成的HttpClientStack或者HurlStack
httpResponse = mHttpStack.performRequest(request, headers);
StatusLine statusLine = httpResponse.getStatusLine();
int statusCode = statusLine.getStatusCode();
responseHeaders = convertHeaders(httpResponse.getAllHeaders());
// Handle cache validation.
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);
}
// A HTTP 304 response does not have all header fields. We
// have to use the header fields from the cache entry plus
// the new ones from the response.
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
entry.responseHeaders.putAll(responseHeaders);
return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
entry.responseHeaders, true,
SystemClock.elapsedRealtime() - requestStart);
}
// Handle moved resources
if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
String newUrl = responseHeaders.get("Location");
request.setRedirectUrl(newUrl);
}
// Some responses such as 204s do not have content. We must check.
if (httpResponse.getEntity() != null) {
responseContents = entityToBytes(httpResponse.getEntity());
} else {
// Add 0 byte response as a way of honestly representing a
// no-content request.
responseContents = new byte[0];
}
// if the request is slow, log it.
long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
logSlowRequests(requestLifetime, request, responseContents, statusLine);
if (statusCode < 200 || statusCode > 299) {
throw new IOException();
}
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);
}
}
}
}
我们大概地讲一下这方法都做了什么
首先是添加header
然后获取响应状态及状态码,如果是304(所请求的资源未修改),则直接读取缓存并返回给用户。
判断是否是为301、302、204(可能不返回信息)
记录响应时间
判断200~299,即为服务器没有正常处理该请求,则抛出异常
如果上面没有这几种情况发生,那么返回新的请求中的内容+头部以及状态码
这个时候我们应该继续去看之前被我们忽略的networkResponse处理,在此之前,我们先看看HurlStack如何获取response。
@Override
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError {
String url = request.getUrl();
HashMap<String, String> map = new HashMap<String, String>();
map.putAll(request.getHeaders());
map.putAll(additionalHeaders);
if (mUrlRewriter != null) {
//web技术,重写url,但是这里在创建HurlStack的时候传值为null,有需要可以自己传个进来
String rewritten = mUrlRewriter.rewriteUrl(url);
if (rewritten == null) {
throw new IOException("URL blocked by rewriter: " + url);
}
url = rewritten;
}
URL parsedUrl = new URL(url);
//获取connection,如果mSslSocketFactory不为空还会为其设置SSLSocketFactory,网络安全用
HttpURLConnection connection = openConnection(parsedUrl, request);
for (String headerName : map.keySet()) {
connection.addRequestProperty(headerName, map.get(headerName));
}
setConnectionParametersForRequest(connection, request);
// Initialize HttpResponse with data from the HttpURLConnection.
ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
int responseCode = connection.getResponseCode();
if (responseCode == -1) {
// -1 is returned by getResponseCode() if the response code could not be retrieved.
// Signal to the caller that something was wrong with the connection.
throw new IOException("Could not retrieve response code from HttpUrlConnection.");
}
//获取响应状态
StatusLine responseStatus = new BasicStatusLine(protocolVersion,
connection.getResponseCode(), connection.getResponseMessage());
BasicHttpResponse response = new BasicHttpResponse(responseStatus);
if (hasResponseBody(request.getMethod(), responseStatus.getStatusCode())) {
//获取响应中的实体
response.setEntity(entityFromConnection(connection));
}
for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
if (header.getKey() != null) {
//为响应结果添加header,也要用在缓存中
Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
response.addHeader(h);
}
}
return response;
}
这里要赞一下Volley这个框架,Http协议实践这一块写得超级棒,我们日常使用http连接时不会注意或使用的属性全都写上去,连协议版本都有,还保存、利用了header里缓存及其他各种属性,对于初学者来说是极好的示范。
我们继续来说一下这里面做了什么:
使用url打开连接,获取connection
设置connection的协议、超时时间、header
获取返回状态判断,返回包括header在内的数据
现在我们往回看一下在NetworkDispatcher中被我们忽略的剩余部分
@Override
public void run() {
<span style="white-space:pre"> </span> //前略
// Perform the network request.
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
// If the server returned 304 AND we delivered a response already,
// we're done -- don't deliver a second identical response.
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
//判断这个结果是否为304且响应已经被发送
//这里调用了finish之后,内部调用RequestQueue将request从队列中删除,并且根据cachekey将所有//相同请求全移除,加入到缓存队列中,这样就回到之前我们分析的缓存队列发现已经有缓存,则直接返//回缓存,不进行网络请求
request.finish("not-modified");
continue;
}
// Parse the response here on the worker thread.
//这里就是之前我们提到的进行数据处理的地方,如果自己想搞个xmlRequest就实现这个方法
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
// Write to cache if applicable.
// TODO: Only update cache metadata instead of entire record for 304s.
//如果请求允许缓存,且响应数据不为空.则缓存此请求
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
// Post the response back.
request.markDelivered();
//这个mDelivery是一个内含handler(主线程)的回调处理器,内部通过Executor发送线程,并在线程//内使用handler将回调发送到主线程
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);
}
}
}
上面解释了mNetwork.performRequest(request)通过其内部的basicNetWork及basicNetWork内部的HttpStack完成了网络请求及返回状态处理,现在我们来解释一下:
拿到返回结果之后判断请求是否有更改,即请求如果是相同的那就交给deliver分发去,并且不再处理相同请求
如果有更改且允许缓存,那就写入缓存,然后通过deliver发送结果。
那么到这里Volley的流程解析就差不多完结了,我们最后来全局描述一下这个结构
RequestQueue将加入的请求加入到mCacheQueue有缓存的情况下处理缓存,没有再加入到mNetworkQueue,采用生产者消费者模式使用线程池对其进行请求,通过BasicNetWork中的HtppStack进行真正的网络请求,并将结果返回,再由NetWorkDispatcher使用Deliver返回数据。
没图,就这样。
做了一点微小的工作。