江南带你从源码教你解析Volley

来源:互联网 发布:知乎怎么发帖子 编辑:程序博客网 时间:2024/04/28 08:08
Volley其实是对okhttp的请求的再一次封装。
okhttp,httpUrlconnection,httpClitConnection

volley不仅可以对json,String,array等可以请求外,还可以对图片进行网络请求       
         int width=0;
int heigh=0;
RequestQueue requestQueue = Volley.newRequestQueue(this);
ImageRequest imageRequest = new ImageRequest("http://192.168.56.1:8080/images/1.jpg",
new Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
iv.setImageBitmap(response);
}
},
width, heigh,
Bitmap.Config.ARGB_8888,
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
}
});
requestQueue.add(imageRequest);
}
这里的width,heigt,如果我们不设置的话就是原图的大小。如下

假如我们给它设置了一个值,他就按照设置的宽度,和高度进行显示




二,1:使用NetworkImageView加载图片,这种方式加载图片的一个好处在于不必担心图片错位问题
NetworkImageView NImageView=new NetworkImageView(getApplicationContext());
RequestQueue newRequestQueue = Volley.newRequestQueue(getApplicationContext());
RequestQueue queue=Volley.newRequestQueue(getApplicationContext());
ImageLoader imageLoader=new ImageLoader(queue, new Imagecache());
NImageView.setImageUrl("", imageLoader);
:2:首选创建一个NetworkImageView的的对象,然后传入一个加载网络的地址值,还有一个ImageLoader对象,创建ImageLoader对象需要一个VolleyQueen和图片的缓存ImageCache,所以我们还得自己写一个类实现ImageCache接口,这里面我们使用的是LRuCache算法。我们得自己设置lrucache的缓存的大小啊之类的。
public class Imagecache implements ImageCache {
private int cacheSize=(int) (Runtime.getRuntime().maxMemory()/8);
LruCache<String, Bitmap> lruCache=new LruCache<String, Bitmap>(cacheSize){
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount();
};
};
@Override
public Bitmap getBitmap(String url) {
return lruCache.get(url);
}
 
@Override
public void putBitmap(String url, Bitmap bitmap) {
lruCache.put(url, bitmap);
}

Lrucache是一种缓存的策略,这样的的话Volley在调用的时候就会通过Lrucache从里面添加进去,再从里面去出bitmap,这样就可以缓存了

Lrucache里面封装了一个LinkedHashMap,他的创建已根据三个参数,一个参数:初始容量,第二参数就是达到什么时候它才开始增加,第三个参数:true,就是按照最近最少原则从里面去,false,则按照自然添加的顺序,查看LinkedHashMap的源码可知道。大概就是说LinkedHashMap的在开始添加进map集合和,假如下次在请求访问A,就把A放到上面去了,当容器装不下的时候就把下面的干掉,因为下面是最近不怎么用的,所以就达到了最近最少的原则。




这里注意的是我们让Lrucache给我们检测什么时候清理缓存,那必须得告诉它缓存的大小是多少,以及每个bitmap对象的大小,这里的话我们必须得给他们统一一下单位

可以看到LinkedHashMapHashMap的基础上又增加了双向链表。也就是说LinkedHashMap的结构是数组+单向链表+双向链表。到此LruCache的基本原理我们都分析清楚了,并且梳理了HashMapLinkedHashMap的内部实现原理,虽然我们在开发项目的时候对基本数据结构的内部原理不太关注,但是我们也应该学习其内部原理。毕竟基础才是最重要的。说到HashMap又想到了androidSDK中的SparseArray可以替代keyInteger类型的HashMap,感性却的可以看这篇文章
要实现 LRU 缓存,我们首先要用到一个类 LinkedHashMap
 
用这个类有两大好处:一是它本身已经实现了按照访问顺序的存储,也就是说,最近读取的会放在最前面,最最不常读取的会放在最后(当然,它也可以实现按照插入顺序存储)。第二,LinkedHashMap 本身有一个方法用于判断是否需要移除最不常读取的数,但是,原始方法默认不需要移除(这是,LinkedHashMap 相当于一个linkedlist),所以,我们需要 override 这样一个方法,使得当缓存里存放的数据个数超过规定个数后,就把最不常用的移除掉。关于 LinkedHashMap 中已经有详细的介绍。
 
