Google的网络请求库Volley的基本使用及原理浅析
介绍
Volley是Google在2013年发布的一款Android平台上的网络请求库。
它有如下特点:
- 使得网络通信更快,更简单
- GET、POST网络请求及网络图像的高效异步处理请求
- 可以对网络请求进行排序优先级处理
- 网络请求的缓存
- 多级别取消请求
- 与Activity生命周期的联动
但它也有一定缺点:
- 不适合进行数据的上传与下载
Volley的使用
建立全局请求队列
首先我们需要一个全局的Application来建立一个全局的请求队列。在这里我们建立一个ContextApplication并继承自Application。并在manifest中指定application的name属性为ContextApplication。
然后我们在ContextApplication中写用于建立请求队列的代码。
public class ContextApplication extends Application {
public static RequestQueue sQueue;
@Override
public void onCreate() {
super.onCreate();
sQueue = Volley.newRequestQueue(getApplicationContext());
}
public static RequestQueue getHttpQueues(){
return sQueue;
}
}
这样就完成了全局请求队列的建立。
GET请求
首先我们学习用StringRequest来请求数据。
此处我们将请求方法设置为了Method.GET,然后填入请求链接,创建请求成功及失败的回调。
之后我们为request设置了一个tag,这样在加入全局队列后可以通过标签找到这个request。
之后将该request放入全局队列。这样一个使用StringRequest的GET请求就成功完成了
String url = "https://www.baidu.com";
StringRequest request = new StringRequest(Method.GET, url, new Listener<String>() {
@Override
public void onResponse(String s) {
//请求成功
}
}, new ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
//请求失败
}
});
request.setTag("httpGET");
ContextApplication.getHttpQueues().add(request);
如果换做是JsonObjectRequest,基本相同,仅仅是最后的结果返回的是JsonObject而已。传入需要多传入一个JsonObject参数。这个参数是用于POST请求,GET请求可以设置为null
可以看出,相比HttpUrlConnection,Volley实现请求确实简单了不少。
POST请求
用StringRequest进行POST请求,参数与之前大致相同,将方法改为POST。然后传递参数通过实现它的传递参数方法,也就是getParams方法。
可以看到,getParams方法返回的是一个Map,因此我们需要建立一个Map,然后将参数添加到Map中,最后返回该map。
String url = "http://xxxxxx.xx";
StringRequest request = new StringRequest(Method.POST, url, new Listener<String>() {
@Override
public void onResponse(String s) {
}
}, new ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
}
}){
@Override
protected Map<String, String> getParams() throws AuthFailureError {
Map<String,String> params = new HashMap<>();
params.put("username","xxxx");
params.put("password","xxxxxxxx");
return params;
}
};
request.setTag("testPOST");
ContextApplication.getHttpQueues().add(request);
这样就完成了用StringRequest进行POST请求。
如果我们换为使用JsonObjectRequest进行请求,那么我们需要在外部建立一个Map,将参数像刚刚那样传递,然后建立一个JsonObject,将Map放入。最后将jsonObject作为参数传入JsonObjectRequest的构造函数即可。
将Volley生命周期与Activity进行关联。
我们只需在Activity的onStop方法,通过cancelAll方法传入tag,将请求关闭即可。
@Override
protected void onStop() {
super.onStop();
ContextApplication.getHttpQueues().cancelAll("testGET");
}
网络图片的加载
网络图片加载需要用到ImageRequest。它需要传入的第一个参数是图片的url,然后是请求成功的回调,之后是图片的最大宽度以及最大高度(设置为0会以原图方式加载)。然后是加载图片的格式,这里选择Config.RGB_565。之后是加载失败的回调。
最后将请求放入队列即可完成网络图片的加载。
private void imageReuqest() {
String url = "https://www.baidu.com/img/baidu_jgylogo3.gif";
ImageRequest request = new ImageRequest(url, new Listener<Bitmap>() {
@Override
public void onResponse(Bitmap bitmap) {
mImageView.setImageBitmap(bitmap);
}
}, 0, 0, Config.ARGB_8888, new ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
}
});
ContextApplication.getHttpQueues().add(request);
}
图片的缓存加载
这里我们使用ImageLoader来进行加载图片。这里需要填入的参数是请求队列以及缓存。
请求队列我们填入全局的队列。而ImageCache本身是起不到作用的,我们需要结合LruCache来使用。
我们自定义一个BitmapCache类,实现ImageCache接口。可以看到,它需要实现getBitmap以及putBitmap方法,通过这两个方法,我们就可以实现图片的缓存。加载图片时,会先从缓存中获取图片,获取不到再从网络获取图片。
public class BitmapCache implements ImageCache {
private static final String CACHE_PATH =
Environment.getExternalStorageDirectory().getAbsolutePath() + "/ONE"; //本地缓存路径
private static LruCache<String, Bitmap> sMemoryCache;
public BitmapCache(){
//初始化LruCache
if (sMemoryCache == null) {
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); //将最大内存换算为kb(原来以B为单位)
final int cacheSize = maxMemory / 8; //取1/8的内存为LruCache的大小
//计算LruCache空间大小
sMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount() / 1024;
}
};
}
}
@Override
public Bitmap getBitmap(String url) {
boolean isLoaded = false;
if (!isLoaded) {
//先从内存拿
Bitmap bitmap = getBitmapFromMemoryCache(url);
if (bitmap != null) {
isLoaded = true;
return bitmap;
}
}
if(!isLoaded) {
//再从本地拿
Bitmap bitmap = getBitmapFromLocal(url);
if (bitmap != null) {
isLoaded = true;
return bitmap;
}
}
return null;
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
addBitmapToMemoryCache(url,bitmap);
addBitmapToLocal(url,bitmap);
}
/**
* 内存缓存
*/
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemoryCache(key) == null) {
sMemoryCache.put(key, bitmap); //图片没有放入时将图片放入内存
}
}
public Bitmap getBitmapFromMemoryCache(String key) {
return sMemoryCache.get(key); //从内存取出对应图片
}
/**
* 本地缓存
*/
public Bitmap getBitmapFromLocal(String url) {
String fileName = null;
try {
//进行MD5加密的原因:不让一些特殊的url影响文件的存储
//同时让接口不被用户看到
//把图片的url当做文件名,并进行MD5加密
fileName = MD5Encoder.encode(url);
File file = new File(CACHE_PATH, fileName);
Bitmap bitmap = null;
if (file.exists()) {
bitmap = BitmapFactory.decodeStream(new FileInputStream(file));
addBitmapToMemoryCache(url, bitmap); //添加到内存缓存
}
return bitmap;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public void addBitmapToLocal(String url, Bitmap bitmap) {
try {
//进行MD5加密的原因:不让一些特殊的url影响文件的存储
//同时让接口不被用户看到
//把图片的url当做文件名,并进行MD5加密
String fileName = MD5Encoder.encode(url);
File file = new File(CACHE_PATH, fileName);
//通过得到文件的父文件,判断父文件是否存在
File parentFile = file.getParentFile();
if (!parentFile.exists()) {
parentFile.mkdirs();
}
//把图片保存至本地
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, new FileOutputStream(file));
} catch (Exception e) {
e.printStackTrace();
}
}
}
之后我们通过构造方法创建ImageLoader,创建好ImageLoader之后,通过ImageLoader的getImageListener方法获取到ImageListener。之后通过imageLoader的get方法进行加载即可。
Volley的源码分析
参考自郭霖大神的博客:http://blog.youkuaiyun.com/guolin_blog/article/details/17656437
要解析Volley的源码,我们从第一个用到的方法——newRequestQueue方法来看起,查看newRequestQueue的代码
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
File cacheDir = new File(context.getCacheDir(), "volley");
String userAgent = "volley/0";
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException var6) {
;
}
if (stack == null) {
if (VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
Network network = new BasicNetwork((HttpStack)stack);
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}
可以看到,这里在第10行判断如果stack是等于null的,则去创建一个HttpStack对象,这里会判断如果手机系统版本号是大于9的,则创建一个HurlStack的实例,否则就创建一个HttpClientStack的实例。
实际上HurlStack的内部就是使用HttpURLConnection进行网络通讯的,而HttpClientStack的内部则是使用HttpClient进行网络通讯的 。
创建好了HttpStack之后,接下来又创建了一个Network对象,它是用于根据传入的HttpStack对象来处理网络请求的,紧接着new出一个RequestQueue对象,并调用它的start()方法进行启动,然后将RequestQueue返回,这样newRequestQueue()的方法就执行结束了。
那么RequestQueue的start()方法内部到底执行了什么东西呢?我们跟进去:
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();
}
}
这里先创建了一个CacheDispatcher的实例,然后调用了它的start()方法,接着在一个for循环里去创建NetworkDispatcher的实例,并分别调用它们的start()方法。
这里的CacheDispatcher和NetworkDispatcher都是继承自Thread的,而默认情况下for循环会执行四次,也就是说当调用了Volley.newRequestQueue(context)之后,就会有五个线程一直在后台运行,不断等待网络请求的到来,其中CacheDispatcher是缓存线程,NetworkDispatcher是网络请求线程。
我们得到RequestQueue后,构建出相应的Request,然后调用RequestQueue的add方法即可完成网络请求,我们可以进入add方法看看。
public Request add(Request request) {
request.setRequestQueue(this);
Set var2 = this.mCurrentRequests;
synchronized(this.mCurrentRequests) {
this.mCurrentRequests.add(request);
}
request.setSequence(this.getSequenceNumber());
request.addMarker("add-to-queue");
if (!request.shouldCache()) {
this.mNetworkQueue.add(request);
return request;
} else {
Map var7 = this.mWaitingRequests;
synchronized(this.mWaitingRequests) {
String cacheKey = request.getCacheKey();
if (this.mWaitingRequests.containsKey(cacheKey)) {
Queue<Request> stagedRequests = (Queue)this.mWaitingRequests.get(cacheKey);
if (stagedRequests == null) {
stagedRequests = new LinkedList();
}
((Queue)stagedRequests).add(request);
this.mWaitingRequests.put(cacheKey, stagedRequests);
if (VolleyLog.DEBUG) {
VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", new Object[]{cacheKey});
}
} else {
this.mWaitingRequests.put(cacheKey, (Object)null);
this.mCacheQueue.add(request);
}
return request;
}
}
}
可以看到,在第11行的时候会判断当前的请求是否可以缓存,如果不能缓存则在第12行直接将这条请求加入网络请求队列,可以缓存的话则在第33行将这条请求加入缓存队列。
由此可见,在默认情况下,每条请求都是可以缓存的,当然我们也可以调用Request的setShouldCache(false)方法来改变这一行为。
既然默认每条请求都是可以缓存的,自然就被添加到了缓存队列中,于是一直在后台等待的缓存线程就要开始运行起来了,我们可以查看CacheDispatcher中的run()方法:
public class CacheDispatcher extends Thread {
……
@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;
}
}
}
}
run方法中代码比较长,我们可以在11行看到一个while循环,说明这个缓存线程是始终运行的。在32行可以看到在从缓存中取出响应的结果。如果缓存结果为空,就把这条请求加入网络请求队列。不为空的情况下则判断缓存是否过期,过期的话仍然将这条请求加入网络请求队列。否则不需要网络请求,直接使用缓存中的数据。
第39行在调用Request的parseNetworkResponse()方法来对数据进行解析 。之后则是将解析后的数据进行回调。回调的部分我们在这里暂时不介绍,因为它和NetworkDispatcher的逻辑基本相同,等到后面一起分析。
我们来到NetWorkDIspatcher来查看它如何处理网络请求队列。
public class NetworkDispatcher extends Thread {
……
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Request<?> request;
while (true) {
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) {
parseAndDeliverNetworkError(request, volleyError);
} catch (Exception e) {
VolleyLog.e(e, "Unhandled exception %s", e.toString());
mDelivery.postError(request, new VolleyError(e));
}
}
}
}
可以看到,NetworkDispatcher中也是while(true)循环,说明网络请求的线程也是不断运行。看到28行会调用Network的performRequest()方法来发送网络请求。而Network是一个接口,这里具体的实现是BasicNetwork,我们来看下它的performRequest()方法:
public class BasicNetwork implements Network {
……
@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
long requestStart = SystemClock.elapsedRealtime();
while (true) {
HttpResponse httpResponse = null;
byte[] responseContents = null;
Map<String, String> responseHeaders = new HashMap<String, String>();
try {
// Gather headers.
Map<String, String> headers = new HashMap<String, String>();
addCacheHeaders(headers, request.getCacheEntry());
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) {
return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED,
request.getCacheEntry() == null ? null : request.getCacheEntry().data,
responseHeaders, true);
}
// 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);
} catch (Exception e) {
……
}
}
}
}
这段方法中大多都是一些网络请求细节方面的东西,我们并不需要太多关心。需要注意在14行调用了HttpStack的performRequest()方法。
这里的HttpStack就是在一开始调用newRequestQueue()方法是创建的实例,默认情况下如果系统版本号大于9就创建的HurlStack对象,否则创建HttpClientStack对象。前面提到,这两个对象的内部分别使用HttpURLConnection和HttpClient来发送网络请求,我们不再需要跟进查看。获取结果之后会将服务器返回的数据组装成一个NetworkResponse对象进行返回。
在NetworkDispatcher中收到了NetworkResponse这个返回值后又会调用Request的parseNetworkResponse()方法来解析NetworkResponse中的数据,以及将数据写入到缓存,这个方法的实现是交给Request的子类来完成的,因为不同种类的Request解析的方式也肯定不同。
在解析完了NetworkResponse中的数据之后,又会调用ExecutorDelivery的postResponse()方法来回调解析出的数据:
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
request.markDelivered();
request.addMarker("post-response");
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}
其中,在mResponsePoster的execute()方法中传入了一个ResponseDeliveryRunnable对象,就可以保证该对象中的run()方法就是在主线程当中运行的了,我们看下run()方法中的代码:
private 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")
@Override
public void run() {
// If this request has canceled, finish it and don't deliver.
if (mRequest.isCanceled()) {
mRequest.finish("canceled-at-delivery");
return;
}
// Deliver a normal response or error, depending.
if (mResponse.isSuccess()) {
mRequest.deliverResponse(mResponse.result);
} else {
mRequest.deliverError(mResponse.error);
}
// If this is an intermediate response, add a marker, otherwise we're done
// and the request can be finished.
if (mResponse.intermediate) {
mRequest.addMarker("intermediate-response");
} else {
mRequest.finish("done");
}
// If we have been provided a post-delivery runnable, run it.
if (mRunnable != null) {
mRunnable.run();
}
}
在第22行调用了Request的deliverResponse()方法。每一条网络请求的响应都是回调到这个方法中,最后我们再在这个方法中将响应的数据回调到Response.Listener的onResponse()方法中就可以了。
以下是Volley库中自带的一张Volley原理图:
其中蓝色部分代表主线程,绿色部分代表缓存线程,橙色部分代表网络线程。我们在主线程中调用RequestQueue的add()方法来添加一条网络请求,这条请求会先被加入到缓存队列当中,如果发现可以找到相应的缓存结果就直接读取缓存并解析,然后回调给主线程。如果在缓存中没有找到结果,则将这条请求加入到网络请求队列中,然后处理发送HTTP请求,解析响应结果,写入缓存,并回调主线程。
广告时间
我是N0tExpectErr0r,一名广东工业大学的大二学生
欢迎来到我的个人博客,所有文章均在个人博客中同步更新哦
http://blog.N0tExpectErr0r.cn