手擼一個簡單的網路框架
開始前
網路訪問框架關心的問題:
- 能併發接受多個請求,並返回"使用者"需要的資料
- 重試機制
實現方式:
- 佇列
- 執行緒池
網路框架實現步驟
- 建立執行緒池管理類(佇列,執行緒池)
- 封裝請求引數
- 封裝響應資料
- 封裝請求任務
- 封裝"使用工具"
- 新增重試機制
建立執行緒池管理類
建立 ThreadPoolManager.java 類,負責管理請求佇列和執行緒池
//1. 建立佇列,用來儲存非同步請求任務 private LinkedBlockingQueue<Runnable> mQueue = new LinkedBlockingQueue<>();//LinkedBlockingQueue FIFO //2. 新增非同步任務到佇列中 public void addTask(Runnable runnable) { try { if (runnable != null) { mQueue.put(runnable); } } catch (InterruptedException e) { e.printStackTrace(); } } //3. 建立執行緒池 private ThreadPoolExecutor mThreadPoolExecutor; //4. 建立佇列與執行緒池的"互動"執行緒 public Runnable communicateThread = new Runnable() { @Override public void run() { Runnable runnable = null; while (true) { try { runnable = mQueue.take(); } catch (InterruptedException e) { e.printStackTrace(); } //執行執行緒池中的執行緒任務 mThreadPoolExecutor.execute(runnable); } } };
[注] communicateThread 執行緒負責從 mQueue 佇列中獲取請求任務,並放到 mThreadPoolExecutor 執行緒池中執行.
構造單例的 ThreadPoolManager,構造方法中初始化執行緒池並執行 communicateThread 執行緒
private ThreadPoolManager() { mThreadPoolExecutor = new ThreadPoolExecutor( 3, 10, 15, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(4), new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { //處理被丟擲來的任務(被拒絕的任務) addTask(r); } }); mThreadPoolExecutor.execute(communicateThread); }
[注] 執行緒池的設定依據具體專案而定.
RejectedExecutionHandler回撥, 任務拒絕後,重新新增到佇列之中.
封裝請求引數
定義介面 IHttpRequest.java 實現必要的引數
public interface IHttpRequest { /** * 協議地址 * @param url */ void setUrl(String url); /** * 設定請求引數 */ void setData(byte[] bytes); /** * 資料資料回撥 * @param callbackListener */ void setListener(CallbackListener callbackListener); /** * 執行請求 */ void execute(); }
execute 方法負責具體的任務執行.
例如我們的請求型別為JSON, 我們可以實現一個JSON的請求
public class JsonHttpRequest implements IHttpRequest { // 省略其他實現方法 @Override public void execute() { URL url = null; HttpURLConnection urlConnection = null; try { url = new URL(this.url); //省略HttpURLConnection請求引數 if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {//得到伺服器返回碼是否連線成功 InputStream in = urlConnection.getInputStream(); mCallbackListener.onSuccess(in); } else { throw new RuntimeException("請求失敗"); } } catch (Exception e) { throw new RuntimeException("請求失敗"); } finally { if (urlConnection != null) { urlConnection.disconnect(); } } } }
封裝響應資料
從上面可以看到有一個 CallbackListener 介面, 負責資料的成功和失敗回撥
public interface CallbackListener { /** * 成功回撥 * @param inputStream */ void onSuccess(InputStream inputStream); /** * 失敗 */ void onFailed(); }
特別的,如果我們請求的是JSON格式的資料, 我們可以自己實現一個Callback, JsonCallbackListener 用於資料的獲取和解析
public class JsonCallbackListener<T> implements CallbackListener { private Class<T> resposeClass; private IJsonDataListener jsonDataListener; Handler handler = new Handler(Looper.getMainLooper()); public JsonCallbackListener(Class<T> responseClass, IJsonDataListener listener) { this.resposeClass = responseClass; this.jsonDataListener = listener; } @Override public void onSuccess(InputStream inputStream) { String response = getContent(inputStream); Log.d(TAG, "onSuccess: response: " + response); final T clazz = new Gson().fromJson(response, resposeClass); handler.post(new Runnable() { @Override public void run() { jsonDataListener.onSuccess(clazz); } }); } private String getContent(InputStream inputStream) { String content = ""; //省略解析過程 return content; } @Override public void onFailed() { } }
封裝請求任務
新增一個 HttpTask 繼承自 Runnable, 作為請求任務
public class HttpTask<T> implements Runnable { private IHttpRequest mHttpRequest; public HttpTask(T requestData, String url, IHttpRequest httpRequest, CallbackListener callbackListener) { mHttpRequest = httpRequest; httpRequest.setUrl(url); httpRequest.setListener(callbackListener); Log.d(TAG, "HttpTask: url: " + url); String content = new Gson().toJson(requestData); try { httpRequest.setData(content.getBytes("utf-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } /////////////////////////////////////////////////////////////////////////// // implements Runnable /////////////////////////////////////////////////////////////////////////// @Override public void run() { try { mHttpRequest.execute(); } catch (Exception e) { //.... } } }
在構造方法中獲取請求引數, run 方法中執行 IHttpRequest 中的 execute 獲取網路資料
封裝使用工具
為方便使用方使用,有必要封裝成工具類
新增 LuOkHttp.java 作為請求工具類
public class LuOkHttp { /** * 傳送網路請求 */ public static<T, M> void sendJsonRequest(T request, String url, Class<M> response, IJsonDataListener listener) { IHttpRequest httpRequest = new JsonHttpRequest(); JsonCallbackListener<M> mJsonCallbackListener = new JsonCallbackListener<>(response, listener); HttpTask<T> httpTask = new HttpTask<>(request, url, httpRequest, mJsonCallbackListener); ThreadPoolManager.getInstance().addTask(httpTask); } }
至此,基本的請求已經實現, 可以執行試一下了.
新增重試機制
網路訪問在很多情況下會失敗,例如通過隧道,坐電梯等,所以有必要在框架層實現重試機制.
首先,需要在我們的執行緒池管理類 ThreadPoolManager 中新增延時佇列
// 建立延時佇列 private DelayQueue<HttpTask> mDelayQueue = new DelayQueue<>(); //新增到延時佇列 public void addDelayTask(HttpTask httpTask) { if (httpTask != null) { httpTask.setDelayTime(3000); mDelayQueue.offer(httpTask); Log.d(TAG, "addDelayTask: "); } }
同樣的, 也需要一個執行緒來負責將延時佇列中的任務放到執行緒池中.
public Runnable delayThread = new Runnable() { @Override public void run() { HttpTask ht = null; while (true) { try { ht = mDelayQueue.take(); } catch (InterruptedException e) { e.printStackTrace(); } if (ht != null && ht.getRetryCount() < 3) { mThreadPoolExecutor.execute(ht); ht.setRetryCount(ht.getRetryCount() + 1); Log.d(TAG, "run: 重試機制: " + ht.getRetryCount()); } else { Log.d(TAG, "run: 重試機制:超出次數 "); } } } };
另外,不要忘記在 ThreadPoolManager 的構造方法中執行這個執行緒.
private ThreadPoolManager() { //... mThreadPoolExecutor.execute(delayThread); }
現在, 你可以斷網測試一下我們的重試機制是否生效.