開始前
網絡訪問框架關心的問題:
- 能并發(fā)接受多個請求,并返回"用戶"需要的數(shù)據
- 重試機制
實現(xiàn)方式:
- 隊列
- 線程池
網絡框架實現(xiàn)步驟
- 創(chuàng)建線程池管理類(隊列,線程池)
- 封裝請求參數(shù)
- 封裝響應數(shù)據
- 封裝請求任務
- 封裝"使用工具"
- 添加重試機制
創(chuàng)建線程池管理類
創(chuàng)建 ThreadPoolManager.java 類,負責管理請求隊列和線程池
//1. 創(chuàng)建隊列,用來保存異步請求任務
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. 創(chuàng)建線程池
private ThreadPoolExecutor mThreadPoolExecutor;
//4. 創(chuàng)建隊列與線程池的"交互"線程
public Runnable communicateThread = new Runnable() {
@Override
public void run() {
Runnable runnable = null;
while (true) {
try {
runnable = mQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
//執(zhí)行線程池中的線程任務
mThreadPoolExecutor.execute(runnable);
}
}
};
[注] communicateThread 線程負責從 mQueue 隊列中獲取請求任務,并放到 mThreadPoolExecutor 線程池中執(zhí)行.
構造單例的 ThreadPoolManager,構造方法中初始化線程池并執(zhí)行 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回調, 任務拒絕后,重新添加到隊列之中.
封裝請求參數(shù)
定義接口 IHttpRequest.java 實現(xiàn)必要的參數(shù)
public interface IHttpRequest {
/**
* 協(xié)議地址
* @param url
*/
void setUrl(String url);
/**
* 設置請求參數(shù)
*/
void setData(byte[] bytes);
/**
* 數(shù)據數(shù)據回調
* @param callbackListener
*/
void setListener(CallbackListener callbackListener);
/**
* 執(zhí)行請求
*/
void execute();
}
execute 方法負責具體的任務執(zhí)行.
例如我們的請求類型為JSON, 我們可以實現(xiàn)一個JSON的請求
public class JsonHttpRequest implements IHttpRequest {
// 省略其他實現(xiàn)方法
@Override
public void execute() {
URL url = null;
HttpURLConnection urlConnection = null;
try {
url = new URL(this.url);
//省略HttpURLConnection請求參數(shù)
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();
}
}
}
}
封裝響應數(shù)據
從上面可以看到有一個 CallbackListener 接口, 負責數(shù)據的成功和失敗回調
public interface CallbackListener {
/**
* 成功回調
* @param inputStream
*/
void onSuccess(InputStream inputStream);
/**
* 失敗
*/
void onFailed();
}
特別的,如果我們請求的是JSON格式的數(shù)據, 我們可以自己實現(xiàn)一個Callback, JsonCallbackListener 用于數(shù)據的獲取和解析
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) {
//....
}
}
}
在構造方法中獲取請求參數(shù), run 方法中執(zhí)行 IHttpRequest 中的 execute 獲取網絡數(shù)據
封裝使用工具
為方便使用方使用,有必要封裝成工具類
添加 LuOkHttp.java 作為請求工具類
public class LuOkHttp {
/**
* 發(fā)送網絡請求
*/
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);
}
}
至此,基本的請求已經實現(xiàn), 可以運行試一下了.
添加重試機制
網絡訪問在很多情況下會失敗,例如通過隧道,坐電梯等,所以有必要在框架層實現(xiàn)重試機制.
首先,需要在我們的線程池管理類 ThreadPoolManager 中添加延時隊列
// 創(chuàng)建延時隊列
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: 重試機制:超出次數(shù) ");
}
}
}
};
另外,不要忘記在 ThreadPoolManager 的構造方法中執(zhí)行這個線程.
private ThreadPoolManager() {
//...
mThreadPoolExecutor.execute(delayThread);
}
現(xiàn)在, 你可以斷網測試一下我們的重試機制是否生效.