上週答應你們的,手擼一個網絡請求框架。
學了快兩個月的 java 基礎,如今咱們來手擼一個網絡請求框架練練手。
手寫一個網絡請求框架須要掌握的知識點比較多,其中牽涉到設計模式、集合、泛型、多線程及併發、網絡編程等知識,算是對 java 基本功比較全面的考查,同時,對架構能力也有必定的要求。java
先來看看需求~~git
首先咱們來回顧一下一次網絡請求的流程。github
1.準備請求
2.根據請求的參數發起網絡請求
3.請求結果處理及回調算法
而後咱們調用的代碼大概長這個樣子~數據庫
Volley.sendRequest(null, url, GankResponse.class, GET, new IDataListener<GankResponse>() {
@Override
public void onSuccess(GankResponse response) {}
@Override
public void onFail(int errorCode, String errorMsg) {}
});複製代碼
哈哈,我憑空想象的,其實我並無用過 Volley。編程
嗯,咱們最終的目的也是把代碼寫成這樣子。json
剛剛咱們說了一次網絡請求大概是三個步驟,接下來,咱們就根據這三個步驟來設計網絡架構。敲黑板,記住了這三個階段。設計模式
1.準備參數api
這個階段通常是從 Activity 界面發起的,說白一點好像就是我客戶端頁面不知道顯示什麼樣的數據,去找服務器要。一次網絡請求實際上就是和服務區的一次交互,在交互過程當中,根據 HTTP 協議(以前講過,弄不明白的回去翻翻個人文章),咱們須要準備如下幾個參數數組
其中前三個參數是用來作網絡請求的,後兩個參數是用來作請求響應的。
所以,咱們這裏須要一個一個 bean (RequestHolder ②)來封裝請求參數RequestHolder須要以下屬性
而後咱們須要一個類(IHttpService ③)去發起這個網絡請求,這個類最少須要如下幾個屬性
IHttpListener④(持有請求結果處理類實例)
至於上面說的這個IHttpListener,屬於階段三,咱們來看看IHttpListener。
咱們先跳過階段二。
上面咱們說了,IHttpService持有IHttpListener,而且在請求結束以後要處理並回調。
請求結果無非就是兩種,成功和不成功。
因此這個類只須要有兩種行爲(方法)和一個屬性
解析數據
onSuccess
IHttpService 在網絡請求成功以後調用這個方法,IHttpListener 須要解析數據(好比說 json 傳、InputStream 轉換成 image、File 等)而後調用 IDataListener 返回給 activity
onFail
IHttpService 網絡請求失敗調用這個方法,IDataListener 直接調用 IDataListener 將錯誤信息返回給 activity
解析數據
這一步是發生在請求成功以後獲得源數據,而後根據源數據的不一樣類型作不一樣的解析,最後調用IHttpListener 返回給 activity。
根據咱們剛纔的設計,網絡請求的真正發起是IHttpService 的 excuse 方法。而後網絡請求須要在子線程,咱們能夠把一次網絡請求當作是一個異步任務,因此咱們須要一個類去實現 Runnable 接口,而後在 run 方法裏去調用 IHttpService 的excuse 執行網絡請求。暫且咱們把這個實現了 Runnable 接口的類叫 HttpTask⑤,這個類比較簡單,只須要持有 IHttpService 的實例便可。
而後針對一個異步任務 HttpTask,咱們要作一個併發管理,因此須要引入線程池來管理全部的 HttpTask。因此咱們須要一個單例的 ThreadPoolManger⑥ 來管理全部的 HttpTask,ThreadPoolManger 裏面咱們能夠直接用 Executors 建立一個線程池,至於線程池的細節,限制最大併發、阻塞、生產消費者模型是怎麼實現的,能夠回過頭去java 技術線程相關的內容。
ThreadPoolManger
好,到這裏,咱們的網絡請求須要的東西差很少就準備好了,如今咱們能夠在 Activity 裏面建立RequestHolder、IHttpService、IHttpListener、HttpTask等類發起網絡請求了,可是好像要建立的東西比較多,並且不少東西不是調用者須要關心的東西,容易出錯。對於客戶端來講,我只須要根據服務器的要求,準備請求參數以及相應參數的類型去接收服務器的響應便可,而不須要關心請求的過程。
因此咱們在這裏建立一個 Volley⑦類,去封裝請求、響應相關的各類類,而後將一個 HttpTask 丟到線程池裏面去。這個類很簡單,只須要一個靜態的 sendRequest 方法便可,方法參數須要傳入 url、requestParams等。
邏輯有點亂,我來捋一下邏輯。首先是從 Activity 開始,有一個和服務器交互的需求。步驟以下:
1.調用 Volley 的靜態方法sendRequest,傳入方法須要的 url、RequestParams 等各類參數。
2.Volley 的sendRequest 方法根據方法參數,構建RequestHolder、IHttpService、IHttpListener、HttpTask等類,並將HttpTask 丟進ThreadPoolManger的線程池裏面等待執行。
3.ThreadPoolManger的線程池裏面其實是一個阻塞隊列,會根據任務取出HttpTask這個任務並執行。
4.HttpTask的 run 方法被調用,run 方法調用IHttpService 的 excuse 方法,正式發起網絡請求。
5.網絡請求結束,IHttpService 調用IHttpListener 的 成功/失敗 的方法進行相應的處理。
6.網絡請求成功,IHttpListener 解析 源數據,並將解析的數據經過 IDataListener回調給 activity。
7.網絡請求失敗,IHttpListener 直接調用IDataListener的 fail 方法告知 activity 請求失敗。
哦,對了,線程切換忘記講,你們思考一下,應該在哪一個類切回主線程?答案我會在代碼中體現。
架構設計大概就是這樣子,爲了便於你們理解,我畫了一個類關係圖,你們湊合着看看。
以前給你們吹了牛逼,說會實現哪些需求,用到哪些知識點,用到哪些設計模式,在擼代碼以前,我先給你們講解一下。
支持請求 JSON 文本類型,音頻,圖片類型,批量下載、上傳~
支持請求 JSON 類、圖片、音頻等資源的下載,這個作了擴展預留。咱們只須要根據請求的數據類型,作不一樣的實現便可,不一樣的實現都須要繼承 IHttpService 接口。
請求各類數據時,調用層不用關心上傳參數的封裝
直接調用public static IHttpService sendRequest(requestParams,url,responseClass,type,listener)方法便可。
獲取數據後,調用層不用關心 JSON 數據的解析
數據的解析同IHttpService,一樣只須要根據不一樣的數據類型,作不一樣的 IHttpListener 實現便可。
回調時,調用層只須要知道傳入的 JSON 的對應響應類
使用了泛型,會在IHttpListener 的 Json 數據實現類裏面將 json 數據轉換成響應類的 bean。
回調響應結果發生在主線程(線程切換)
已實現,在IHttpListener 的實現類裏面。
對下載,上傳擴展
預留了接口,一樣只須要對IHttpService 和 IHttpListener接口進行擴展,下一篇文章會講解實現。
支持高併發請求,請求隊列一次獲取,能夠設置最大併發數,設置先請求先執行
經過線程池實現。
請求隊列
線程池裏面作了封裝,這裏直接調用concurrent 包裏面的工具類 Executors建立了線程池,裏面用到了 LinkedBlockingQueue 。
阻塞隊列
LinkedBlockingQueue 就是阻塞隊列,具體參考 concurrent 工具包的講解。
線程拒絕策略
待實現
模板方法模式
模板方法:定義一個操做中算法的框架,而將一些步驟延遲到子類中。模板方法模式使得子類能夠不改變一個算法的結構便可重定義該算法的某些特定步驟。
個人HttpTask 裏面的網絡請求痛的都是 IHttpService 類,根據請求的不一樣數據,使用不一樣的子類,同理還有 IHttpListener 。
單例模式
ThreadPoolManger 是單例
策略模式
根據不一樣的請求數據,選擇使用IHttpService JSonHTTPService 或者FileDownHttpService(這個類在文件下載模塊中)
生產者消費者模式
線程池自己就是一個生產者消費者模型,Activity 發生一個請求,Volley 將請求生產成一個 HttpTask 丟進線程池,而後請求結束就至關於消費了這個請求。
架構設計出來了,類關係圖也設計好了,接下來咱們就開始擼代碼了。
從哪裏開始擼呢? 你能夠選擇按照邏輯順序,從 activity 裏面調用 Volley 開始擼,差什麼類就擼什麼類,遇神殺神遇佛殺佛。
然而我不推薦這種方法,這種方法容易出錯,效率低。
首先,咱們先來看看,上面分析的時候,我標記的幾個重點類對象
①IDataListener
②RequestHolder
③IHttpService
④IHttpListener
⑤HttpTask
⑥ThreadManger
⑦Volley
接下來,咱們就來按照這個順序擼代碼吧。
這個類很簡單,只須要作一個請求結果回調。
public interface IDataListener<T> {
/**
* @param t 響應參數
*/
void onSuccess(T t);
void onFail(int errorCode, String errorMsg);
}複製代碼
其中 T 是泛型操做,由於咱們也不知道返回結果會是什麼類型的數據結構。
如今直接擼這個類,咱們會遇到問題,由於RequestHolder會持有 IHttpService 和 IHttpListener 的引用,因此咱們應該先把兩個接口擼出來。
這個類很簡單,只須要持有IHttpService、IHttpListener、URL、requestParams、RequestHolder 等參數就好了,沒有任何邏輯操做。
注意:這裏只須要持有IHttpService、和IHttpListener的接口引用,不要去持有這兩個接口的實例引用。
因爲 IHttpService 被 RequestHolder 持有且是真正的網絡請求相關的類構造類以及執行類,因此咱們須要定義設置一下接口方法。
public interface IHttpService {
/**
* 設置 url
*
* @param url url address
*/
void setUrl(String url);
/**
* 設置處理接口
*
* @param listener 處理接口
*/
void setHttpListener(IHttpListener listener);
/**
* 設置請求參數
*
* @param data 請求參數 byte 數組
*/
void setRequestData(byte[] data);
/**
* 執行請求
*/
void excute();
void cancel();
boolean isCancel();
void setRequestHeader(Map<String, String> map);
void setRequestType(String type);
}複製代碼
定義了以上接口方法,有些參數真的是懶得寫註釋了,大家應該都看得懂。
而後在 IHttpService 的實現類裏面須要作具體的 Http 請求,這裏我偷個懶,直接用了 HttpClient。具體實現類 JsonHttpService你們能夠去下載個人源碼閱讀。
這個類是交給 IHttpService 類在網絡請求結束以後負責解析數據的,因爲網絡請求的結果只會有兩種--成功和失敗,因此這裏就不定義解析數據的方法了,由於它是在網絡請求成功以後調用的。
public interface IHttpListener {
/**
* 網絡請求成功回調
*
* @param httpEntity 網絡請求返回結果
*/
void onSuccess(HttpEntity httpEntity);
void onFail(int errorCode, String errorMsg);
}複製代碼
而後 IHttpListener 持有 IDataListener 實例,最後調用 IDataListener 來回調給 Activity。
對了,這裏須要作線程切換哦,具體實現代碼也很簡單,請下載源碼自行閱讀。
整套網絡請求寫完了,咱們須要一個類來調用 IHttpService 的 excuse 的方法去執行網絡請求,且excuse方法必須在子線程。咱們把一次網絡請求封裝成一個異步任務,即一個 Runnable,代碼很簡單。
public class HttpTask<T> implements Runnable {
private IHttpService mHttpService;
public HttpTask(RequestHolder<T> holder) throws UnsupportedEncodingException {
mHttpService = holder.getHttpService();
mHttpService.setHttpListener(holder.getHttpListener());
mHttpService.setRequestType(holder.getRequestType());
mHttpService.setUrl(holder.getUrl());
mHttpService.setRequestHeader(holder.getRequestHeader());
T requestParams = holder.getRequestParams();
if (requestParams != null) {
String requestInfo = JSON.toJSONString(requestParams);
mHttpService.setRequestData(requestInfo.getBytes("UTF-8"));
}
}
@Override
public void run() {
if (!mHttpService.isCancel()) {
mHttpService.excute();
}
}
}複製代碼
最後咱們須要一個線程池管理HttpTask。因此咱們須要一個ThreadPoolManger,由於這個類必須保證惟一,因此單例。線程池管理咱們直接用 concurrent 包封裝好的Executors 類建立,這個類代碼很簡單,以下:
public class ThreadPoolManger {
private static volatile ThreadPoolManger mInstance;
private final ExecutorService mExecutorService;
private ThreadPoolManger() {
mExecutorService = Executors.newFixedThreadPool(4);
}
public static ThreadPoolManger getInstance() {
if (mInstance == null) {
synchronized (ThreadPoolManger.class) {
mInstance = new ThreadPoolManger();
}
}
return mInstance;
}
public void execute(HttpTask task) {
mExecutorService.execute(task);
}
}複製代碼
最後建立IHttpService、RequestHolder、IHttpListener、HttpTask 等類,並將HttpTask 丟進線程池。這一系列操做對於調用者來講是不須要關心的,能夠隱藏,因此須要一個 Volley 類去隱藏這個過程。代碼也很簡單:
public class Volley {
static HashMap<String, String> mGlobalHeader = new HashMap<>();
public static <T, M> IHttpService sendRequest(T requestParams, String url, Class<M> responseClass, @RequestType String type,IDataListener<M> listener) {
IHttpService jsonHttpService = new JsonHttpService();
IHttpListener jsonDealListener = new JsonDealListener<>(listener, responseClass);
RequestHolder<T> requestHolder = new RequestHolder<>(requestParams, jsonHttpService, jsonDealListener, url,type);
try {
HttpTask<T> task = new HttpTask<>(requestHolder);
ThreadPoolManger.getInstance().execute(task);
} catch (UnsupportedEncodingException e) {
listener.onFail(0, e.getMessage());
}
return jsonHttpService;
}
public static void setGlobalHeader(String key, String value) {
mGlobalHeader.put(key, value);
}
}複製代碼
至此,除了IHttpListener 和 IHttpService 的實現類沒有貼上來,整個網絡請求架構的雛形已經完成了。
咱們來測試一下~
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void test(View view) {
String url = "http://gank.io/api/data/Android/10/1";
for (int i = 0; i < 50; i++) {
Volley.sendRequest(null, url, GankResponse.class, GET, new IDataListener<GankResponse>() {
@Override
public void onSuccess(GankResponse response) {
Log.e("___", "請求成功");
}
@Override
public void onFail(int errorCode, String errorMsg) {
Log.e("___error", errorMsg + "__errorCode:" + errorCode);
}
});
}
}
}複製代碼
以上是在 MainActivity 裏面點擊了一個按鈕,作了一個50次網絡請求的併發操做,親測成功。
下期咱們將基於這個架構,繼續作下載的擴展。
其中會涉及到多線程下載,斷點續傳等各類操做,若是時間來的及的話,還會結合數據庫作一些神奇的操做。
哦,對了,貼上
若是個人文章能給你帶來幫助,請記得點個 star,麼麼噠~