来源: http://wiki.jikexueyuan.com/project/java-collection/linkedhashmap-lrucache.html

这样我们就可以使用Volley自带的空间com.android.volley.toolbox.NetworkImageView,实现了图片的显示问题,另外这个肯定不会出现的错位等问题。


其实volley已经帮我们做了内存,和磁盘的缓存,但是由于这个volley的缓存是基于http协议的,后面通过源码可以知道他的缓存是得进行判断response的消息头里面的有效时间,所以这里我们自己得封装得写一个缓存机制,这就用到了ImageLoader


Volley只是保证取消掉的请求不会进行回调而已,但并没有说可以中断任何请求。由此可见即使是Volley也无法做到中断一个正在执行的线程,如果有一个线程正在执行,Volley只会保证在它执行完之后不会进行回调,但在调用者看来,就好像是这个请求就被取消掉了一样。

下面我们看看Volley的底层源码
首先介绍的是StringRequest,首先创建,这里看到接受了成功的监听

然后

为什么成功的监听在StringRequest里,而失败监听放到Request中,可能是因为失败的监听进行的回调操作都一样,但成功每个子类都不同,所以交给子类去实现。

下面我们看看Volley.newRequestQueen(context)做了什么?
//2.创建请求队列RequestQueue requestQueue = Volley.newRequestQueue(getContext());


这里根据版本的不同创建不同的网络请求对象,因为谷歌工程师当初在9版本一下的时候HttpUrlconntion有个明显的bug,为了兼容,所以低于9一下用了HttpClint


然后下面对stack进行了封装,NetWork netWork=new BasicNetWork(stack),这个类才是真正的发起网络请求的,其实调用它发送网络请求就相当于调用HttpUrlconntion与HttpClint发起网络请求。
然后我们发现在这个方法里还创建了RequestQueen对象,并且把缓存目录与网络    请求对象一起传进去了,将来请求得到的数据就可以直接放进去磁盘缓存,内存缓存里面了






追进方法看到

下面我们看下requestQueue.add(imageRequest)做了什么?


追源码可看到这个确实是个HashSet

可以看出add就是把我们的请求添加进一个set集合中,这里面封装了所有未结束的请求,为什么说是未完成的请求?


看下面源码可知,请求都结束完成以后都会走finish方法把请求从set集合中清除,所以但凡剩下的都是为完成的请求。


这里大家是不是有个疑问到底是什么时候我们给RequestQueen中把Request移除的呢,也就是说Request是怎么拿到RequestQueen对象然后调用他的finish方法的呢?

因为在上面我就已经说过了进RequestQueen的时候就把当前的引用赋值给Request了,所以当Request结束的时候就可以拿到这个RequestQueen引用调用它的finish方法把自己从set集合中清除。好机智的想法。


接着往下看,我们看到这个方法判断是否从本地取缓存,默认情况下返回为true,那么!true=false,所以就不走网络,先从下面本地拿缓存。当然这个我们是可以进行设置的。



我们在开发的时候可以通过设置setShouldCache(fasle)这样的话就可以直接从网络中请求数据了,即便本地是有的,也不走本地
stringRequest.setShouldCache(false);
这样设置就不会从本地取了

这里值得提一嘴的就是mNetWorkQueen,这里保存着直接从网络获取数据的请求。这就是说这里的请求取出来都事可以直接发起网络请求的
mNetworkQueue

接着看下面的代码很重要,首先MCacheQueen保存所有需要从缓存取数据的请求,MWaitingRequests是保存同一个url后续发起的请求。


这里cacheKey就是对应的url
当第一次请求进来的时候MWaitingRequests是没有对应的url的所以返回的是false,然后走下面的else,把这个url设置进去,并且把这个请求添加进缓存请求里面,假如下个也是这个url请求,这时候由于MWaitingRequests是有key的,所以会走前面判断,由于MWaitingRequests在刚刚传的是null,所以那个MWaitingRequests.get()返回的也是null.,然后就给stageRequest设置一个linkedList(),并且添加了当前的请求,把请求的这个集合传给MWaitingRequests,也就是说第二个请求以及第二个后面来的请求都是放这个MWaitingRequests里面了

