江南帶你從源碼教你解析Volley

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不斷地發送返回的數據到主線程中並且寫進磁盤。
















發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章