Volley原始碼解析(一)——傳送請求與結束請求
Volley是一個Android HTTP庫,只支援非同步方式。
傳送請求樣例
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);
傳送請求原始碼分析
一個請求的生命週期如下圖:
從上圖可以看出,在Volley中會有三個執行緒:UI執行緒負責發請求和收響應;快取分發器;網路分發器。
Volley類中只負責一件事情,就是建立一個RequestQueue物件,用於存放請求,該物件最好是單例的,供整個APP使用。
其具體實現如下:
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
//建立快取目錄
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
String userAgent = "volley/0";
//更新userAgent欄位
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
}
//對HttpStack賦值
if (stack == null) {
//如果SDK大於8,使用HurlStack,否則使用HttpClientStack
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物件
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}
RequestQueue的構造方法中有兩個引數,一個是Cache,負責將響應儲存到磁碟,一個是Network,負責執行HTTP操作。
下面先看一下RequestQueue的內部定義,RequestQueue內部主要有四個欄位是在初始化的時候指定的,分別是:
private final Cache mCache;
private final Network mNetwork;
/** 分發HTTP響應 */
private final ResponseDelivery mDelivery;
/** 網路分發器 */
private NetworkDispatcher[] mDispatchers;
RequestQueue的構造方法一共有三個,但最終都會呼叫下面的這個構造器,如下:
public RequestQueue(Cache cache, Network network, int threadPoolSize,
ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}
RequesteQueue#start()
當建立好RequestQueue之後,呼叫了start()方法,start()方法如下:
public void start() {
stop(); // Make sure any currently running dispatchers are stopped.
// 建立快取分發器,然後啟動
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
// 建立網路分發器,然後啟動
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
可以看到RequestQueue的start方法就是啟動其內部的分發器,主要包括一個快取分發器和多個網路分發器,網路分發器的數量實在構造方法中設定的,預設為4個。
前面的例子中,當建立好Request和RequestQueue之後,就將Request放進RequestQueue中就可以了。
RequestQueue#add()方法
add()方法實現如下:
public <T> Request<T> add(Request<T> request) {
//Request和RequestQueue關聯
request.setRequestQueue(this);
//將請求加入到Set中
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
// Process requests in the order they are added.
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");
// 如果這個請求不應該被快取,那麼直接新增進網路佇列中
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
//如果該請求已經在執行了
if (mWaitingRequests.containsKey(cacheKey)) {
Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
if (stagedRequests == null) {
stagedRequests = new LinkedList<Request<?>>();
}
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.put(cacheKey, null);
mCacheQueue.add(request);
}
return request;
}
}
這裡面涉及到RequestQueue中的多個個集合,定義分別如下:
//如果當前請求已經在執行了,那麼將會加入到該集合中
private final Map<String, Queue<Request<?>>> mWaitingRequests =
new HashMap<String, Queue<Request<?>>>();
//當前正在執行的請求
private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();
//快取請求優先順序佇列
private final PriorityBlockingQueue<Request<?>> mCacheQueue =
new PriorityBlockingQueue<Request<?>>();
//網路請求優先順序佇列
private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
new PriorityBlockingQueue<Request<?>>();
可以看到,add()方法主要做的就是根據不同情況將請求加入到不同的佇列中。由於快取請求佇列和網路請求佇列都是使用的優先順序佇列,所以可以給Request設定優先順序。
1. 如果請求不應該從快取中得到,那麼直接加入到網路請求佇列中;
2. 如果請求已經在執行了,那麼將其加入到正在執行的一個HashMap中,其中key是請求,值是一個請求的佇列;
3. 如果請求沒有在執行,那麼將其加入到快取佇列中。
在這裡,我們已經將一個請求提交了,下面看一下,Request的快取鍵值是如何得到的。
Request#getCacheKey()
public String getCacheKey() {
return getUrl();
}
public String getUrl() {
return mUrl;
}
可以看到一個Request的鍵值就是其URL。
現在考慮一個問題:建立了同一個相同URL的兩個Request,或者說同一個Request新增到RequestQueue兩次,情況是怎麼樣的呢?下面分別分析:
RequestQueue新增同一相同URL的兩個Request物件
設為Request1和Request2,新增時,由於Request沒有重寫equals方法和hashCode方法,所以mCurrentRequests會人為這是兩個不同的請求,都新增進Set,然後比如說Request1首先獲得了mWaitingRequests的鎖,由於mWaitingRequests中還沒有該URL,所以被新增進mWaitingRequests和放到了快取佇列中,然後當Request2再獲取到mWaitingRequests時候,由於已經有了URL,所以會在mWaitingRequests中建立一個連結串列並把該請求放入連結串列中,從而可以看出同一時刻相同URL的請求只會被執行一次。不過具體放在連結串列中的請求在Request1被處理之後是如何處理的,下面一篇部落格會分析到。
Request新增同一相同的Request兩次
經過前面的分析,可以知道,在mCurrentRequests中後一個Request會代替前一個Request,而後一個Request會被放入mWaitingRequests的連結串列中。可以發現同一個Request物件即在快取佇列中,又在待處理的佇列中。
下面就Request被執行完之後,看是怎樣操作的來解釋上面兩個問題。
結束請求原始碼分析
當想取消一個Request的執行時,可以呼叫RequestQueue的finish()來主要取消執行,也可以在Request被正常執行完之後自己呼叫finish()方法,下面先從Request的finish()方法看起,其實現如下:
Request#finish()
void finish(final String tag) {
if (mRequestQueue != null) {
mRequestQueue.finish(this);
}
if (MarkerLog.ENABLED) {
final long threadId = Thread.currentThread().getId();
if (Looper.myLooper() != Looper.getMainLooper()) {
// If we finish marking off of the main thread, we need to
// actually do it on the main thread to ensure correct ordering.
Handler mainThread = new Handler(Looper.getMainLooper());
mainThread.post(new Runnable() {
@Override
public void run() {
mEventLog.add(tag, threadId);
mEventLog.finish(this.toString());
}
});
return;
}
mEventLog.add(tag, threadId);
mEventLog.finish(this.toString());
} else {
long requestTime = SystemClock.elapsedRealtime() - mRequestBirthTime;
if (requestTime >= SLOW_REQUEST_THRESHOLD_MS) {
VolleyLog.d("%d ms: %s", requestTime, this.toString());
}
}
}
可以看到,首先也是呼叫了RequestQueue的finish()方法,下面再來分析RequestQueue的finish()方法。
RequestQueue#finish()
void finish(Request<?> request) {
// 首先從當前執行集合中刪除
synchronized (mCurrentRequests) {
mCurrentRequests.remove(request);
}
//如果請求應該被快取
if (request.shouldCache()) {
synchronized (mWaitingRequests) {
//得到請求的鍵值,即URL
String cacheKey = request.getCacheKey();
//得到與URL關聯的等待佇列
Queue<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);
//如果佇列不為null
if (waitingRequests != null) {
//將所有請求都加入到快取佇列中
mCacheQueue.addAll(waitingRequests);
}
}
}
}
這兒,可以看到,首先是從Set集合中刪除當前請求,然後判斷請求是否應該被快取,這和add()方法裡面是相一致的,add()方法裡只有請求允許使用快取,才會被加入到URL關聯的等待佇列中。然後就是將與URL關聯的請求都加入到快取佇列中。下面就上面兩個問題再次給出解釋。
1. RequestQueue新增同一相同URL的兩個Request物件:對於這種情況,Request1被執行,Request2被存在等待佇列中,當Request1執行完成後,將會從URL關聯的佇列中得到Request2物件,然後將Request2加入到快取佇列,由於Request1之前已經有快取結果了,所以執行Request2時只需要經過CacheDispatcher就可以得到結果然後finish了,可以發現這種情況下,同一個URL的只會進行一次網路請求,其餘的都是走快取請求;不過這是針對於可以快取響應的情況,如果不能快取響應,那麼都會直接加入到網路請求中執行兩次網路操作。
2. Request新增同一相同的Request兩次:當執行了第一次之後,就從Set中成功移除了Request,然後再從等待佇列中取出,加入到快取佇列,可以當發現這一次的Request依然會走CacheDispatcher中一趟。
至此,我們將一個請求提交給了RequestQueue,那麼RequestQueue是如何執行請求,又是如何將響應交付給UI執行緒處理呢?這一部分,我們下一篇再講。下一篇文章Volley原始碼解析(二)——CacheDispatcher將會介紹CacheDispatcher是如何進行快取分發的。
相關推薦
Volley原始碼解析(一)——傳送請求與結束請求
Volley是一個Android HTTP庫,只支援非同步方式。 傳送請求樣例 final TextView mTextView = (TextView) findViewById(R.id.text); ... // Instantiate
Dubbo原始碼解析(一)請求排程器 Dispatcher
排程器 Dispatcher 排程策略 all 所有訊息都派發到執行緒池,包括請求,響應,連線事件,斷開事件,心跳等。 direct 所有訊息都不派發到執行緒池,全部在 IO 執行緒上直接執行。 message 只有請求響應訊息派發到執行緒池,其它連線斷開
Android Volley解析(一)之GET、POST請求篇
一、 Volley 的地位 自2013年Google I/O 大會上,Google 推出 Volley 之後,一直到至今,由於其使用簡單、程式碼輕量、通訊速度快、併發量大等特點,倍受開發者們的青睞。 先看兩張圖,讓圖片告訴我們 Volley 的用處; 第一
Spring原始碼解析(一)——元件註冊1
一、@Configuration&@Bean給容器中註冊元件 public class Person { private String name; private Integer age; public Person() { } public
認真的 Netty 原始碼解析(一)
本文又是一篇原始碼分析文章,其實除了 Doug Lea 的併發包原始碼,我是不太愛寫原始碼分析的。 本文將介紹 Netty,Java 平臺上使用最廣泛的 NIO 包,它是對 JDK 中的 NIO 實現的一層封裝,讓我們能更方便地開發 NIO 程式。其實,Netty 不僅僅是 NIO 吧,但是,基本上大家
EventBus原始碼解析(一)—訂閱過程
1.EventBus原始碼解析(一)—訂閱過程 2.EventBus原始碼解析(二)—釋出事件和登出流程 前言 最近發現EventBus用起來是真的方便,本來對於EventBus我對於這個框架的原始碼的閱讀的優先順序是比較低的,因為這個框架不像OkHttp,Gli
【MapReduce詳解及原始碼解析(一)】——分片輸入、Mapper及Map端Shuffle過程
title: 【MapReduce詳解及原始碼解析(一)】——分片輸入、Mapper及Map端Shuffle過程 date: 2018-12-03 21:12:42 tags: Hadoop categories: 大資料 toc: true 點選檢視我的部落格:Josonlee’
Redis5.0原始碼解析(一)----------簡單動態字串(SDS)
基於Redis5.0 Redis 沒有直接使用 C 語言傳統的字串表示(以空字元結尾的字元陣列,以下簡稱 C 字串), 而是自己構建了一種名為簡單動態字串(simple dynamic string,SDS)的抽象型別, 並將 SDS 用作 Redis 的預設字串表示。
OkHttp原始碼解析(一)
簡單使用 OkHttpClient okHttpClient = new OkHttpClient.Builder().build(); Request request = new Request.Builder() .url("www.bai
OKHttp 3.10原始碼解析(一):執行緒池和任務佇列
OKhttp是Android端最火熱的網路請求框架之一,它以高效的優點贏得了廣大開發者的喜愛,下面是OKhttp的主要特點: 1.支援HTTPS/HTTP2/WebSocket 2.內部維護執行緒池佇列,提高併發訪問的效率 3.內部維護連線池,支援多路複用,減少連線建立開銷 4.
SLF4J原始碼解析(一)
提出問題 閱讀原始碼之前,首先提幾個問題 SLF4J是如何整合不同的日誌框架的 Class Path中為什麼只能有且僅有一種日誌框架的binding 這段文字摘錄自官網:In your code, in addition to slf4j-api-1.8.0-beta2.jar, y
Java容器——HashMap(Java8)原始碼解析(一)
一 概述 HashMap是最常用的Java資料結構之一,是一個具有常數級別的存取速率的高效容器。相對於List,Set等,結構相對複雜,本篇我們先對HashMap的做一個基本說明,對組成元素和構造方法進行介紹。 二 繼承關係 首先看HashMap的繼承關係,比較簡單,實現了Map和序列化
jQuery深入之原始碼解析(一)
總體架構 可以看出來jQuery主要有三個模組: 入口模組、功能模組、底層支援模組。 - 入口模組 在構造jQuery物件模組中,如果在呼叫建構函式建立jQuery物件時,會呼叫選擇器
python 原始碼解析(一)
為了看懂 python 原始碼 ,特地學了 c++ ,依然看不懂,看了個大概,先留個坑,慢慢填。 先從 python dict 物件開始看起。 python dict 物件 是鍵值對的 一種結構,類似於 java 的hashmap 物件。 dictd 物件 , 每個鍵值對 ,
ElasticSearch原始碼解析(一):轉一篇介紹中文分詞的文章
轉自:http://www.cnblogs.com/flish/archive/2011/08/08/2131031.html 基於CRF(Conditional Random Field)分詞演算法 論文連結:http://nlp.stanford.edu/pubs/
ActiveMQ原始碼解析(一):聊聊broker
一、Broker 訊息佇列核心,相當於一個控制中心,負責路由訊息、儲存訂閱和連線、訊息確認和控制事務 1.Broker介面 定義了一些獲取broker本身相關資訊,新增connection、destination、session、訊息生產者、控制事務
StringBuffer和StringBuilder原始碼解析(一)--構造方法
前幾天接到阿里巴巴的電話面試,被虐的一塌糊塗。當時問我列印字串可以用這種方式System.out.println(“a” + “b” + “c”),但是一般我們不用這種方式,而要用StringBuff
jdk原始碼解析(一)
1、先說一下原始碼解析的過程:JDK-->JRE-->JVM(以openJDK代替)注意:這裡要了解jdk和jre和jvm他們分別是什麼?以及他們的關係才可以繼續。這裡先上一章從網上下載的關係圖方便理解 2、筆者本地的jdk是oraclejdk,jvm所
Spark原始碼解析(一)
RDD之getNarrowAncestors內部方法分析 最近開始spark的原始碼攻關,其實看原始碼一直是我最怕的東西,因為太多、太雜、太深導致不能夠很好的把我脈絡導致每次最後都放棄。有人跟我說看原
Spring基於註解形式的 AOP的原理流程及原始碼解析(一)
在Spring的配置類上添加註解@EnableAspectJAutoProxy: @Configuration @EnableAspectJAutoProxy(proxyTargetClass = true) public class MvcContextCo