Android——Volley框架学习总结
转载请注明本文出自changel的博客(http://www.cnblogs.com/caichongyang/p/4399790.html ),请尊重他人的辛勤劳动成果,谢谢!
Volley框架特点:
- 适用于频繁请求而每次请求数据量不会很大;
- 在请求的基础上做了磁盘缓存;
- 防止多次相同请求浪费资源;
- 提供String、Json、图片异步下载;
- 网络请求的优先级处理;
- 图片请求无需担心生命周期问题。
Volley框架使用:
- 首先,通过Volley的静态方法new一个请求队列
1 RequestQueue mQueue = Volley.newRequestQueue(context);
- 假如我们创建一个StringRequest实例(Volley提供,StringRequest、ImageRequest、JsonRequest。)
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); } });
- 将XXXRequest对象添加进队列中
mQueue.add(stringRequest);
- 调用RequestQueue的start方法就可以开始一条网络请求了
mQueue.start();
- 当然我们可以设置请求的方式:
StringRequest stringRequest = new StringRequest(Method.POST, url, listener, errorListener);
- 设置提交的参数:
StringRequest stringRequest = new StringRequest(Method.POST, url, listener, errorListener) { @Override protected Map<String, String> getParams() throws AuthFailureError { Map<String, String> map = new HashMap<String, String>(); map.put("params1", "value1"); map.put("params2", "value2"); return map; } };
- 至于其他的请求,读者可以自己去尝试。比如图片和Json的请求。(注意Volley还提供了自己的控件,com.android.volley.NetworkImageView,使用时我们无需担心相关网络请求的生命周期问题。)代码如下:
mImageLoader = new ImageLoader(mRequestQueue, new BitmapLruCache()); if(holder.imageRequest != null) { holder.imageRequest.cancel(); } holder.imageRequest = mImageLoader.get(BASE_UR + item.image_url, holder.imageView, R.drawable.loading, R.drawable.error);
mImageView.setImageUrl(url, imageLoader) //这里使用的imageView的控件是Volley提供的
Volley框架时序图:
自己画了一个Volley框架的时序图,帮助理解。Volley框架实现原理:
下面给出自己画的一张UML帮助理解,画得可能不是很准确。但至少整体的框架还是明确的。
这个框架我觉得最核心的部分就是RequestQueue这个请求队列,首先通过Volley.newRequestQueue(context)的方法就可以拿到一个RequestQueue对象。
这个请求队列主要是由两部分构成,一个是cache缓存,一个network网络请求。其实这两个只是一个接口而已。
我们一开始要往请求队列里面添加请求的任务,比如请求一些文字信息,那么就创建一个StringRequest对象,通过构造函数,将两个接口对象传递进去,进行绑定。
1 /** 2 * Adds a Request to the dispatch queue. 3 * @param request The request to service 4 * @return The passed-in request 5 */ 6 public <T> Request<T> add(Request<T> request) { 7 // Tag the request as belonging to this queue and add it to the set of current requests. 8 request.setRequestQueue(this); 9 synchronized (mCurrentRequests) { 10 mCurrentRequests.add(request); 11 } 12 13 // Process requests in the order they are added. 14 request.setSequence(getSequenceNumber()); 15 request.addMarker("add-to-queue"); 16 17 // If the request is uncacheable, skip the cache queue and go straight to the network. 18 if (!request.shouldCache()) { 19 mNetworkQueue.add(request); 20 return request; 21 } 22 23 // Insert request into stage if there's already a request with the same cache key in flight. 24 synchronized (mWaitingRequests) { 25 String cacheKey = request.getCacheKey(); 26 if (mWaitingRequests.containsKey(cacheKey)) { 27 // There is already a request in flight. Queue up. 28 Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey); 29 if (stagedRequests == null) { 30 stagedRequests = new LinkedList<Request<?>>(); 31 } 32 stagedRequests.add(request); 33 mWaitingRequests.put(cacheKey, stagedRequests); 34 if (VolleyLog.DEBUG) { 35 VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey); 36 } 37 } else { 38 // Insert 'null' queue for this cacheKey, indicating there is now a request in 39 // flight. 40 mWaitingRequests.put(cacheKey, null); 41 mCacheQueue.add(request); 42 } 43 return request; 44 } 45 }
(其实内部并不是简单的将Request的子类直接添加到队列中,而是通过mWaitingRequests,这个是一个MAP的集合,通过containsKey(cacheKey)来判断任务队列中是否已经存在了该请求任务,主要是避免重复请求。)
其实所有的请求对象都是继承于Request的抽象类,该抽象类还实现了Comparable 的接口,该接口是将该对象添加进PriorityBlockingQueue中必须实现的,然后Request的子类实现Request类中parseNetworkResponse()的抽象方法,这个方法是对请求返回的数据进行解析。我们可以将数据封装为一个对象,进行使用。
1 @Override 2 protected Response<String> parseNetworkResponse(NetworkResponse response) { 3 String parsed; 4 try { 5 parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); 6 } catch (UnsupportedEncodingException e) { 7 parsed = new String(response.data); 8 } 9 return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response)); 10 }
当调用RequestQueue对象内的start()方法的时候,具体是通过CacheDispatcher缓存调度和NetworkDispatcher网络调度去实现的。NetworkDispatcher这个就是网络调度的核心,它其实是一个Thread的子类。而CacheDispatcher也是一个Thread的子类。简单点说,NetworkDispatcher 线程的run()方法就是把请求队列取出一个Request,让Network去执行一次请求。这里说的并不是很准确,因为其实Cache和Network只是两个接口,NetWork接口定义了一个请求的方法,真正的实现类还是BasicNetwork,所以具体的网络请求是在BasicNetwork里面的performRequest方法去实现的。下面我贴出这个方法的源码:
1 public NetworkResponse performRequest(Request<?> request) throws VolleyError { 2 long requestStart = SystemClock.elapsedRealtime(); 3 while (true) { 4 HttpResponse httpResponse = null; 5 byte[] responseContents = null; 6 Map<String, String> responseHeaders = Collections.emptyMap(); 7 try { 8 // Gather headers. 9 Map<String, String> headers = new HashMap<String, String>(); 10 addCacheHeaders(headers, request.getCacheEntry()); 11 httpResponse = mHttpStack.performRequest(request, headers); 12 StatusLine statusLine = httpResponse.getStatusLine(); 13 int statusCode = statusLine.getStatusCode(); 14 15 responseHeaders = convertHeaders(httpResponse.getAllHeaders()); 16 // Handle cache validation. 17 if (statusCode == HttpStatus.SC_NOT_MODIFIED) { 18 19 Entry entry = request.getCacheEntry(); 20 if (entry == null) { 21 return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null, 22 responseHeaders, true, 23 SystemClock.elapsedRealtime() - requestStart); 24 } 25 26 // A HTTP 304 response does not have all header fields. We 27 // have to use the header fields from the cache entry plus 28 // the new ones from the response. 29 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5 30 entry.responseHeaders.putAll(responseHeaders); 31 return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data, 32 entry.responseHeaders, true, 33 SystemClock.elapsedRealtime() - requestStart); 34 } 35 36 // Handle moved resources 37 if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY) { 38 String newUrl = responseHeaders.get("Location"); 39 request.setRedirectUrl(newUrl); 40 } 41 42 // Some responses such as 204s do not have content. We must check. 43 if (httpResponse.getEntity() != null) { 44 responseContents = entityToBytes(httpResponse.getEntity()); 45 } else { 46 // Add 0 byte response as a way of honestly representing a 47 // no-content request. 48 responseContents = new byte[0]; 49 } 50 51 // if the request is slow, log it. 52 long requestLifetime = SystemClock.elapsedRealtime() - requestStart; 53 logSlowRequests(requestLifetime, request, responseContents, statusLine); 54 55 if (statusCode < 200 || statusCode > 299) { 56 throw new IOException(); 57 } 58 return new NetworkResponse(statusCode, responseContents, responseHeaders, false, 59 SystemClock.elapsedRealtime() - requestStart); 60 } catch (SocketTimeoutException e) { 61 attemptRetryOnException("socket", request, new TimeoutError()); 62 } catch (ConnectTimeoutException e) { 63 attemptRetryOnException("connection", request, new TimeoutError()); 64 } catch (MalformedURLException e) { 65 throw new RuntimeException("Bad URL " + request.getUrl(), e); 66 } catch (IOException e) { 67 int statusCode = 0; 68 NetworkResponse networkResponse = null; 69 if (httpResponse != null) { 70 statusCode = httpResponse.getStatusLine().getStatusCode(); 71 } else { 72 throw new NoConnectionError(e); 73 } 74 if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || 75 statusCode == HttpStatus.SC_MOVED_TEMPORARILY) { 76 VolleyLog.e("Request at %s has been redirected to %s", request.getOriginUrl(), request.getUrl()); 77 } else { 78 VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl()); 79 } 80 if (responseContents != null) { 81 networkResponse = new NetworkResponse(statusCode, responseContents, 82 responseHeaders, false, SystemClock.elapsedRealtime() - requestStart); 83 if (statusCode == HttpStatus.SC_UNAUTHORIZED || 84 statusCode == HttpStatus.SC_FORBIDDEN) { 85 attemptRetryOnException("auth", 86 request, new AuthFailureError(networkResponse)); 87 } else if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || 88 statusCode == HttpStatus.SC_MOVED_TEMPORARILY) { 89 attemptRetryOnException("redirect", 90 request, new AuthFailureError(networkResponse)); 91 } else { 92 // TODO: Only throw ServerError for 5xx status codes. 93 throw new ServerError(networkResponse); 94 } 95 } else { 96 throw new NetworkError(networkResponse); 97 } 98 } 99 } 100 }
而且这个方法有两种访问服务器的方法,一种是基于HttpClient去实现的,一种是HttpURLConnection去实现的。这么做是为了适配不同Sdk版本的对网络请求的要求。在创建BasicNetwork的时候,会判断当前的版本,去选择哪种方式。在Volley类的静态方法里面进行判断。源码如下:
1 public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {
2 File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
3
4 String userAgent = "volley/0";
5 try {
6 String packageName = context.getPackageName();
7 PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
8 userAgent = packageName + "/" + info.versionCode;
9 } catch (NameNotFoundException e) {
10 }
11
12 if (stack == null) {
13 if (Build.VERSION.SDK_INT >= 9) {
14 stack = new HurlStack();
15 } else {
16 // Prior to Gingerbread, HttpUrlConnection was unreliable.
17 // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
18 stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
19 }
20 }
21
22 Network network = new BasicNetwork(stack);
23
24 RequestQueue queue;
25 if (maxDiskCacheBytes <= -1)
26 {
27 // No maximum size specified
28 queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
29 }
30 else
31 {
32 // Disk cache size specified
33 queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);
34 }
35
36 queue.start();
37
38 return queue;
39 }
1 @Override 2 public synchronized void put(String key, Entry entry) { 3 pruneIfNeeded(entry.data.length); 4 File file = getFileForKey(key); 5 try { 6 FileOutputStream fos = new FileOutputStream(file); 7 CacheHeader e = new CacheHeader(key, entry); 8 boolean success = e.writeHeader(fos); 9 if (!success) { 10 fos.close(); 11 VolleyLog.d("Failed to write header for %s", file.getAbsolutePath()); 12 throw new IOException(); 13 } 14 fos.write(entry.data); 15 fos.close(); 16 putEntry(key, e); 17 return; 18 } catch (IOException e) { 19 } 20 boolean deleted = file.delete(); 21 if (!deleted) { 22 VolleyLog.d("Could not clean up file %s", file.getAbsolutePath()); 23 } 24 }
1 @Override 2 public synchronized Entry get(String key) { 3 CacheHeader entry = mEntries.get(key); 4 // if the entry does not exist, return. 5 if (entry == null) { 6 return null; 7 } 8 9 File file = getFileForKey(key); 10 CountingInputStream cis = null; 11 try { 12 cis = new CountingInputStream(new FileInputStream(file)); 13 CacheHeader.readHeader(cis); // eat header 14 byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead)); 15 return entry.toCacheEntry(data); 16 } catch (IOException e) { 17 VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString()); 18 remove(key); 19 return null; 20 } finally { 21 if (cis != null) { 22 try { 23 cis.close(); 24 } catch (IOException ioe) { 25 return null; 26 } 27 } 28 } 29 }
@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(); while (true) { try { // Get a request from the cache triage queue, blocking until // at least one is available. final Request<?> request = mCacheQueue.take(); 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. 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. request.addMarker("cache-hit"); Response<?> response = request.parseNetworkResponse( new NetworkResponse(entry.data, entry.responseHeaders)); request.addMarker("cache-hit-parsed"); 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. request.addMarker("cache-hit-refresh-needed"); request.setCacheEntry(entry); // Mark the response as intermediate. response.intermediate = true; // Post the intermediate response back to the user and have // the delivery then forward the request along to the network. mDelivery.postResponse(request, response, new Runnable() { @Override public void run() { try { mNetworkQueue.put(request); } catch (InterruptedException e) { // Not much we can do about this. } } }); } } catch (InterruptedException e) { // We may have been interrupted because it was time to quit. if (mQuit) { return; } continue; } } }
在阅读的时候,注意下request.addMarker("*********");这些方法的调用,其实是对request设置一些标签,这个在后面会依据这些标签,对request的状态进行判断,主要的思路就是先对请求的任务在磁盘中查找,查找不到就将这个请求任务put进网络请求的队列中(在这里我们可以对这个框架进行优化,在堆磁盘查找的前,我们可以用一个数据结构来将一些数据保存在内存中,每次查找时先查询内存缓存,没有再在磁盘中查找。)request.addMarker("cache-hit-expired");这个就是标志着在磁盘中没有该缓存,所以接下来的代码会将请求添加到网络请求队列中。 mNetworkQueue.put(request);
1 /** 2 * A Runnable used for delivering network responses to a listener on the 3 * main thread. 4 */ 5 @SuppressWarnings("rawtypes") 6 private class ResponseDeliveryRunnable implements Runnable { 7 private final Request mRequest; 8 private final Response mResponse; 9 private final Runnable mRunnable; 10 11 public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) { 12 mRequest = request; 13 mResponse = response; 14 mRunnable = runnable; 15 } 16 17 @SuppressWarnings("unchecked") 18 @Override 19 public void run() { 20 // If this request has canceled, finish it and don't deliver. 21 if (mRequest.isCanceled()) { 22 mRequest.finish("canceled-at-delivery"); 23 return; 24 } 25 26 // Deliver a normal response or error, depending. 27 if (mResponse.isSuccess()) { 28 mRequest.deliverResponse(mResponse.result); 29 } else { 30 mRequest.deliverError(mResponse.error); 31 } 32 33 // If this is an intermediate response, add a marker, otherwise we're done 34 // and the request can be finished. 35 if (mResponse.intermediate) { 36 mRequest.addMarker("intermediate-response"); 37 } else { 38 mRequest.finish("done"); 39 } 40 41 // If we have been provided a post-delivery runnable, run it. 42 if (mRunnable != null) { 43 mRunnable.run(); 44 } 45 } 46 }
值得注意的是,ExecutorDelivery对象里面还有一个handler,它是获取了主线程的looper,主要是为了及时实现与UI界面进行交互。比如在我们请求获取图片类型数据的时候。
Volley框架的不足:
- 它只适用于频繁而数据量小的请求。(当请求的是比较大的数据时,这个Volley框架发挥的作用就不是很大了。)
- 它只实现了一级缓存(磁盘缓存)。(这样做虽然节省内存资源的消耗,但是读写数据的速度会比较慢。我们可以在这个基础上进行优化,多加一级内存缓存,实现多级缓存。读者可以自行对这个框架进行优化。对于缓存机制,个人觉得可以学习下Universal image loader这个框架,里面有几种缓存机制写得很好。)。
好了,以上就是我对Volley框架的学习和理解,希望这篇文章能帮到那些对Volley这个框架还不是很熟悉的朋友们,当然也希望可以抛砖引玉的作用。大家对这篇文章有什么看法或者评价,欢迎评论交流交流。