今天想总结的是Volley这个网络请求框架,虽然volley论火爆程度比不上okhttp和retrofit 这两个,并且在日常使用的过程中可能很少有人能去深究为什么volley是Google 所推荐的网络框架,不管从使用还是从源码去理解,我觉得volley都是一款值得去深究 去学习的框架
volley的gayhub地址 https://github.com/google/volley
写在下面之前可以先看下我的总结,因为很多东西和大家讲的差不多,主要更是对volley的一个总结-----如果嫌太长可以直接看这一段总结(虽然好像也有点长哦,不过理一理还是很容易看懂的~~)
volley是一个轻量型的框架 基本满足了 我们日常中的网络请求 适合于数据小 频繁请求的场景 因为volley是面向接口开发的 所以它的可扩展性强 是基于httpurlconnection 开发的 当每次请求进来的时候 会加入到一个requestqueue中 这个queue内部维护了两个队列 一个是缓存队列 多个是网络请求队列(默认是4个) 然后建立完队列之后会建立一个请求(request,有Stringrequest jsonarrayrequest imagerequest 都是继承于request 所以如果你要自定义网络请求那么继承于request就可以了,重写里面的两个返回方法 一个是deliverResponse方法 主要作用是实现成功回调 还有一个是parseNetworkResponse() 来处理响应数据 )至此一个完整的request过程就结束了 然后就开始添加请求进队列了 这个过程首先判断是否允许被缓存(默认是可以被缓存的) 如果不允许那就添加到网络缓存队列,如果可以再进一步判断是否有相同请求在执行 如果有那就直接等待 如果没有那就放入缓存队列中 然后完成整个添加过程 下面我们就详细分析一下缓存线程的过程 里面一开始有个while循环 表示它一直在等待着请求的到来 接着首先判断这个缓存 有没有 如果没有那就直接添加到网络缓存中去 如果有判断是否过期 过期了同样添加到网络缓存中去 如果都正常 那就直接打包数据 解析为response 直接post到主线程 处理 然后我们在分析一下这个网络线程 内部封装了httpurlconnection 将处理结果回调到上面的方法中进行处理 我们通过分析知道 缓存线程和网络请求线程都通过一个ExecutorDelivery#postResponse()方法将结果返回到主线程中处理 其实这个方法的实现很简单 就是里面获取到了主线程的handler 才能将处理结果返回给主线程的
优点
纵观目前的三大网络请求框架 okhttp retrofit volley 各有各的优势,下面是我从网上找到的一些对比 ,可能篇幅优点长,其实也没必要去深究,只需要看最后一个我标红的地方就知道volley的优点了。
1.OkHttp
Android 开发中是可以直接使用现成的api进行网络请求的,就是使用HttpClient、HttpUrlConnection 进行操作,目前HttpClient 已经被废弃,而 android-async-http 是基于HttpClient的,可能也是因为这个原因作者放弃维护。 而OkHttp是Square公司开源的针对Java和Android程序,封装的一个高性能http请求库,它的职责跟HttpUrlConnection 是一样的,支持 spdy、http 2.0、websocket ,支持同步、异步,而且 OkHttp 又封装了线程池,封装了数据转换,封装了参数使用、错误处理等,api使用起来更加方便。可以把它理解成是一个封装之后的类似HttpUrlConnection的东西,但是在使用的时候仍然需要自己再做一层封装,这样才能像使用一个框架一样更加顺手。
2.Retrofit
Retrofit是Square公司出品的默认基于OkHttp封装的一套RESTful网络请求框架,RESTful是目前流行的一套api设计的风格, 并不是标准。Retrofit的封装可以说是很强大,里面涉及到一堆的设计模式,可以通过注解直接配置请求,可以使用不同的http客户端,虽然默认是用http ,可以使用不同Json Converter 来序列化数据,同时提供对RxJava的支持,使用Retrofit + OkHttp + RxJava + Dagger2 可以说是目前比较潮的一套框架,但是需要有比较高的门槛。
3.Volley
Volley是Google官方出的一套小而巧的异步请求库,该框架封装的扩展性很强,支持HttpClient、HttpUrlConnection, 甚至支持OkHttp,而且Volley里面也封装了ImageLoader,所以如果你愿意你甚至不需要使用图片加载框架,不过这块功能没有一些专门的图片加载框架强大,对于简单的需求可以使用,稍复杂点的需求还是需要用到专门的图片加载框架。Volley也有缺陷,比如不支持post大数据,所以不适合上传文件。不过Volley设计的初衷本身也就是为频繁的、数据量小的网络请求而生。
其实在日常开发中,选择以上三中框架都可以作为项目的主要网络请求框架都是可行的,根据各自的使用特点去封装使用,但上面的对比圈红的地方也就是volley的最强优点,短小精悍,高频繁,数据量小。下面我们就是通过这个特点从源码角度去理解它。
这个图可能每个人都见过吧 2333333 再形象不过了
由此可见volley的特点
通信更快,更简单(数据量不大,但网络通信频繁)
Get、Post网络请求及网络图像的高效率异步处理
排序(可以通过网络请求的优先级进行处理)
网络请求的缓存
多级别取消请求
volley的使用
volley自带三种返回方式
Volley的GET、Post
StringRequest:主要使用在对请求数据的返回类型不确定的情况下,StringRequest涵盖了JsonObjectRequest 和JsonArrayRequest。
JsonObjectRequest:当确定请求数据的返回类型为JsonObject时使用。
JsonArrayRequest:当确定请求数据的返回类型为JsonArray时使用。
下面是volley的基本使用方式
首先我们需要创建一个RequestQueue requestQueue,然后构建一个自己所需要的XXRequestreq,之后通过requestQueue.add(req);将请求添加至请求队列
public class MyApplication extends Application {
public static RequestQueue queues;
@Override
public void onCreate() {
super.onCreate();
queues = Volley.newRequestQueue(getApplicationContext());
}
public static RequestQueue getHttpQueues(){
return queues;
}
}
------------------------------------------------------------
private void volley_Get(){
String url = "";
StringRequest request = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() {
@Override
public void onResponse(String s) {
//success 成功操作
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
//fail 失败操作
}
});
request.setTag("Get");
MyApplication.getHttpQueues().add(request);
}
---------------------------------------------------------------------
public void vollye_Post(){
String url = "";
StringRequest request = new StringRequest(Request.Method.POST, url, new Response.Listener<String>() {
@Override
public void onResponse(String s) {
//-----------------
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
//-----------------------
}
}){
@Override
protected Map<String, String> getParams() throws AuthFailureError {
Map<String,String> hashMap = new HashMap<>();
hashMap.put("tel", "......");
return hashMap;
}
};
request.setTag("Post");
MyApplication.getHttpQueues().add(request);
}
Post请求的方法使用与Get方法相似,但它多了一个方法,getParams()这个方法用Map来获取你所需要Post的数据, 返回数据为Json格式数据时,即将你所要传入的数据放到参数中即可,不用使用getParams()方法.
Volley的生命周期和Activity的生命周期
代码很简单,其实就是在Activity销毁时候,取消对应的网络请求,避免网络请求在后台浪费资源,在onStop()方法中通过之前设置的Tag取消网络请求:
@Override
protected void onStop() {
super.onStop();
//通过Tag标签取消请求队列中对应的全部请求
MyApplication.getHttpQueues().cancelAll(tag);
}
volley还有个很少有人使用的功能 那就是 使用ImageRequest加载网络图片
LruCache:这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。
ImageCache:Volley框架内部自己处理了DiskBasedCache硬盘缓存,但是没有处理LruCache内存缓存,因为一般在处理图片的问题上才更多的用到LruCache缓存,但是它提供了一个ImageCache接口供我们自己实现,该接口默认需要实现两个方法:getBitmap(String key)和putBitmap(String key, Bitmap bitmap)
ImageRequest request = new ImageRequest(url, new Response.Listener<Bitmap>() {
@Override
public void onResponse(Bitmap bitmap) {
imageView.setImageBitmap(bitmap);
}
}, 0, 0, Bitmap.Config.RGB_565, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
imageView.setBackgroundResource(Color.BLACK);
}
});
MyApplication.getHttpQueues().add(request);
上面只是简单的介绍了一下volley的使用方式,一般使用过程还需要进一步的封装,各位看客可以去github上去找一找封装好的库,这不是今天的重头戏,今天我们主要通过源码去理解volley
源码
上源码前 再去偷一张图 先看下大致的加载过程
创建一个请求
RequestQueue queue = Volley.newRequestQueue(context)
Volley.newRequestQueue(this)
来创建一个请求队列。Volley
中提供了两种创建请求队列的方法,newRequestQueue(Context context,HttpStack stack)
和 newRequestQueue(Context context)
这行代码就是执行了一个new出一个队列的操作,
public static RequestQueue newRequestQueue(Context context) {
// 在此方法内部会调用另一个 newRequestQueue 方法,第二个参数为 null 代表使用默认的 HttpStack 实现
return newRequestQueue(context, null);
}
/**
* Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
* You may set a maximum size of the disk cache in bytes.
*
* @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.
* @param maxDiskCacheBytes the maximum size of the disk cache, in bytes. Use -1 for default size.
* @return A started {@link RequestQueue} instance.
*/
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;
}
有三个参数,我们使用了只有一个参数context的方法,这个是对应的重载方法,最终调用的是三个参数的方法,context是上下文;stack代表需要使用的网络连接请求类,这个一般不用设置,方法内部会根据当前系统的版本号调用不同的网络连接请求类(HttpUrlConnection和HttpClient);最后一个参数是缓存的大小。接着我们看方法内部,这里先创建了缓存文件,然后根据不同的系统版本号实例化不同的请求类,用stack引用这个类。接着又实例化了一个BasicNetwork,这个类在下面会说到。然后到了实际实例化请求队列的地方:new RequestQueue(),这里接收两个参数,分别是缓存和network(BasicNetwork)。实例化RequestQueue后,调用了start()方法,最后返回这个RequestQueue。
Volley
调用 getCacheDir()
方法来获取缓存目录,Volley
中的缓存文件会存储在 /data/data/packagename/cache 目录下面,并不是存储在 SD 卡中的。
Volley 当中有对 HttpStack
的默认实现,HttpStack
是真正用来执行请求的接口 ,根据版本号的不同,实例化不同的对象,在 Android2.3 版本之前采用基于 HttpClient 实现的 HttpClientStack 对象,不然则采用基于 HttpUrlConnection 实现的 HUrlStack。
之后我们通过 HttpStack
构建了一个 Network
对象,它会调用 HttpStack#performRequest()
方法来执行请求,并且将请求的结果转化成 NetworkResponse
对象,NetworkResponse 类封装了响应的响应码,响应体,响应头等数据。
接着我们会将之前构建的缓存目录以及网络对象传入 RequestQueue(Cache cache, Network network)
的构造函数中,构造一个 RequestQueue 对象,然后调用队列的 start()
方法来启动队列,其实就是启动队列中的两种线程:
/**
* Creates the worker pool. Processing will not begin until {@link #start()} is called.
*
* @param cache A Cache to use for persisting responses to disk
* @param network A Network interface for performing HTTP requests
* @param threadPoolSize Number of network dispatcher threads to create
* @param delivery A ResponseDelivery interface for posting responses and errors
*/
public RequestQueue(Cache cache, Network network, int threadPoolSize,
ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
//实例化网络请求数组,数组大小默认是4
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize,
//ResponseDelivery是一个接口,实现类是ExecutorDelivery
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
public RequestQueue(Cache cache, Network network) {
this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}
//启动队列中所有的调度线程.
public void start() {
stop(); // 确保停止所有当前正在运行的调度线程
// 创建缓存调度线程,并启动它,用来处理缓存队列中的请求
mCacheDispatcher = new CacheDispatcher(mCacheQueue,mNetworkQueue,mCache,mDelivery);
mCacheDispatcher.start();
// 创建一组网络调度线程,并启动它们,用来处理网络队列中的请求,默认线程数量为4,也可以通过RequestQueue的构造函数指定线程数量。
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue,mNetwork,mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
在 start() 方法中,主要是启动了两种线程分别是 CacheDispatcher
和 NetworkDispatcher
,它们都是线程类,顾名思义 CacheDispatcher 线程会处理缓存队列中请求,NetworkDispatcher 处理网络队列中的请求,由此可见在我们调用 Volley 的公开方法创建请求队列的时候,其实就是开启了两种线程在等待着处理我们添加的请求。
添加请求 add(Request)
看下手画的流程图 有点丑
创建了 RequestQueue ,现在我们只需要构建一个 Request 对象,add(Request<T> request)
方法
public <T> Request<T> add(Request<T> request) {
// 将请求加入到当前请求队列当中,毋庸置疑的我们需要将所有的请求集合在一个队列中,方便我们做统一操作,例如:取消单个请求或者取消具有相同标记的请求...
request.setRequestQueue(this);
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
// 给请求设置顺序.
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");
// 如果请求是不能够被缓存的,直接将该请求加入网络队列中.
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
// 如果有相同的请求正在被处理,就将请求加入对应请求的等待队列中去.等到相同的正在执行的请求处理完毕的时候会调用 finish()方法,然后将这些等待队列中的请求全部加入缓存队列中去,让缓存线程来处理
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
if (mWaitingRequests.containsKey(cacheKey)) {
// 有相同请求在处理,加入等待队列.
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 {
// 向mWaitingRequests中插入一个当前请求的空队列,表明当前请求正在被处理
mWaitingRequests.put(cacheKey, null);
mCacheQueue.add(request);
}
return request;
}
}
上面手画的就是执行流程
处理请求缓存/网络
这次流程图不手画了,从网上偷了一张 2333333
先看下缓存请求
public CacheDispatcher(
BlockingQueue<Request<?>> cacheQueue, BlockingQueue<Request<?>> networkQueue,
Cache cache, ResponseDelivery delivery) {
mCacheQueue = cacheQueue;
mNetworkQueue = networkQueue;
mCache = cache;
mDelivery = delivery;
}
这是构造函数,可以看见该对象内部持有缓存队列,网络队列,缓存对象,响应投递对象的引用。
@Override
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher");
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// 初始化缓存,将缓存目录下的所有缓存文件的摘要信息加载到内存中.
mCache.initialize();
//无线循环,意味着线程启动之后会一直运行
while (true) {
try {
// 从缓存队列中取出一个请求,如果没有请求则一直等待
final Request<?> request = mCacheQueue.take();
request.addMarker("cache-queue-take");
// 如果当前请求已经取消,那就停止处理它
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}
// 尝试取出缓存实体对象
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
request.addMarker("cache-miss");
// 没有缓存,将当前请求加入网络请求队列,让NetworkDispatcher进行处理.
mNetworkQueue.put(request);
continue;
}
// 如果缓存实体过期,任然将当前请求加入网络请求队列,让NetworkDispatcher进行处理.
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
// 将缓存实体解析成NetworkResponse对象.
request.addMarker("cache-hit");
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
if (!entry.refreshNeeded()) {
// 缓存依旧新鲜,投递响应.
mDelivery.postResponse(request, response);
} else {
//缓存已经不新鲜了,我们可以进行响应投递,然后将请求加入网络队列中去,进行新鲜度验证,如果响应码为 304,代表缓存新鲜可以继续使用,不用刷新响应结果
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
// 标记当前响应为中间响应,如果经过服务器验证缓存不新鲜了,那么随后将有第二条响应到来.这意味着当前请求并没有完成,只是暂时显示缓存的数据,等到服务器验证缓存的新鲜度之后才会将请求标记为完成
response.intermediate = true;
// 将响应投递给用户,然后加入网络请求队列中去.
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;
}
}
}
}
再看下网络队列
public NetworkDispatcher(BlockingQueue<Request<?>> queue,
Network network, Cache cache,ResponseDelivery delivery) {
mQueue = queue;
mNetwork = network;
mCache = cache;
mDelivery = delivery;
}
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
long startTimeMs = SystemClock.elapsedRealtime();
Request<?> request;
try {
// 从网络队列中取出一个请求,没有请求则一直等待.
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 (request.isCanceled()) {
request.finish("network-discard-cancelled");
continue;
}
addTrafficStatsTag(request);
// 执行网络请求.
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
// 如果响应码为304,并且我们已经传递了一次响应,不需要再传递一次验证的响应,意味着本次请求处理完成。也就是说该请求的缓存是新鲜的,我们直接使用就可以了。
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
continue;
}
// 在工作线程中想响应数据解析成我们需要的Response对象,之所以在工作线程进行数据解析,是为了避免一些耗时操作造成主线程的卡顿.
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
// 如果允许,则将响应数据写入缓存,这里的缓存是需要服务器支持的,这点我们接下来再说
// 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");
}
// 传递响应数据.
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);
}
}
}
private void processRequest() throws InterruptedException {
// Take a request from the queue.
Request<?> request = mQueue.take();
long startTimeMs = SystemClock.elapsedRealtime();
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");
request.notifyListenerResponseNotUsable();
return;
}
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");
request.notifyListenerResponseNotUsable();
return;
}
// 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);
request.notifyListenerResponseReceived(response);
} catch (VolleyError volleyError) {
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
parseAndDeliverNetworkError(request, volleyError);
request.notifyListenerResponseNotUsable();
} 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);
request.notifyListenerResponseNotUsable();
}
}
网络调度线程也是不断地从队列中取出请求并且判断该请求是否被取消了。如果该请求没有被取消,就去请求网络,并把网络请求的数据回调回主线程。请求网络时调用Network的performRequest()方法
mDelivery.postResponse(request, response);
@Override
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
request.markDelivered();
request.addMarker("post-response");
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}
ResponseDeliveryRunnable里面做了什么?
@SuppressWarnings("unchecked")
@Override
public void run() {
// NOTE: If cancel() is called off the thread that we're currently running in (by
// default, the main thread), we cannot guarantee that deliverResponse()/deliverError()
// won't be called, since it may be canceled after we check isCanceled() but before we
// deliver the response. Apps concerned about this guarantee must either call cancel()
// from the same thread or implement their own guarantee about not invoking their
// listener after cancel() has been called.
// 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();
}
}
上面代码可以看到如果响应成功将会执行Request的deliverResponse方法,并把响应结果传进去,如果响应失败,就执行deliverError方法,并把响应失败的对象传进去。接着我们就看看Request的deliverResponse都干了什么。Request的子类有很多,这里就拿StringRequest来做参考。
@Override
protected void deliverResponse(String response) {
Response.Listener<String> listener;
synchronized (mLock) {
listener = mListener;
}
if (listener != null) {
listener.onResponse(response);
}
}
错误的回调在父类Request中可以找到
public void deliverError(VolleyError error) {
Response.ErrorListener listener;
synchronized (mLock) {
listener = mErrorListener;
}
if (listener != null) {
listener.onErrorResponse(error);
}
}
拿到响应结果之后,如果请求成功则回调onResponse
,如果请求失败则回调onErrorResponse,整个流程就是这样了。
总结
(1)Volley是如何把请求的数据回调回主线程中的?
使用Handler.postRunnable(Runnable)方法回调回主线程中进行处理,ExecutorDelivery的构造方法中可以看到这段代码,如下所示
public ExecutorDelivery(final Handler handler) {
// Make an Executor that just wraps the handler.
mResponsePoster = new Executor() {
@Override
public void execute(Runnable command) {
handler.post(command);
}
};
}
总结就是持有主线程的handler
(2)Volley开启了几个后台线程?
总共开启了5个线程:1个缓存调度线程和4个网络调度线程,并且线程的优先级为10,即后台线程。Volley其实并没有开启线程池去维护线程,而是硬性地开了5个线程
(3)为什么说volley适合多频网络请求
ByteArrayPool
ByteArrayPool产生背景
根据类名,知道这是一个字节数组缓存池。没错,就是用来缓存 网络请求获得的数据。
当网络请求得到返回数据以后,我们需要在内存开辟出一块区域来存放我们得到的网络数据,不论是json还是图片,都会存在于内存的某一块区域,然后拿到UI显示,然而客户端请求是相当频繁的操作,想一下我们平时使用知乎等一些客户端,几乎每一个操作都要进行网络请求(虽然知乎大部分是WebView)。那么问题来了:这么频繁的数据请求,获得数据以后我们先要在堆内存开辟存储空间,然后显示,等到时机成熟,GC回收这块区域,如此往复,那么GC的负担就相当的重,然而Android客户端处理能力有限,频繁GC对客户端的性能有直接影响。我们能不能减少GC和内存的分配呢?我想这个问题就是这个类的产生背景。
实现原理(怎么实现缓存从而减少GC)
在ByteArrayPool中维护了两个List集合。
属性名 作用 类型
mBuffersByLastUse 按照最近使用对byte[]排序 LinkedList
mBuffersBySize 按照byte[]大小对byte[]排序 ArrayList
通过上述两个属性的作用可以分析出它们是ByteArrayPool类对byte[]的管理。
从缓冲区取空间
当请求数据返回以后,我们不是急于在内存开辟空间,而是从ByteArrayPool中取出一块已经分配的内存区域。此时会调用ByteArrayPool的getBuf(int)方法,来得到一块参数大小的区域
方法首先检查 要插入的数据大小有没有超出边界,如果没有,利用二分法找到插入位置,将数据插入到上述的两个集合,完成排序。然后更新缓冲池的大小,以方便从缓冲区中取存储空间。
ByteArrayPool利用getBuf和returnBuf以及mBuffersByLastUse和mBuffersBySize完成字节数组的缓存。当需要使内存区域的时候,先从已经分配的区域中获得以减少内存分配次数。当空间用完以后,在将数据返回给此缓冲区。这样,就会减少内存区域堆内存的波动和减少GC的回收,让CPU把更多的性能留给页面的渲染,提高性能。
https://blog.youkuaiyun.com/u013233097/article/details/52793351 答案来自这篇博主
总结就是内存做了一层字节缓存
(4)全局共享RequestQueue
RequestQueue没有必要每个Activity里面都创建,全局保有一个即可。这时候自然想到使用Application了。我们可以在Application里面创建RequestQueue,并向外暴露get方法。