Volley框架介绍
在开发一些调用服务器端API以获得数据的应用时,需要编写繁琐的基于HttpClient或HttpURLConnection的代码与处理异步请求的线程管理操作,如Android Studio自带的LoginActivity模板,就实现了一个继承自AsyncTask的臃肿内部类来处理一个简单的登录请求。
应运而生的诸多网络通信框架中,Volley的优点包括(翻译自google官方文档):
- 自动调度网络请求
- 可以并发进行多个网络通信
- 可操作的标准HTTP缓存(磁盘、内存)
- 支持请求优先级
- 提供可以取消一个或多个请求的接口
- 可以简易地自定义一些组件
- 通过排序从网络异步获取数据,正确地填充UI组件
- 提供调试与跟踪工具
总的来说,Volley框架在轻量级与可拓展性上的优越表现让我们可以利用它进行一些频繁但数据量小的网络通信,利用内置的HTTP缓存功能也可以实现图片资源的加载与缓存。
配置
Volley的开源地址为https://github.com/google/volley ,一般可以通过在项目里导入module的方式引入clone到本地的Volley库,或者在项目的build.gradle中加入
dependencies {
...
compile 'com.android.volley:volley:1.0.0'
}
的方式添加对volley的依赖。
框架使用的基本方法
Volley的常规使用方法(来自Google官方文档):
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);
相对来说调用过程十分简洁,主要的组件包括:用于调度、并发执行多个HTTP请求的RequestQueue,volley库中的Request对象,传入Request对象的Listener用于处理成功请求后的response,传入Request对象的ErrorListener用于执行请求错误/异常后的相关操作。
宏观来看,Volley框架的总体运行与调用结构为(图片转载自http://blog.csdn.net/geolo/article/details/43966171 ):
整体调用结构可以看到,传入RequestQueue的对象可以是现有的,StringRequest(获得String格式的response),JsonObjectRequest(可以传入一个json对象作为post请求的params,并返回json格式的response),ImageRequest(前文提过volley框架因为缓存的存在对图片资源的加载与管理有很好的表现),实现Request抽象类获得的自定义请求对象,实现方式非常简洁,可以参考官方文档https://developer.android.com/training/volley/request-custom.html 的示例,这里不做赘述。
而RequestQueue管理了两种线程,NetworkDispatcher与CacheDispatcher,分别处理网络请求与缓存管理,内部利用底层的HttpClient或HttpURLConnection实现网络通信,利用磁盘缓存(CacheDispathcer运行时读取文件写入HashMap中由内存管理)实现缓存数据的持久化。
request生命周期上图是来自Google官方文档的,request对象的生命期示意图,这里进行了主线程、缓存线程、网络线程的区分,首先检查该请求是否命中缓存,若命中则返回缓存结果,若丢失则加入NetworkDispatcher线程池进行网络通信,返回结果、写入缓存。
下面通过阅读volley库中的一些源码来详细解析这个过程的具体实现方式。
Volley源码解析
首先我们一般通过调用Volley.newRequestQueue(Context context)静态方法获得RequestQueue对象的实例(当然可以直接传入RequestQueue需要的一些构造参数对象new出来,自己实现一个单例工厂管理RequestQueue对象是一个不错的选择,适用于经常需要网络通信的应用),方法具体实现为:
/**
* Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
*
* @param context A {@link Context} to use for creating the cache dir.
* @param stack An {@link HttpStack} to use for the network, or null for default.
* @return A started {@link RequestQueue} instance.
*/
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
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 = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}
对于RequestQueue需要的两个参数,Cache接口与Network接口,volley库中自带了BasicNetwork与DiskBasedCache作为一种具体实现类。
首先判断设备的版本号是否为9及以上,若是则使用HurlStack作为Network实现类的参数,否则使用HttpClientStack,前者内部封装的网络通信组件为HttpURLConnection,后者为HttpClient,这两个基本组件的使用场景与android的版本迭代过程有关,因此这里不再做细致介绍。
public interface Network {
/**
* Performs the specified request.
* @param request Request to process
* @return A {@link NetworkResponse} with data and caching metadata; will never be null
* @throws VolleyError on errors
*/
NetworkResponse performRequest(Request<?> request) throws VolleyError;
}
Network接口提供了performRequest方法,很直观地传入request获得response的过程,在BasicNetwork中,利用作为参数传入的HttpStack接口,调用底层的网络通信组件(前段已有介绍)获得response,并添加了加入至缓存(后文会解释)、异常处理、记录长时请求、尝试重请求等封装好的功能。
public interface Cache {
Entry get(String key);
void put(String key, Entry entry);
void initialize();
void invalidate(String key, boolean fullExpire);
void remove(String key);
void clear();
class Entry {
public byte[] data;
public String etag;
public long serverDate;
public long lastModified;
public long ttl;
public long softTtl;
public Map<String, String> responseHeaders = Collections.emptyMap();
boolean isExpired() {
return this.ttl < System.currentTimeMillis();
}
boolean refreshNeeded() {
return this.softTtl < System.currentTimeMillis();
}
}
}
Cache接口提供了一个内部类Entry作为缓存的元数据,其中包括一个存储responseHeaders的Map,事实上Volley框架提供的缓存功能便是根据response的headers信息来管理缓存的刷新周期、有效时长等信息,实现HTTP标准缓存。DiskBasedCache利用读写文件实现了基于磁盘的缓存持久化,CacheDispatcher线程会调用intialize()方法加数据从文件加载到内存的HashMap中,并对每个请求进行cache是否命中的操作。
Volley框架的这种设计方法支持了开发者对Cache与Network接口的自定义实现与扩展,我在这只对库中自带的两个实现类简单介绍,底层的实现方式并非是框架运作流程的重点所在,因此不再贴上繁琐的代码与分析以免浪费阅读者的时间。
回到之前在Volley中创建的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();
}
}
之前的介绍中提过,NetworkDispatcher与CacheDispatcher均为由ReuquestQueue管理的线程,他们都继承自Thread类,同时运作的有一个CacheDispatcher与多个NetworkDispatcher(默认4个)。可以看到两种线程都传入了mNetworkQueue(Request对象作为范型的优先队列)与mCache的引用,因此它们可以对每个加入的request进行缓存的查找、替换、添加等操作。
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<>();
}
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.shouldCache()判断请求是否可以加入缓存,若不可以直接加入mNetworkQueue,若可以则加mCacheQueue,途中会有一个判断阻塞中的请求是否与新请求相同的过滤判断。
接下来回到NetworkDispatcher与CacheDispatcher线程,看看他们都在执行什么工作:
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;
}
}
}
}
以上是CacheDispatcher的run()方法,首先initialize()持有的Cache对象,将缓存读入内存,接下来在while(true)的轮询中,每次从mCacheQueue队列中取出一个request,分别判断是否命中缓存、缓存是否过期,若未命中或过期则加入mNetworkQueue,否则直接返回缓存结果。
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
long startTimeMs = SystemClock.elapsedRealtime();
Request<?> request;
try {
// Take a request from the queue.
request = mQueue.take();
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}
try {
request.addMarker("network-queue-take");
// If the request was cancelled already, do not perform the
// network request.
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
continue;
}
addTrafficStatsTag(request);
// 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()) {
request.finish("not-modified");
continue;
}
// Parse the response here on the worker thread.
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.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);
}
}
}
以上为NetworkDispatcher的run()方法,同样是通过while(true)轮询,每次从mNetworkQueue中取出request,调用Network接口的处理请求方法,获得response对象,后续自然也有相应的异常处理、错误码处理、response交给request的parseNetworkResponse()解析等操作,到这里流程就已经比较清晰了,解析操作大部分情况自带的StringRequest与JsonObjectRequest已经足够,volley也支持直接重写该方法,可以参考上文链接中的示例。
源码的解析有些杂乱,宏观上还是线程的管理实现异步请求,加上一个与计算机中的二级缓存机制十分相似的缓存层实现,可以回顾一下之前的request生命周期图,再次理解下三种线程的相互关系。
虽然写了很多,但volley框架使用起来非常简洁,功能也很完善,之后有心情也许会给出一些具体应用项目中的实现场景(你就是想应付第三次博客啊喂!)。
网友评论