Volley介绍
Volley 是 Google 在 2013 Google I/O 大会上发布推出的 Android 异步网络请求框架和图片加载框架,其最明显的一个优点就是特别适合数据量不大但是通信频繁的场景,最明显的缺点就是大数据传输表现的很糟糕。(适合于数据量小,通信频繁的网络操作)
Volley提供了以下的便利功能
- JSON数据和图像等的异步下载;
- 网络请求排序(scheduling);
- 网络请求优先级处理;
- 缓存;
- 多级别取消请求;
- 与Activity生命周期联动(Activity结束时同时取消所有网络请求);
newRequestQueue的使用
Volley的用法很简单,发起一条HTTP GET请求,然后接收HTTP响应。首先需要获取到一个RequestQueue对象,可以调用如下方法获取到:
RequestQueue newQueue = Volley.newRequestQueue(context);
这里拿到的RequestQueue是一个请求队列对象,它可以缓存所有的HTTP请求,然后按照一定的算法并发地发出这些请求。
RequestQueue内部的设计就是非常合适高并发的,因此我们不必为每一次HTTP请求都创建一个RequestQueue对象,这是非常浪费资源的,基本上在每一个需要和网络交互的Activity中创建一个RequestQueue对象就足够了。
Request的使用
Request是请求的基类,拥有以下子类:
- ClearCacheRequest
- JsonRequest
- ImageRequest
- StringRequest
在基类中定义了一个内部接口类 Method
分别定义了集中常见的请求方式
public interface Method {
int DEPRECATED_GET_OR_POST = -1;
int GET = 0;
int POST = 1;
int PUT = 2;
int DELETE = 3;
int PATCH = 4;
}
同时还定义了一个枚举 Priority
定义了一些优先级的常量
public static enum Priority {
LOW,
NORMAL,
HIGH,
IMMEDIATE;
private Priority() {
}
}
StringRequestd的使用
StringRequest的构造函数需要传入四个参数:
public StringRequest(int method, String url, Listener<String> listener, ErrorListener errorListener) {
super(method, url, errorListener);
this.mListener = listener;
}
- 第一个参数是表示请求的方式 默认是0 也就是 GET 请求
- 第二个参数是目标服务器的URL地址
- 第三个参数是服务器响应成功的回调
- 第四个参数是服务器响应失败的回调
将这个StringRequest对象添加到RequestQueue里面就可以了。使用Volley时,可以从任何线程开始请求,但响应始终传递到了主线程上。
以下是阅读Android Developer文档中的VolleyDemo:
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() {
@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);
以上就用Volley发起了一个简单的HTTP 的 GET 请求。
Volley发出Request
Volley发出网络请求,阻塞I/O和解析数据都是在子线程中完成的,您可以在任何线程添加网络请求,但是响应始终都是传递到主线程上。
分析一下 Android Developer 上的这幅图:
- RequestQueue会维护一个缓存调度线程(CacheDispatcher)
- 同时还会维护一网络调度线程池(NetworkDispatcher[])->线程池默认大小为4
-
Volley.newRequestQueue(context);
的代码中在data/data/包名/cache/目录下创建了一个volley的文件夹用于存放缓存(本地缓存) - 同时创建一个 RequestQueue 类,并调用了其start() 方法
BasicNetwork network1 = new BasicNetwork((HttpStack)stack);//将在后文提到
RequestQueue queue1 = new RequestQueue(new DiskBasedCache(cacheDir), network1);
queue1.start();
- start()方法看上去像是线程开启的方法,但是 RequestQueue 并不是一个Thread类,不过在当中它干的就是开启线程的事。
public void start() {
this.stop();
this.mCacheDispatcher = new CacheDispatcher(this.mCacheQueue, this.mNetworkQueue, this.mCache, this.mDelivery);
this.mCacheDispatcher.start();
for(int i = 0; i < this.mDispatchers.length; ++i) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(this.mNetworkQueue, this.mNetwork, this.mCache, this.mDelivery);
this.mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
没错,它逐一开启了缓存调度线程和所有的网络调度线程
-
每一条线程就开始不断的循环读取cache队列或者net队列的头
-
当一个Request被添加到队列中的时候,cache线程会首先对这个请求进行筛选,如果这个请求的内筒可以在缓存中找到,并且没有过期,cache线程会自己解析响应的内容,并分发到主线程(UI);
-
如果缓存中没有,这个request就会被加入到另一个NetworkQueue,所有真正准备进行网络通信的request都在这里,第一个可用的net线程会从NetworkQueue中拿出一个request扔向服务器。当响应获得数据之后,这个net线程会解析原始响应数据,写入缓存,并把解析后的结果返回给主线程。
取消一个Request
要取消一个请求,调用cancel()即可。一旦取消,Volley保证你的响应处理程序将永远不会被调用。这意味着在实践中,你可以取消所有待定的请求在你Activity的onStop()方法中,你不必乱抛垃圾的响应处理程序或者检查getActivity() == NULL,无论onSaveInstanceState()是否已经被调用。
你发出去的请求必须要自己保证是可控的,在这里还有一个更简单的方法:你可以标记发送的每个请求对象,然后你可以使用这个标签来提供请求取消的范围。
下面是一个使用字符串标签的例子:
-
定义您的标签同时将其添加到Request中
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);
-
在您的Activity的 onStop() 方法中,取消所有被标记的Request
@Override protected void onStop () { super.onStop(); if (mRequestQueue != null) { mRequestQueue.cancelAll(TAG); } }
取消请求时要小心。如果你是根据你的响应处理程序来推进一个状态或启动另一个进程,你需要考虑到这个。同样,响应处理程序将不会被调用。
BasicNetwork
现在我们来说说 BasicNetwork,前文在讲 RequestQueue 的时候有所提到,其中 BasicNetwork 是创建 RequestQueue 对象时当中的一个参数
-
BasicNetwork 是Volley的一个默认网络实现,App连接HTTP客户端必须要对其初始化。最主要的是实现了 Network 接口中的
NetworkResponse performRequest(Request<?> var1) throws VolleyError;
方法,其实这个接口中就只有这一个方法。
@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
long requestStart = SystemClock.elapsedRealtime();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()); if(networkResponse1 == 304) { return new NetworkResponse(304, request.getCacheEntry().data, responseHeaders1, true); } byte[] responseContents1 = this.entityToBytes(httpResponse.getEntity()); long requestLifetime = SystemClock.elapsedRealtime() - requestStart; this.logSlowRequests(requestLifetime, request, responseContents1, statusCode2); if(networkResponse1 != 200 && networkResponse1 != 201 && networkResponse1 != 202 && networkResponse1 != 204) { throw new IOException(); } return new NetworkResponse(networkResponse1, responseContents1, responseHeaders1, false); } catch (SocketTimeoutException var12) { attemptRetryOnException("socket", request, new TimeoutError()); } catch (ConnectTimeoutException var13) { attemptRetryOnException("connection", request, new TimeoutError()); } catch (MalformedURLException var14) { throw new RuntimeException("Bad URL " + request.getUrl(), var14); } catch (IOException var15) { boolean statusCode = false; NetworkResponse networkResponse = null; if(httpResponse == null) { throw new NoConnectionError(var15); } int statusCode1 = httpResponse.getStatusLine().getStatusCode(); VolleyLog.e("Unexpected response code %d for %s", new Object[]{Integer.valueOf(statusCode1), request.getUrl()}); if(responseContents == null) { throw new NetworkError(networkResponse); } networkResponse = new NetworkResponse(statusCode1, (byte[])responseContents, responseHeaders, false); if(statusCode1 != 401 && statusCode1 != 403) { if(statusCode1 != 400 && statusCode1 != 404 && statusCode1 != 406 && statusCode1 != 501) { throw new ServerError(networkResponse); } throw new ClientError(networkResponse); } attemptRetryOnException("auth", request, new AuthFailureError(networkResponse)); } } }
-
这个方法是网络请求的具体实现,以一个while大循环包裹,其中
httpResponse = this.mHttpStack.performRequest(request, e);
是主要的网络请求代码,由HttpStack发出。在创建对象的时候传入的对象。以下具体说明 -
创建 BasicNetwork 对象的时候,需要传入一个HttpStack的对象。
HttpStack stack;
...
if(stack == null) {
if(VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}BasicNetwork network1 = new BasicNetwork((HttpStack)stack);
-
可以看出在 Froyo(2.2) 之前,采用基于 HttpClient 的 HttpClientStack,之后使用基于 HttpURLConnection 的 HurlStack,原因呢是因为Froyo(2.2) HttpURLConnection 有个重大 Bug,调用 close() 函数会影响连接池,导致连接复用失效,所以在 Froyo 之前使用 HttpURLConnection 需要关闭 keepAlive。
private void disableConnectionReuseIfNecessary() { // 这是一个2.2版本之前的bug if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) { System.setProperty("http.keepAlive", "false"); } }
-
另外 Gingerbread(2.3) HttpURLConnection 默认开启了 gzip 压缩,提高了 HTTPS 的性能,Ice Cream Sandwich(4.0) HttpURLConnection 支持了请求结果缓存。再加上 HttpURLConnection 本身 API 相对简单,所以对 Android 来说,在 2.3 之后建议使用 HttpURLConnection,之前建议使用 AndroidHttpClient。下面我们来具体说说网络请求接口HttpStack。
HttpStack接口
HttpStack接口中也只有一个方法HttpResponse performRequest(Request<?> var1, Map<String, String> var2) throws IOException, AuthFailureError;
在前文我们提到了HttpStack的两个实现类 HttpClientStack 和 HurlStack,在实现的具体方法中真正进行了网络请求。
-
HurlStack中封装了HttpURLConnection
...
URL parsedUrl1 = new URL(url);
HttpURLConnection connection = this.openConnection(parsedUrl1, request);
Iterator responseCode = map.keySet().iterator();
... -
HttpClientStack中封装了HttpClient,也就是传进来的AndroidHttpClient。
Request的parseNetworkResponse方法
以上步骤,在NetworkDispatcher中收到了NetworkResponse这个返回值后又会调用Request的parseNetworkResponse()方法来解析NetworkResponse中的数据,同时将数据写入到缓存,这个方法的实现是交给Request的子类来完成的,因为不同种类的Request解析的方式也肯定不同。
ResponseDelivery接口
在获取了解析后的数据之后,NetworkDispatcher的run()方法最后调用了this.mDelivery.postResponse(request, response);
将数据post到主线程(UI线程),其中的 mDelivery 就是 ResponseDelivery接口的具体实现类 ExecutorDelivery对象,具体的方法如下:
@Override
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
request.markDelivered();
request.addMarker("post-response");
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}
ExecutorDelivery 的构造方法如下:
public ExecutorDelivery(final Handler handler) {
this.mResponsePoster = new Executor() {
public void execute(Runnable command) {
handler.post(command);
}
};
}
在创建其实例的时候传入了 RequestQueue 实例的 mDelivery :
NetworkDispatcher networkDispatcher = new NetworkDispatcher(this.mNetworkQueue, this.mNetwork, this.mCache, this.mDelivery);
而 mDelivery 就是创建 RequestQueue 时创建的主线程Handler:
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize, new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
如此就保证了 ExecutorDelivery 对象中的 run() 方法在主线程中运行。
而在 run() 方法中最核心的代码就是:
if(this.mResponse.isSuccess()) {
this.mRequest.deliverResponse(this.mResponse.result);
} else {
this.mRequest.deliverError(this.mResponse.error);
}
调用了Request的 deliverResponse(this.mResponse.result)
和 mRequest.deliverError(this.mResponse.error)
方法,其中就调用了需要重写的实现Listener接口方法,将反馈发送到回调到UI线程。
这是后来想起加的一张NetworkDispatcher的工作流程图:
NetworkDispatcher.png有什么不对还请指出,谢谢指教。
网友评论