VolleySupport使用說明

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目錄下

發佈了46 篇原創文章 · 獲贊 66 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章