雖然市面上有很多優秀的開源網絡框架,例如volley,Okhttp。爲啥還要自己來手寫一個呢?主要是爲了加深一下自己的記憶,還有把自己的技術點串聯起來。代碼並不難,都挺簡單的,有詳細的註釋。可以優化的地方很多,但是寫文的時候狀態不是特別好,所以就懶得去改了。再者,大部分情況下我也是會去用OKHttp的!寫下來是對自己花費時間的認可。
我相信大部分朋友手寫一個簡單的網絡請求框架都沒什麼問題。不過溫故而知新嘛,我再寫一遍的時候,倒是發現了一些以前未曾注意的地方。
這個框架稍微注重了一下多併發的問題。但是沒有集合命令模式,所以沒有撤銷請求這個功能。
第一個類是展示給應用層調用的靜態方法。寫成靜態方法是因爲我懶,喜歡的可以自己改成鏈式調用。這個方法需要傳入的參數也寫的很清楚啦!
public class SimpleHttp {
/**
* 訪問地址URL
* 請求參數DATA
* 回調函數 Listener
* 需要的類型Cls
*/
public static<T,K> void sendRequest(String url, T data, Class<K> cls, IHttpRequestListener listener){
//構建一個請求對象
IHttpRequest iHttpRequest=new HttpRequest();
//構建一個返回回調
IHttpResponseListener iHttpResponseListener=new HttpResponseListener<>(cls,listener);
//構建一個請求線程
HttpTask httpTask=new HttpTask(iHttpRequest,url,data,iHttpResponseListener);
//構建一個線程管理
ThreadManager.getInstance().addTask(httpTask);
}
}
這個是請求的對象接口,至少得滿足可擴展性嘛!
public interface IHttpRequest {
//訪問的鏈接URL
void setUrl(String Url);
//請求的參數
void setBytes(byte[] data);
//請求返回的回調
void setIDnHttpResponseListener(IHttpResponseListener iDnHttpResponseListener);
//請求方法
void execute();
}
這個是請求的實現類啦!選的是HttpURLConnection 因爲足夠輕量,基本能滿足需求。請求方法我沒有封裝到外面,想要使用的可以自行封裝一下。
public class HttpRequest implements IHttpRequest {
private String Url;
private byte[] data;
private IHttpResponseListener iDnHttpResponseListener;
private HttpURLConnection httpURLConnection;
@Override
public void setUrl(String Url) {
this.Url = Url;
}
@Override
public void setBytes(byte[] data) {
this.data = data;
}
@Override
public void setIDnHttpResponseListener(IHttpResponseListener iDnHttpResponseListener) {
this.iDnHttpResponseListener=iDnHttpResponseListener;
}
@Override
public void execute() {
URL url = null;
try {
url = new URL(this.Url);
httpURLConnection = (HttpURLConnection) url.openConnection();
httpURLConnection.setConnectTimeout(6000); //連接超時時間
httpURLConnection.setUseCaches(false); //不使用緩存
httpURLConnection.setInstanceFollowRedirects(true); //是成員變量 僅作用域當前函數,設置當前這個對象
httpURLConnection.setReadTimeout(3000); //響應超時的時間
httpURLConnection.setDoInput(true); //設置這個連接對否可以寫入數據;
httpURLConnection.setDoOutput(true); //設置這個連接對否可以輸出數據;
httpURLConnection.setRequestMethod("POST"); //設置這個請求的方法
httpURLConnection.setRequestProperty("Content-Type","application/json;charset=UTF-8");
httpURLConnection.connect(); //建立連接
//---------------使用字節流發送數據---------------------------
OutputStream out = httpURLConnection.getOutputStream();
//緩衝字節流 包裝字節流
BufferedOutputStream bos = new BufferedOutputStream(out);
//把字節流數組寫入緩衝區中
bos.write(data);
//刷新緩衝區 發送數據
bos.flush();
out.close();
bos.close();
//如果響應碼爲200代表請求訪問成功
if(httpURLConnection.getResponseCode() == HttpURLConnection.HTTP_OK){
InputStream in = httpURLConnection.getInputStream();
//回調內部的回調接口
iDnHttpResponseListener.onSuccess(in);
}else{
throw new RuntimeException("請求失敗");
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("請求失敗");
}finally {
//關閉httpURLConnection對象
httpURLConnection.disconnect();
}
}
}
這是返回時用的回調接口,實現2個方法即可。
public interface IHttpResponseListener {
//返回成功
void onSuccess(InputStream inputStream);
//反回失敗
void onFailed();
}
返回回調接口的實現類,把返回回來的參數轉換成自己想要的數據類型。
public class HttpResponseListener<T> implements IHttpResponseListener {
private IHttpRequestListener listener;
private Class<T> cls ;
//切換線程
Handler handler=new Handler(Looper.getMainLooper());
public HttpResponseListener(Class<T>cls , IHttpRequestListener listener){
this.cls=cls;
this.listener=listener;
}
@Override
public void onSuccess(InputStream inputStream) {
String responseStr=getContent(inputStream);
final T t= JSON.parseObject(responseStr,cls);
handler.post(new Runnable() {
@Override
public void run() {
listener.onSuccess(t);
}
});
}
@Override
public void onFailed() {
handler.post(new Runnable() {
@Override
public void run() {
listener.onFailed();
}
});
}
/**
* inputStream轉爲String類型
* @param inputStream
* @return
*/
private String getContent(InputStream inputStream) {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder sb = new StringBuilder();
String line = null;
try {
while ((line = bufferedReader.readLine()) != null) {
sb.append(line + "/n");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return sb.toString().replace("/n","");
}
}
線程池的管理類,這裏爲什麼要用LinkedBlockingQueue不用ArrayBlockingQueue的原因在於
鏈表持有2把鎖,可以同時進出,但是Array只有一把鎖。
在只進或者只出的時候,用數組會有明顯優勢,但是要滿足同時進出,鏈表會更快。
這裏還用了DelayQueue,這是一個延時列表。如果不設置延時時間,會出問題。
雖然拒絕策略大部分時候都沒啥用,但是你不寫肯定是不行的!
public class ThreadManager {
private static ThreadManager threadManager=new ThreadManager();
public static ThreadManager getInstance(){
return threadManager;
}
//請求隊列
private LinkedBlockingQueue<Runnable> requestQueue=new LinkedBlockingQueue<>();
//失敗隊列
private DelayQueue<Delayed> failedQueue=new DelayQueue<>();
//線程池
private ThreadPoolExecutor threadPoolExecutor;
private ThreadManager(){
threadPoolExecutor = new ThreadPoolExecutor(3, 10, 15,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(4), new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
addTask(r);
}
});
threadPoolExecutor.execute(runnable);
threadPoolExecutor.execute(failRunnable);
}
//添加線程
public void addTask(Runnable runnable){
if(runnable==null){
return;
}
try {
requestQueue.put(runnable);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//添加失敗線程
public void addFailedTask(HttpTask httpTask){
if(httpTask==null){
return;
}
httpTask.setDelayTIme(3000);
failedQueue.offer(httpTask);
}
//核心線程
private Runnable runnable=new Runnable() {
@Override
public void run() {
while (true){
try {
Runnable runnable=requestQueue.take();
threadPoolExecutor.execute(runnable);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
//失敗核心線程
private Runnable failRunnable=new Runnable() {
@Override
public void run() {
while (true){
try {
HttpTask httpTask= (HttpTask) failedQueue.take();
if(httpTask.getRetryTimes()<3){
httpTask.setRetryTimes(httpTask.getRetryTimes()+1);
threadPoolExecutor.execute(httpTask);
}else {
IHttpResponseListener httpResponseListener=httpTask.getListener();
httpResponseListener.onFailed();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
}
這個就是請求的線程啦!之後需要把請求線程交給線程池去管理。
public class HttpTask<T> implements Runnable, Delayed {
private IHttpRequest iHttpRequest;
private IHttpResponseListener listener;
//延遲時間
private long delayTIme;
//重試次數
private int retryTimes;
public HttpTask(IHttpRequest iHttpRequest,String url,T Data,IHttpResponseListener listener){
this.iHttpRequest=iHttpRequest;
this.listener=listener;
this.iHttpRequest.setUrl(url);
this.iHttpRequest.setIDnHttpResponseListener(listener);
if(Data!=null){
String jsonStr= JSON.toJSONString(Data);
try {
this.iHttpRequest.setBytes(jsonStr.getBytes("UtF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
try {
this.iHttpRequest.execute();
}catch (Exception e){
ThreadManager.getInstance().addFailedTask(this);
}
}
public IHttpResponseListener getListener() {
return listener;
}
public long getDelayTIme() {
return delayTIme;
}
public void setDelayTIme(long delayTIme) {
this.delayTIme = delayTIme+System.currentTimeMillis();
}
public int getRetryTimes() {
return retryTimes;
}
public void setRetryTimes(int retryTimes) {
this.retryTimes = retryTimes;
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(getDelayTIme()-System.currentTimeMillis(),TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
return 0;
}
}
大部分地方都加了註釋,加上代碼本身難度也不高。如果還有什麼問題也可以直接問我。