那么我们这些后面的请求什么时候该怎么办,并且 什么时候才能轮到这些相同url的Request请求执行呢?
通过追源码我们可以知道,当第一个请求在调用RequestQueen的finish方法的时候把所有的等待请求队列MWaitingRequests清空,并添加到缓存队列MCacheQueen中去


接下来就是我们获得了mNetWorkQueen,mCurrentRequest,mCacheQueen,mWaitingRequests 那么接下来是由谁来干网络请求这个活呢?

接着看

再追进去发现CacheDispatcher是个线程


那么这个线程做了什么呢?从他的run方法中
首先提升线程的优先级,然后把磁盘中的缓存拿到内存中


接下来





其实这里的response就是我们需要的信息,由于这些信息都是封装在网络请求返回的entry中,我们把它进行一次封装获取他的消息头,数据。





然后我么追进去发现把这两个参数封装成一个任务



然后调用他的excute方法,把这个对象发送到主线程


这时候我们就想知道ExcetorDelivery什么时候被创建的?我通过看RequestQueen对象的时候就有
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize,
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
然后调用下面这个
public RequestQueue(Cache cache, Network network, int threadPoolSize,
ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}
我们把当前线程的getMainlooper()放进handler中,获得快乐delivery。




通过追看

然后我们再看CacheDispatcher的里的run执行这个操作就能够理解了

再然后就是




其实就是我们的CacheDispatcher分发器就持有一个主线程的handler然后往把消息发到主线程


追进mRequest.deliverResponse(mResponse.result);源码可以看出成功以后数据,通过不同子类去实现的



调用错误实现的接口。


上面的情况是假如在CacheDispatcher中的cachequeen中不断地循环找到缓存的情况,假如没有缓存的话呢?


通过查看源码发现没有缓存的Request又被放到NewWorkQueen中。


由上面的所有我们先把流程梳理一遍当一个请求进来的时候都会被放到cacheQueen中中,除了相同的的url会当道WaitingQueen中,然后cacheQueen通过开启一个子线程CacheDispatcher中的一个无限循环不断地从里面去缓存,如果有的话就把缓存信息handler发送出去,如果没有的话将Request当到NetWorkQueen中


下面我们看看NetWorkQueen里面做了什么?
在RequestQueen的start()方法中上面是开启CacheDispatcher子线程,下面是开启NewWorkDiapater子线程


NetWorkQueen也是一个线程


然后我们看这四个线程的run分别做了什么?
这个take()也是一个消息堵塞机制做的,返回为null没有消息的话就堵塞,一旦有消息它就被激活获取Request

这里是四个线程从消息队列里面去,出来的话肯定是按照顺序的,但是由于每个线程完成的顺序肯定是不一样的,所以最后哪个先返回时不能确定的

然后
这里的mNetWork有可能是HttpUrlconntion或者HttpClint发送请求到服务器获取数据


接着看下面的代码


点进去发现是发现是进行的磁盘缓存

这根上面的CacheDispatcher刚开始run方法的时候把磁盘中的缓存写到内存中不谋而合,也就说他们既做了磁盘缓存也做了内存缓存

然后通过ExcetorDelivery把请求得到的数据发送到主线程中去



一般来说一个请求进来默认请求都先放进CacheQueen中,通过CacheDispatcher不断地从缓存中去Request,在去消息之前先把磁盘中的所有的缓存(请求头,实体类)写到内存中,这样就变成了内存缓存了,当有缓存的时候就通过ExcetorDelivery发布请求的结果,没有的话就把Request添加进NewWorkQueen中,接着RequestQueen调用四个NetWorkDispatch线程不断地从NetWorkQueen去请求,当取到请求后调用NetWork(其实就是封装HttpUrlconntion与HttpClint)从网络中获取数据,然后通过ExcetorDelivery不断地发送返回的数据到主线程中并且写进磁盘。
















0 0
原创粉丝点击