Volley簡介
Volley 是 Google I/O 2013上發(fā)布的網(wǎng)絡(luò)通信庫,使網(wǎng)絡(luò)通信更快、更簡單、更健壯。Volley特別適合數(shù)據(jù)量不大但是通信頻繁的場景,而對于大數(shù)據(jù)量的網(wǎng)絡(luò)操作,比如說下載文件等,Volley的表現(xiàn)就會非常糟糕。Volley提供的功能有:
- JSON,圖像等的異步下載;
- 網(wǎng)絡(luò)請求的排序(scheduling)
- 網(wǎng)絡(luò)請求的優(yōu)先級處理
- 緩存
- 多級別取消請求
- 和Activity和生命周期的聯(lián)動(Activity結(jié)束時同時取消所有網(wǎng)絡(luò)請求)
Volley的優(yōu)點
- 體積小,使用Volley可以使用Volley.jar或者通過gradle導(dǎo)入依賴,全部只有42個類,只有100多k大小。
- 非常適合進行數(shù)據(jù)量不大,但通信頻繁的網(wǎng)絡(luò)操作。
- 可直接在主線程調(diào)用服務(wù)端并處理返回結(jié)果
- 可以取消請求,容易擴展,面向接口編程。
- 網(wǎng)絡(luò)請求線程NetworkDispatcher默認開啟了4個,可以優(yōu)化,通過手機CPU數(shù)量。
- 通過使用標準的HTTP緩存機制保持磁盤和內(nèi)存響應(yīng)的一致。
Volley的缺點
- 使用的是HttpClient、HttpURLConnection
- Android6.0不支持HttpClient了,如果想支持得添加org.apache.http.legacy.jar或者在app module中的build.gradle中加入useLibrary 'org.apache.http.legacy'
- 對大文件下載Volley的表現(xiàn)非常糟糕
- 只支持Http請求
- 圖片加載性能一般
Volley的基本用法
關(guān)于Volley的使用,官方文檔的地址是https://developer.android.com/training/volley/index.html
Volley的用法非常簡單,發(fā)起一條HTTP請求,然后接收HTTP響應(yīng)。首先需要獲取到一個RequestQueue對象,可以調(diào)用如下方法獲取到:
RequestQueue queue = Volley.newRequestQueue(context);
RequestQueue是一個所有請求的隊列對象,它可以緩存所有HTTP請求,它的內(nèi)部有兩個隊列:
/** The cache triage queue. */
private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<?>>();
/** The queue of requests that are actually going out to the network. */
private final PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<Request<?>>();
緩存請求的隊列和處理請求的隊列都是優(yōu)先級阻塞隊列,RequestQueue是按照一定的算法并發(fā)地發(fā)出這些請求。RequestQueue內(nèi)部的設(shè)計就是非常合適高并發(fā)的,因此我們不必為每一次HTTP請求都創(chuàng)建一個RequestQueue對象,這是非常浪費資源的,基本上在每一個需要和網(wǎng)絡(luò)交互的Activity中創(chuàng)建一個RequestQueue對象就足夠了。
接下來為了要發(fā)出一條HTTP請求,我們還需要創(chuàng)建一個StringRequest對象,如下所示:
String url ="http://shenhuniurou.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) {
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
}
}
);
StringRequest封裝了一個請求,包括請求地址,請求方式,請求結(jié)果監(jiān)聽等。它有兩個構(gòu)造方法
public StringRequest(int method, String url, Listener<String> listener, ErrorListener errorListener) {
super(method, url, errorListener);
mListener = listener;
}
public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {
this(Method.GET, url, listener, errorListener);
}
如果沒傳請求方式,那么默認是GET方式。最后把這個請求添加到RequestQueue中去。
// Add the request to the RequestQueue.
queue.add(stringRequest);
另外還需要在AndroidManifest.xml中添加用戶權(quán)限
<uses-permission android:name="android.permission.INTERNET"/>
如果我們要使用POST方式并向服務(wù)器傳遞參數(shù),那就必須在StringRequest的匿名類中重寫getParams方法
StringRequest stringRequest = new StringRequest(Request.Method.POST, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
}
}) {
@Override protected Map<String, String> getParams() throws AuthFailureError {
HashMap<String, String> params = new HashMap<>();
params.put("key1", "value1");
params.put("keyn", "keyn");
return params;
}
};
以上是使用StringRequest類來提交參數(shù)發(fā)起請求,StringRequest是繼承自Request<String>的,Request<T>是一個泛型抽象類,除了StringRequest,Request還有一個直接子類JsonRequest,不過JsonRequest也是一個泛型抽象類,它有兩個子類,JsonArrayRequest和JsonObjectRequest,用于請求JSON類型的數(shù)據(jù),用法如下:
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(
Request.Method.GET,
"http://www.weather.com.cn/data/sk/101110101.html",
null,
new Response.Listener<JSONObject>() {
@Override public void onResponse(JSONObject response) {
mTextView.setText("Response is: "+ response.toString());
}
},
new Response.ErrorListener() {
@Override public void onErrorResponse(VolleyError error) {
}
});
queue.add(jsonObjectRequest);
JsonArrayRequest jsonArrayRequest = new JsonArrayRequest(
Request.Method.POST,
url,
null,
new Response.Listener<JSONArray>() {
@Override public void onResponse(JSONArray response) {
}
},
new Response.ErrorListener() {
@Override public void onErrorResponse(VolleyError error) {
}
});
queue.add(jsonArrayRequest);
注意,并不是所有格式的數(shù)據(jù)都可以使用這兩個類來發(fā)起請求的,只有JSON格式的才可以,它提交的參數(shù)類型也是json格式的。
使用Volley加載網(wǎng)絡(luò)圖片
使用Volley加載網(wǎng)絡(luò)圖片使用的是Volley中的ImageRequest類來發(fā)起請求,ImageRequest同樣是Request類,不過它的泛型是Bitmap,和StringRequest、JsonRequest用法類似,先創(chuàng)建一個RequestQueue對象,創(chuàng)建一個Request對象,然后將Request對象添加到RequestQueue中即可。
final ImageView imageView = (ImageView) findViewById(R.id.imageView);
ImageRequest imageRequest = new ImageRequest(
"http://tuku.chengdu.cn/CHN_CommendationPic/20120319105921966875741.jpg",
new Response.Listener<Bitmap>() {
@Override public void onResponse(Bitmap response) {
imageView.setImageBitmap(response);
}
},
0,
0,
ImageView.ScaleType.FIT_XY,
Bitmap.Config.RGB_565,
new Response.ErrorListener() {
@Override public void onErrorResponse(VolleyError error) {
}
}
);
queue.add(imageRequest);
ImageRequest的構(gòu)造方法中有兩個,其中有一個已經(jīng)棄用了,我們看到一共有七個參數(shù),第一個url是圖片的地址,第二個參數(shù)是加載圖片成功后的回調(diào),里面返回的Bitmap位圖,第三個maxWidth和第四個maxHeight是用于指定允許圖片最大的寬度和高度,如果指定的網(wǎng)絡(luò)圖片的寬度或高度大于這里的最大值,則會對圖片進行壓縮,指定成0的話就表示不管圖片有多大,都不會進行壓縮;第五個參數(shù)scaleType表示圖片顯示的縮放類型,被棄用的那個構(gòu)造方法是沒有這個參數(shù)的,它默認使用ScaleType.CENTER_INSIDE,第六個參數(shù)Config是常量,用于指定顏色的屬性,其中ARGB_8888可以展示最好的顏色屬性,每個圖片像素占據(jù)4個字節(jié)的大小,而RGB_565則表示每個圖片像素占據(jù)2個字節(jié)大小。第七個參數(shù)是加載圖片失敗后的回調(diào)。
小結(jié):Request類的子類發(fā)送請求的步驟基本是一樣的,可分為三步:
- 創(chuàng)建RequestQueue對象
- 創(chuàng)建Request類的子類對象(StringRequest/JsonRequest/ImageRequest)
- 將Request的子類對象添加到RequestQueue對象中
除了使用ImageRequest可以來加載圖片之外,Volley中還可以使用ImageLoader
來加載圖片,ImageLoader內(nèi)部其實也是用ImageRequest來實現(xiàn)的,不過ImageLoader明顯要比ImageRequest更加高效,因為它不僅可以幫我們對圖片進行緩存,還可以過濾掉重復(fù)的鏈接,避免重復(fù)發(fā)送請求,使用方法:
RequestQueue queue = Volley.newRequestQueue(this);
ImageLoader imageLoader = new ImageLoader(queue, new ImageLoader.ImageCache() {
@Override public Bitmap getBitmap(String url) {
return null;
}
@Override public void putBitmap(String url, Bitmap bitmap) {
}
});
ImageLoader.ImageListener listener = ImageLoader.getImageListener(imageView, R.mipmap.ic_launcher, R.mipmap.ic_launcher_round);
imageLoader.get("http://tuku.chengdu.cn/CHN_CommendationPic/20120319105921966875741.jpg", listener);
因為ImageLoader不是繼承自Request類,所以它加載圖片的用法和之前那些不一樣了,大致步驟為:
- 創(chuàng)建RequestQueue對象
- 創(chuàng)建ImageLoader對象
- 創(chuàng)建ImageListener對象
- 調(diào)用ImageLoader的get方法
在創(chuàng)建ImageLoader對象時,我們傳了兩個參數(shù),RequestQueue和ImageCache,ImageCache是實現(xiàn)圖片緩存的的接口,當圖片加載成功后,會將圖片緩存起來,下次再加載同樣一張圖片時,就不用去網(wǎng)絡(luò)上加載而是直接在緩存中加載即可,這樣既節(jié)省了資源也節(jié)省了請求時間。
我們看到ImageCache是一個接口:
public interface ImageCache {
public Bitmap getBitmap(String url);
public void putBitmap(String url, Bitmap bitmap);
}
我們要自己實現(xiàn)圖片的緩存,只要實現(xiàn)這個接口就行了:
public class BitmapCache implements ImageLoader.ImageCache {
private LruCache<String, Bitmap> mCache;
public BitmapCache() {
int maxSize = 2 * 1024 * 1024;
mCache = new LruCache<String, Bitmap>(maxSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight();
}
};
}
@Override
public Bitmap getBitmap(String url) {
return mCache.get(url);
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
mCache.put(url, bitmap);
}
}
所以當我們創(chuàng)建ImageLoader對象時,new一個BitmapCache對象傳進去即可:
ImageLoader imageLoader = new ImageLoader(queue, new BitmapCache());
除了以上兩種加載圖片的方式之外,Volley還提供了另外一種,使用NetworkImageView,不同于以上兩種方式,NetworkImageView是一個自定義View,它是繼承自ImageView,具備ImageView控件的所有功能,并且在原生的基礎(chǔ)之上加入了加載網(wǎng)絡(luò)圖片的功能。NetworkImageView控件的用法要比前兩種方式更加簡單,大致可以分為以下幾步:
- 創(chuàng)建一個RequestQueue對象。
- 創(chuàng)建一個ImageLoader對象。
- 在布局文件中添加一個NetworkImageView控件。
- 在代碼中獲取該控件的實例。
- 設(shè)置要加載的圖片地址。
RequestQueue mQueue = Volley.newRequestQueue(this);
ImageLoader imageLoader = new ImageLoader(mQueue, new BitmapCache());
NetworkImageView networkImageView = (NetworkImageView) findViewById(R.id.networkImageView);
networkImageView.setDefaultImageResId(R.mipmap.ic_launcher);
networkImageView.setErrorImageResId(R.mipmap.ic_launcher);
networkImageView.setImageUrl("http://tuku.chengdu.cn/CHN_CommendationPic/20120319105921966875741.jpg", imageLoader);
使用ImageRequest和ImageLoader這兩種方式來加載網(wǎng)絡(luò)圖片,都可以傳入一個最大寬度和高度的參數(shù)來對圖片進行壓縮,但是由于NetworkImageView是一個控件,在加載圖片的時候它會自動獲取自身的寬高,然后對比網(wǎng)絡(luò)圖片的寬度,再決定是否需要對圖片進行壓縮。也就是說,壓縮過程是在內(nèi)部完全自動化的,并不需要我們關(guān)心,所以我們加載的時候不需要手動傳入最大寬高,NetworkImageView會始終呈現(xiàn)給我們一張大小剛剛好的網(wǎng)絡(luò)圖片,不會多占用任何一點內(nèi)存,如果你不想對圖片進行壓縮,只需要在布局文件中把NetworkImageView的layout_width和layout_height都設(shè)置成wrap_content就可以了,這樣NetworkImageView就會將該圖片的原始大小展示出來,不會進行任何壓縮。
實現(xiàn)自定義Request
一般如果請求的數(shù)據(jù)是字符串、圖片、JSON類型的數(shù)據(jù)時,我們都不需要自定義Request,但是如果這些還不滿足我們的需求時,就要自定義Request了,需要做的事有下面兩步:
- 繼承Request<T>泛型類,其中的泛型表示我們請求期望解析響應(yīng)的類型。
- 實現(xiàn)抽象方法
parseNetworkResponse()
和deliverResponse()
。
Gson是使用反射將Java對象轉(zhuǎn)換為JSON或從JSON轉(zhuǎn)換的庫。你可以定義與其對應(yīng)的JSON鍵具有相同名稱的Java實體對象,將Gson傳遞給類對象,Gson就可以將這個Java對象的各個字段自動填充值。以下是使用Gson進行解析的Volley請求的完整實現(xiàn):
public class GsonRequest<T> extends Request<T> {
private Gson mGson;
private Class<T> clazz;
private Map<String, String> headers;
private Response.Listener<T> listener;
public GsonRequest(int method, String url, Class<T> clazz, Map<String, String> headers, Response.Listener<T> listener, Response.ErrorListener errorListener) {
super(method, url, errorListener);
mGson = new Gson();
mClass = clazz;
mListener = listener;
}
public GsonRequest(String url, Class<T> clazz, Listener<T> listener,
ErrorListener errorListener) {
this(Method.GET, url, clazz, listener, errorListener);
}
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
return headers != null ? headers : super.getHeaders();
}
@Override
protected void deliverResponse(T response) {
listener.onResponse(response);
}
@Override
protected Response<T> parseNetworkResponse(NetworkResponse response) {
try {
String json = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
return Response.success(mGson.fromJson(json, clazz), HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
} catch (JsonSyntaxException e) {
return Response.error(new ParseError(e));
}
}
}
使用的時候我們縣創(chuàng)建一個對象實體類,定義其屬性和getter/setter方法,然后使用這個GsonRequest類解析時應(yīng)該這樣:
GsonRequest<JavaBean> gsonRequest = new GsonRequest<JavaBean>(
url, JavaBean.class,
new Response.Listener<JavaBean>() {
@Override
public void onResponse(JavaBean javaBean) {
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e("TAG", error.getMessage(), error);
}
});
mQueue.add(gsonRequest);
Volley源碼解析
先來看下面這張圖,是Volley官方文檔主頁上摳下來的,它解釋了Volley的工作流程和整體結(jié)構(gòu)
一般我們要分析一個框架的源碼,首先我們要找到它的入口,從Volley的使用方法來看,我們第一步是先通過Volley類的newRequestQueue(this)
創(chuàng)建一個RequestQueue對象,那就從這里開始吧。Volley類只有兩個方法,都是newRequestQueue
,一個是一個參數(shù),另一個是兩個參數(shù),我們直接看最終調(diào)用的那個:
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
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 = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}
我們傳進來的HttpStack是null,所以這里會先判斷sdk版本,如果是9以上就創(chuàng)建HurlStack
類對象,否則創(chuàng)建HttpClientStack
類型對象,至于HurlStack和HttpClientStack這兩個類是什么?我們點進去可以看到這個類的說明:
/**
* An {@link HttpStack} based on {@link HttpURLConnection}.
*/
public class HurlStack implements HttpStack {
/**
* An HttpStack that performs request over an {@link HttpClient}.
*/
public class HttpClientStack implements HttpStack {
也就是說HurlStack內(nèi)部是基于HttpURLConnection實現(xiàn)的,而HttpClientStack是基于HttpClient來實現(xiàn)的,這里為什么要根據(jù)sdk版本來分別選擇這兩種不同的類呢?
我們根據(jù)上面源碼中提示的文章地址http://android-developers.blogspot.com/2011/09/androids-http-clients.html可以知道,大多數(shù)的Android應(yīng)用程序都會使用HTTP協(xié)議來發(fā)送和接收網(wǎng)絡(luò)數(shù)據(jù),而Android中主要提供了兩種方式來進行HTTP操作,HttpURLConnection和HttpClient。這兩種方式都支持HTTPS協(xié)議、以流的形式進行上傳和下載、配置超時時間、IPv6、以及連接池等功能。但是在Android2.3之前,HttpURLConnection是不可靠,bug太多,而HttpClient比較穩(wěn)定,bug較少,API很多,基本上已經(jīng)滿足了開發(fā)者們的需求,因此很難在不破壞其兼容性的情況下進行拓展。
在Android2.2版本之前,HttpURLConnection一直存在著一些令人厭煩的bug。比如說對一個可讀的InputStream調(diào)用close()方法時,就有可能會導(dǎo)致連接池失效了。那么我們通常的解決辦法就是直接禁用掉連接池的功能。
在Android2.3版本的時候,加入了更加透明化的響應(yīng)壓縮。HttpURLConnection會自動在每個發(fā)出的請求中加入如下消息頭:Accept-Encoding: gzip,并處理相應(yīng)的返回結(jié)果。但是如果啟動了響應(yīng)壓縮的功能,HTTP響應(yīng)頭里的Content-Length就會代表著壓縮后的長度,這時再使用getContentLength()方法來取出解壓后的數(shù)據(jù)就是錯誤的了。正確的做法應(yīng)該是一直調(diào)用InputStream.read()方法來讀取響應(yīng)數(shù)據(jù),一直到出現(xiàn)-1為止。
在Android 2.3版本中還增加了一些HTTPS方面的改進,現(xiàn)在HttpsURLConnection會使用SNI(Server Name Indication)的方式進行連接,使得多個HTTPS主機可以共享同一個IP地址。除此之外,還增加了一些壓縮和會話的機制。如果連接失敗,它會自動去嘗試重新進行連接。這使得HttpsURLConnection可以在不破壞老版本兼容性的前提下,更加高效地連接最新的服務(wù)器。
在Android4.0版本中,我們又添加了一些響應(yīng)的緩存機制。當緩存被安裝后(調(diào)用HttpResponseCache的install()方法),所有的HTTP請求都會滿足以下三種情況:
所有的緩存響應(yīng)都由本地存儲來提供。因為沒有必要去發(fā)起任務(wù)的網(wǎng)絡(luò)連接請求,所有的響應(yīng)都可以立刻獲取到。
有條件的緩存響應(yīng)必須要有服務(wù)器來進行更新檢查。比如說客戶端發(fā)起了一條類似于“如果a.png這張圖片發(fā)生了改變,就將它發(fā)送給我” 這樣的請求,服務(wù)器需要將更新后的數(shù)據(jù)進行返回,或者返回一個304(Not Modified)狀態(tài)。如果請求的內(nèi)容沒有發(fā)生,客戶端就不會下載任何數(shù)據(jù)。
沒有緩存的響應(yīng)都是由服務(wù)器直接提供的。這部分響應(yīng)會在稍后存儲到響應(yīng)緩存中。
在Android2.2版本之前,HttpClient擁有較少的bug,因此使用它是最好的選擇。而在Android2.3版本及以后,HttpURLConnection則是最佳的選擇。它的API簡單,體積較小,因而非常適用于Android項目。壓縮和緩存機制可以有效地減少網(wǎng)絡(luò)訪問的流量,在提升速度和省電方面也起到了較大的作用。對于新的應(yīng)用程序應(yīng)該更加偏向于使用HttpURLConnection,因為在以后的工作當中我們也會將更多的時間放在優(yōu)化HttpURLConnection上面。
以上解釋了在sdk為9以上使用HttpURLConnection的原因。下面還是回到Volley的源碼解析中來,使用HttpStack對象創(chuàng)建好Network之后,再根據(jù)Network和Cache來構(gòu)建RequestQueue對象,調(diào)用其start方法啟動,然后將RequestQueue對象返回,newRequestQueue方法結(jié)束。
接著來看看這個start方法方法內(nèi)部都做了什么。
public void start() {
stop(); // Make sure any currently running dispatchers are stopped.
// Create the cache dispatcher and start it.
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
// Create network dispatchers (and corresponding threads) up to the pool size.
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
首先要知道這個CacheDispatcher和這個NetworkDispatcher都是繼承自Thread,一開始停止所有線程,然后創(chuàng)建一個新的CacheDispatcher線程,啟動,然后創(chuàng)建默認4個NetworkDispatcher,啟動,當start方法執(zhí)行完后,Volley已經(jīng)啟動了5個線程等待網(wǎng)絡(luò)請求任務(wù)來處理了。CacheDispatcher是緩存線程,NetworkDispatcher是網(wǎng)絡(luò)請求線程。
在拿到RequestQueue對象后,我們會把我們的請求add到RequestQueue對象中去,其中add的方法如下:
public <T> Request<T> add(Request<T> request) {
// Tag the request as belonging to this queue and add it to the set of current requests.
request.setRequestQueue(this);
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
// Process requests in the order they are added.
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");
// If the request is uncacheable, skip the cache queue and go straight to the network.
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
// Insert request into stage if there's already a request with the same cache key in flight.
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
if (mWaitingRequests.containsKey(cacheKey)) {
// There is already a request in flight. Queue up.
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 {
// Insert 'null' queue for this cacheKey, indicating there is now a request in
// flight.
mWaitingRequests.put(cacheKey, null);
mCacheQueue.add(request);
}
return request;
}
}
先是給這個請求設(shè)置了一些屬性,然后根據(jù)該請求是否可以緩存,將其加入到不同的請求隊列中,在默認情況下,每條請求都是可以緩存的,當然我們也可以調(diào)用Request的setShouldCache(false)方法來改變這一默認行為。
這里補充一點,請求默認是緩存的,然后通過拿到請求的cacheKey,以鍵值對形式將請求緩存到緩存隊列中去,那么這里請求request的cacheKey是怎么來的?
看Request中這兩個方法:
public String getUrl() {
return mUrl;
}
public String getCacheKey() {
return getUrl();
}
其實cacheKey就是請求的url,因為這個請求肯定是唯一的。
既然默認每條請求都是可以緩存的,自然就被添加到了緩存隊列中,于是一直在后臺等待的緩存線程就開始處理請求了,我們再看看緩存線程CacheDispatcher的run方法:
@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;
}
}
}
可以看到有一個while(true)循環(huán),說明緩存線程始終是在運行的,接著會嘗試從緩存當中取出響應(yīng)結(jié)果,如果為空的話,則把這條請求加入到網(wǎng)絡(luò)請求隊列中,如果不為空的話,再判斷該緩存是否已過期,如果已經(jīng)過期了則同樣把這條請求加入到網(wǎng)絡(luò)請求隊列中,否則就認為不需要重發(fā)網(wǎng)絡(luò)請求,直接使用緩存中的數(shù)據(jù)即可。之后會調(diào)用Request的parseNetworkResponse()方法來對數(shù)據(jù)進行解析,再往后就是將解析出來的數(shù)據(jù)進行回調(diào)了,這部分代碼我們先跳過,因為它的邏輯和NetworkDispatcher后半部分的邏輯是基本相同的,等會再看,先來看一下NetworkDispatcher中是怎么處理網(wǎng)絡(luò)請求隊列的,同樣是NetworkDispatcher的run方法:
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
long startTimeMs = SystemClock.elapsedRealtime();
Request<?> request;
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) {
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);
}
}
}
真正執(zhí)行網(wǎng)絡(luò)請求是調(diào)用Network的performRequest方法,而Network是一個接口,它的實現(xiàn)類是BasicNetwork,performRequest方法的代碼如下:
@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
long requestStart = SystemClock.elapsedRealtime();
while (true) {
HttpResponse httpResponse = null;
byte[] responseContents = null;
Map<String, String> responseHeaders = Collections.emptyMap();
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();
.
.
.
} catch (SocketTimeoutException e) {
attemptRetryOnException("socket", request, new TimeoutError());
}
.
.
.
}
}
我們看到實際上是調(diào)用了HttpStack的performRequest方法,而HttpStack前面已經(jīng)說過,就是HttpURLConnecttion或者HttpClient,請求到數(shù)據(jù)后封裝成NetworkResponse對象返回。在NetworkDispatcher的run方法中發(fā)送請求后返回NetworkResponse對象,接著調(diào)用了Request的parseNetworkResponse方法來解析響應(yīng)數(shù)據(jù)和把響應(yīng)數(shù)據(jù)存入緩存中,不同的Request的子類其parseNetworkResponse方法都不一樣,當我們自定義Request時也必需要重寫parseNetworkResponse方法的。
解析完之后,接著會調(diào)用mDelivery.postResponse方法來回調(diào)解析出來的響應(yīng)數(shù)據(jù),這個mDelivery是一個接口ResponseDelivery,結(jié)果分發(fā)器,它的實現(xiàn)類是ExecutorDelivery,負責分發(fā)響應(yīng)和錯誤數(shù)據(jù),其中的postResponse方法如下:
@Override
public void postResponse(Request<?> request, Response<?> response) {
postResponse(request, response, null);
}
@Override
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()方法就是在主線程當中運行,也就是把解析后的數(shù)據(jù)從子線程中切換到主線程了。
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();
}
}
}
可以看到請求成功后調(diào)用了Request的deliverResponse()方法,這個方法就是我們在自定義Request時需要重寫的另外一個方法,每一條網(wǎng)絡(luò)請求的響應(yīng)都是回調(diào)到這個方法中,最后我們再在這個方法中將響應(yīng)的數(shù)據(jù)回調(diào)到Response.Listener的onResponse()方法中就可以了。
至于這個從主線程切換到子線程再到主線程的過程到底是怎樣的呢?我再來梳理一遍:
當我們創(chuàng)建RequestQueue時,在start方法中,會創(chuàng)建五個子線程,其中一個CacheDispatcher,默認四個NetworkDispatcher,他們的構(gòu)造方法中都有傳遞一個對象,就是這個mDelivery,它是一個ResponseDelivery接口,實現(xiàn)類是ExecutorDelivery,我們看看RequestQueue的這兩個構(gòu)造方法就清除了:
public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize, new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
所以RequestQueue是自己構(gòu)建了這個ExecutorDelivery對象,然后再傳遞到后面的子線程后的,ExecutorDelivery的構(gòu)造方法參數(shù)就是一個主線程的handler對象,所以這個mDelivery內(nèi)部是持有一個主線程的消息系統(tǒng)的。然后在請求完成,解析完響應(yīng)數(shù)據(jù)后,調(diào)用了mDelivery的postResponse方法,在postResponse方法最后調(diào)用了一個Executor對象的execute方法方法,參數(shù)就是一個Runnable,并且將解析后的響應(yīng)數(shù)據(jù)傳給Runnable。我們再看看ExecutorDelivery的構(gòu)造方法:
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);
}
};
}
由于這個消息系統(tǒng)是在主線程構(gòu)造,也就是說Runnable會被發(fā)送到主線程去執(zhí)行,這樣就把解析后的數(shù)據(jù)傳遞到主線程了。
現(xiàn)在再回過頭看源碼解析開頭的那張流程圖就比較清晰了,我們在主線程中調(diào)用RequestQueue的add()方法來添加一條網(wǎng)絡(luò)請求,這條請求會先被加入到緩存隊列當中,如果發(fā)現(xiàn)可以找到相應(yīng)的緩存結(jié)果就直接讀取緩存并解析,然后回調(diào)給主線程。如果在緩存中沒有找到結(jié)果,則將這條請求加入到網(wǎng)絡(luò)請求隊列中,然后處理發(fā)送HTTP請求,解析響應(yīng)結(jié)果,寫入緩存,并回調(diào)主線程。
Volley的源碼解析就結(jié)束了,其實源碼解析沒有必要每個類每個方法都去看一遍,我們只需要將這個框架的工作流程相關(guān)的源碼即可,主要是掌握框架的優(yōu)秀的設(shè)計思路以及一些良好的編碼實現(xiàn)。好了,就到這里。
參考資料
手撕 Volley(一)
手撕 Volley(二)
手撕 Volley(三)
Android Volley完全解析(一),初識Volley的基本用法
Android Volley完全解析(二),使用Volley加載網(wǎng)絡(luò)圖片
Android Volley完全解析(三),定制自己的Request
Android Volley完全解析(四),帶你從源碼的角度理解Volley
HTTP協(xié)議詳解
HTTP協(xié)議詳解
HttpClient和HttpURLConnection的區(qū)別