1、概述
VolleySupport是基於Google的Volley框架,在其基礎上簡單封裝完成的。添加的代碼主要位於manager目錄下。Volley的源碼已經加入到Android Source中,路徑爲frameworks/volley
2 Volley源碼分析
2.1 功能說明
volley提供功能如下:
JSON,圖像等的異步下載;
網絡請求的排序(scheduling)
網絡請求的優先級處理
緩存
多級別取消請求
和Activity和生命週期的聯動(Activity結束時同時取消所有網絡請求)
2.2 Volley整體設計思路
首先添加實現Request抽象類的任務到RequestQueue中
然後通過Dispatch不斷從RequestQueue中取出請求:
優先判斷CacheDispatcher中是否有緩存數據
Cache中沒有則通過NatworkDispatcher,從網絡獲取數據並緩存。
最後通過ResponseDelivery分發數據,做回調處理。
如下圖所以:
2.3 主要類分析
Volley:Volley 工具類,對外暴露的 API,通過 newRequestQueue(…) 函數新建並啓動一個請求隊列RequestQueue。這裏進行了版本號判斷,大於等於9就使得HttpStack對象的實例爲HurlStack,小於9則實例爲HttpClientStack。
Request:表示一個請求的抽象類。StringRequest、JsonRequest、ImageRequest等都是它的子類,表示某種類型的請求。
RequestQueue:表示請求隊列,裏面包含一個CacheDispatcher(用於處理走緩存請求的調度線程)、NetworkDispatcher數組(用於處理走網絡請求的調度線程),一個ResponseDelivery(返回結果分發接口),通過 start() 函數啓動時會啓動CacheDispatcher和NetworkDispatchers。
mCacheQueue 緩存請求隊列
mNetworkQueue 網絡請求隊列
mCurrentRequests 正在進行中(正在請求中或者正在分發中),尚未完成的請求集合
mWaitingRequests 等待請求的集合,如果一個請求正在被處理並且可以被緩存,後續的相同 url 的請求,將進入此等待隊列CacheDispatcher:一個線程,用於調度處理緩存請求。啓動後會不斷從緩存請求隊列mCacheQueue中取請求處理:
對於已經取消的請求,標記爲跳過並結束這個請求;
新的或者過期的請求,直接放入mNetworkQueue中由N個NetworkDispatcher進行處理;
已獲得緩存信息(網絡應答)卻沒有過期的請求,由Request的parseNetworkResponse進行解析,從而確定此應答是否成功。如果需要更新緩存那麼該請求還會被放入mNetworkQueue中由N個NetworkDispatcher進行處理。
最後將請求和應答交由Delivery(ExecutorDelivery)分發者進行處理,
NetworkDispatcher:一個線程,用於調度處理走網絡的請求。啓動後會不斷從網絡請求隊列中取請求處理,隊列爲空則等待,請求處理結束則將結果傳遞給ResponseDelivery去執行後續處理,並判斷結果是否要進行緩存。Volley中是使用了一個NetworkDispatcher線程數組去處理網絡請求的。
ResponseDelivery:返回結果分發接口,最終調用Listener和ErrorListener方法通知UI線程。如果等待列表mWaitingRequests中存在相同URL的請求,則會將剩餘的層級請求全部丟入mCacheQueue交由CacheDispatcher進行處理。目前實現類只有基於ExecutorDelivery的在入參 handler 對應線程內進行分發。
HttpStack:處理 Http 請求,返回請求結果。目前 Volley 中有基於 HttpURLConnection 的HurlStack和 基於 Apache HttpClient 的HttpClientStack。
Network:調用HttpStack處理請求,並將結果轉換爲可被ResponseDelivery處理的NetworkResponse。其實現類爲BasicNetwork。
Cache:緩存請求結果,Volley 默認使用的是基於 sdcard 的DiskBasedCache。volley還提供了一個NoCache,也就是無緩存。
NetworkDispatcher得到請求結果後判斷是否需要存儲在 Cache。
CacheDispatcher會從 Cache 中取緩存結果。
類圖如下:
2.4 添加請求
添加請求過程add分析如下
2.5 緩存處理
volley處理請求流程:
2.6 網絡請求
2.7 Response轉換
2.8 數據分發處理
3對volley的封裝
主要位於manager
包中。
3.1 RequestManager
簡單封裝了初始化和獲取RequesQueue的方法。
public static void init(Context context, String userAgent) {
RequestManager.init(context, userAgent, 4);
}
public static void init(Context context, String userAgent, int threadPool) {
mRequestQueue = Volley.newRequestQueue(context, userAgent, threadPool);
}
3.2 Multipart請求支持
流格式上傳文件支持包含三個文件
1、MultipartRequestParams爲參數列表,支持兩種類型參數
普通鍵值對
文件或文件流
MultipartRequestParams內部其實是一個FileWrapper,用來封裝需要上傳的流(文件最終也會被轉換成流)。
private static class FileWrappers {
public InputStream[] inputStreams;
public String[] fileNames;
public String contentType;
public FileWrappers(InputStream[] inputStreams, String[] fileNames,
String contentType) {
this.inputStreams = inputStreams;
this.fileNames = fileNames;
this.contentType = contentType;
}
public String getFileName(int i) {
if (fileNames != null) {
return fileNames[i];
} else {
return "nofilename";
}
}
}
MultipartRequestParams提供了兩類put方法,一種是類似map的可以存儲鍵值對,這裏略過;另一種可以put進來流或者文件,如下。
public void put(String key, File[] files) {
try {
FileInputStream[] inputStreams = new FileInputStream[files.length];
String[] fileNames = new String[files.length];
for (int i = 0; i < files.length; i++) {
if (files[i] != null) {
inputStreams[i] = new FileInputStream(files[i]);
fileNames[i] = files[i].getName();
} else {
inputStreams[i] = null;
fileNames[i] = null;
}
}
put(key, inputStreams, fileNames);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public void put(String key, InputStream[] streams, String[] fileNames) {
put(key, streams, fileNames, null);
}
public void put(String key, InputStream[] streams, String fileNames[], String contentType) {
if (key != null && streams != null) {
fileParams.put(key, new FileWrappers(streams, fileNames, contentType));
}
}
最終文件保存在了fileParams中。
MultipartRequestParams還提供了一個方法getEntity,用來將前面添加進來的參數和流封裝成HttpEntity。
public HttpEntity getEntity() {
HttpEntity entity = null;
MultipartEntity multipartEntity = new MultipartEntity();
// Add string params
for (ConcurrentHashMap.Entry<String, String> entry : urlParams.entrySet()) {
multipartEntity.addPart(entry.getKey(), entry.getValue());
}
if (!fileParams.isEmpty()) {
// Add file params
int currentIndex = 0;
int lastIndex = fileParams.entrySet().size() - 1;
for (ConcurrentHashMap.Entry<String, FileWrappers> entry : fileParams.entrySet()) {
FileWrappers file = entry.getValue();
if (file.inputStreams != null) {
boolean last = currentIndex == lastIndex;
for (int i = 0; i < file.inputStreams.length; i++) {
boolean isLast;
if (i == file.inputStreams.length - 1) {
isLast = last & true;
} else {
isLast = last & false;
}
if (file.contentType != null) {
multipartEntity.addPart(entry.getKey(), file.getFileName(i), file.inputStreams[i], file.contentType, isLast);
} else {
multipartEntity.addPart(entry.getKey(), file.getFileName(i), file.inputStreams[i], isLast);
}
}
}
currentIndex++;
}
}
entity = multipartEntity;
return entity;
}
2.這裏新建了一個MultipartEntity,提供了addPart方法添加流以及普通鍵值對,最終通過out寫入流中。
public void addPart(final String key, final String fileName, final InputStream fin, String type, final boolean isLast) {
writeFirstBoundaryIfNeeds();
try {
type = "Content-Type: " + type + "\r\n";
out.write(("Content-Disposition: form-data; name=\"" + key + "\"; filename=\"" + fileName + "\"\r\n").getBytes());
out.write(type.getBytes());
out.write("Content-Transfer-Encoding: binary\r\n\r\n".getBytes());
final byte[] tmp = new byte[4096];
int l = 0;
if (fin != null) {
while ((l = fin.read(tmp)) != -1) {
out.write(tmp, 0, l);
}
}
if (!isLast) {
out.write(("\r\n--" + boundary + "\r\n").getBytes());
} else {
writeLastBoundaryIfNeeds();
}
out.flush();
} catch (final IOException e) {
e.printStackTrace();
} finally {
try {
if (fin != null) {
fin.close();
}
} catch (final IOException e) {
e.printStackTrace();
}
}
}
3.最終實現了一個MultipartRequest,這裏繼承的是StringRequest,因爲差別不大。重寫了getBody()
方法,把MultipartEntity中的Entry讀取出來,通過getBody返回一個字節數組給Http。(默認的getbody是獲取Post的參數數據返回字節數組)
public byte[] getBody() throws AuthFailureError {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
if(params != null) {
httpEntity = params.getEntity();
try {
httpEntity.writeTo(baos);
} catch (IOException e) {
e.printStackTrace();
}
}
return baos.toByteArray();
}
4 取消請求
RequestQueue中添加了一個取消請求的方法,可以在不需要這個網絡請求的時候通過tag取消它。
public void cancelAll(final Object tag) {
if (tag == null) {
throw new IllegalArgumentException("Cannot cancelAll with a null tag");
}
cancelAll(new RequestFilter() {
@Override
public boolean apply(Request<?> request) {
return request.getTag() == tag;
}
});
}
5 重試機制
volley提供了重試機制接口RetryPolicy
,且提供了一個默認的實現類DefaultRetryPolicy
public class DefaultRetryPolicy implements RetryPolicy {
private int mCurrentTimeoutMs; //當前超時時間毫秒值
private int mCurrentRetryCount; //當前重試次數
private final int mMaxNumRetries; //最大重試次數
private final float mBackoffMultiplier;//超時重試延時
public static final int DEFAULT_TIMEOUT_MS = 15*1000;
public static final int DEFAULT_MAX_RETRIES = 2;
public static final float DEFAULT_BACKOFF_MULT = 1f;
public DefaultRetryPolicy() {//使用默認的構造方法參數實例化
this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT);
}
public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) {
mCurrentTimeoutMs = initialTimeoutMs;
mMaxNumRetries = maxNumRetries;
mBackoffMultiplier = backoffMultiplier;
}
@Override
public void retry(VolleyError error) throws VolleyError {
mCurrentRetryCount++;
//超時次數*重試延時 結果爲下次重試網絡請求時間,支持定製曲線增加時間。
mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
if (!hasAttemptRemaining()) {
throw error;
}
}
protected boolean hasAttemptRemaining() {
return mCurrentRetryCount <= mMaxNumRetries;
}
}
6 地址
maven地址 com.android.common:VolleySupport:1.0.1
源碼地址 maven
目錄下