廢話前言
我們在平時使用第三方框架的時候總覺得就那樣使用就行了,然而如果第三方框架不存在了,就會感覺不知所措了。其實我們應該瞭解第三方訪問框架的設計思路,進而學習其中的思想,不管到哪裏,用什麼語言都能應對。
市面上常用的網絡訪問框架是okhttp。他的優點是接收多個請求,重試機制。他在請求的時候將請求放入請求隊列。使用線程池來響應每一個請求。線程池如果有空閒的線程時去相應請求。否則就等待。線程池有默認的幾個任務線程。任務線程是負責處理請求的,線程池中有一個核心線程專門負責不停的去隊列中獲取請求,把請求交給任務線程去處理。這樣任務線程跟請求關聯起來。
網絡訪問框架的請求設計包含url,params,listener又包括post和get請求。還包括Basic認證等。這些都是對HttpUrlConnection的封裝。
對高併發的理解
最原始的處理方式
1、一個activity有多個請求,假設10個,先存起來這些請求。先後順序往server發送。服務器對客戶端的相應來一個請求就會創建一個線程,然而server假設最多能承受10萬個請求。如果數量多了話服務器受不了。
2、後來解決方法是服務器建立線程池,將線程交給線程池處理,去優化thread。如果請求越來越多還是承受不了,
3、分發,中央處理器,請求分發到不同的服務器。一臺服務器承受不了,擴展服務器,中央處理器將請求分發到機房的服務器。用來相應請求。
對服務器的優化,Nio。
高併發最終解決唯一的就是加服務器。代碼對高併發的處理是節省服務器的硬件成本。
正文
廢話少說,上代碼
定義請求接口封裝數據
package com.tydfd.tydfdokhttp.util;
import com.tydfd.tydfdokhttp.http.UserBean;
/**
* @author liudo
*/
public interface IHttpRequest {
/**
* 封裝請求接口
* @param url
*/
void setUrl(String url);
/**
* 設置數據
* @param data
*/
void setData(byte[] data);
/**
* 設置回調
* @param callbackListener
*/
void setListener(CallbackListener callbackListener);
/**
* 執行線程
*/
void execute(UserBean userBean);
}
設置回調接口
package com.tydfd.tydfdokhttp.util;
import java.io.InputStream;
public interface CallbackListener {
void onSuccess(InputStream inputStream);
void onFailure();
}
線程池設計
package com.tydfd.tydfdokhttp.util;
import android.util.Log;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author liudo
*/
public class ThreadPoolManager {
/**
* 創建隊列,用來保存異步請求任務
*/
private LinkedBlockingDeque<Runnable> mQueue = new LinkedBlockingDeque<>();
/**
* 添加異步任務到隊列中
* @param runnable
*/
public void addTask(Runnable runnable){
if(runnable != null){
try {
mQueue.put(runnable);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 創建延遲隊列
*/
private DelayQueue<HttpTask> mDelayQueue = new DelayQueue<>();
public void addDelayTask(HttpTask ht){
if(ht != null){
ht.setDelayTime(3000);
mDelayQueue.offer(ht);
}
}
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.getRetryCount() < 3){
mThreadPoolExecutor.execute(ht);
ht.setRetryCount(ht.getRetryCount()+1);
Log.e("=== 重試機制 ===",ht.getRetryCount() + "");
}else{
Log.e("=== 重試機制 ===","執行次數超限,放棄");
}
}
}
};
/**
* 3 創建線程池
*/
private ThreadPoolExecutor mThreadPoolExecutor;
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);
mThreadPoolExecutor.execute(delayThread);
}
/**
* 創建 隊列與線程池的"交互"線程
*/
public Runnable communicateThread = new Runnable() {
@Override
public void run() {
Runnable ruun = null;
while (true){
try {
ruun = mQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
mThreadPoolExecutor.execute(ruun);
}
}
};
private static ThreadPoolManager threadPoolManager = new ThreadPoolManager();
public static ThreadPoolManager getInstance(){
return threadPoolManager;
}
}
請求實現類
package com.tydfd.tydfdokhttp.util;
import android.util.Base64;
import com.tydfd.tydfdokhttp.http.UserBean;
import java.io.BufferedOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* @author liudo
*/
public class JsonHttpRequest implements IHttpRequest{
private String url;
private byte[] data;
private CallbackListener mCallbackListener;
private HttpURLConnection urlConnection;
@Override
public void setUrl(String url) {
this.url = url;
}
@Override
public void setData(byte[] data) {
this.data = data;
}
@Override
public void setListener(CallbackListener callbackListener) {
this.mCallbackListener = callbackListener;
}
@Override
public void execute(UserBean userBean) {
URL url = null;
try {
url = new URL(this.url);
/**
* 打開http連接
*/
urlConnection = (HttpURLConnection) url.openConnection();
/**
* 連接的超時時間
*/
urlConnection.setConnectTimeout(6000);
/**
* 不使用緩存
*/
urlConnection.setUseCaches(false);
/**
* 是成員函數,僅作用於當前函數,設置這個連接是否可以被重定向
*/
urlConnection.setInstanceFollowRedirects(true);
/**
* 響應的超時時間
*/
urlConnection.setReadTimeout(3000);
/**
* 設置這個連接是否可以寫入數據
*/
urlConnection.setDoInput(true);
/**
* 設置這個連接是否可以輸出數據
*/
urlConnection.setDoOutput(true);
/**
* 設置請求的方式
*/
if(data==null){
urlConnection.setRequestMethod("GET");
}else {
urlConnection.setRequestMethod("POST");
}
/**
* 設置Basic認證
*/
if(userBean!=null||userBean.getUsername()!= ""||userBean.getPassword()!=""){
String userMsg = userBean.getUsername() + ":" + userBean.getPassword();
String base64UserMsg = Base64.encodeToString(userMsg.getBytes(),Base64.DEFAULT);
final String tokenStr = "Basic " + base64UserMsg;
urlConnection.addRequestProperty("Authorization", tokenStr);
}
/**
* 設置消息的類型
*/
urlConnection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
/**
* 連接,從上述至此的配置必須要在connect之前完成,實際上它只是建立了一個與服務器的TCP連接
*/
urlConnection.connect();
/**
* -------------使用字節流發送數據--------------
*/
OutputStream out = urlConnection.getOutputStream();
/**
* 緩衝字節流包裝字節流
*/
BufferedOutputStream bos = new BufferedOutputStream(out);
/**
* 把這個字節數組的數據寫入緩衝區中
*/
bos.write(data);
/**
* 刷新緩衝區,發送數據
*/
bos.flush();
out.close();
bos.close();
/**
* ------------字符流寫入數據------------
*/
/**
* 得到服務端的返回碼是否連接成功
*/
if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
InputStream in = urlConnection.getInputStream();
mCallbackListener.onSuccess(in);
}else{
// 訪問失敗,重試
throw new RuntimeException("請求失敗");
}
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException("請求失敗");
}finally{
urlConnection.disconnect();
}
}
}
響應請求泛型封裝
package com.tydfd.tydfdokhttp.util;
import android.os.Handler;
import android.os.Looper;
import com.alibaba.fastjson.JSON;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* @author liudo
*/
public class JsonCallbackListener<T> implements CallbackListener{
private Class<T> responseClass;
Handler handler = new Handler(Looper.getMainLooper());
private IJsonDataListener mIJsonDataListener;
public JsonCallbackListener(Class<T> responseClass,IJsonDataListener listener){
this.responseClass = responseClass;
mIJsonDataListener = listener;
}
@Override
public void onSuccess(InputStream inputStream) {
String response = getContent(inputStream);
final T clazz = JSON.parseObject(response,responseClass);
handler.post(new Runnable() {
@Override
public void run() {
mIJsonDataListener.onSuccess(clazz);
}
});
}
private String getContent(InputStream inputStream){
String content=null;
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder sb = new StringBuilder();
String line = null;
try {
while ((line = reader.readLine()) != null) {
sb.append(line + "\n");
}
} catch (IOException e) {
System.out.println("Error=" + e.toString());
} finally {
try {
inputStream.close();
} catch (IOException e) {
System.out.println("Error=" + e.toString());
}
}
return sb.toString();
} catch (Exception e) {
e.printStackTrace();
}
return content;
}
@Override
public void onFailure() {
}
}
請求任務
package com.tydfd.tydfdokhttp.util;
import com.alibaba.fastjson.JSON;
import com.tydfd.tydfdokhttp.http.UserBean;
import java.io.UnsupportedEncodingException;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
/**
* @author liudo
*/
public class HttpTask<T> implements Runnable, Delayed {
private IHttpRequest mIHttpRequest;
private UserBean mUserBean;
public HttpTask(T requestData, String url, UserBean userBean,IHttpRequest httpRequest, CallbackListener callbackListener){
this.mIHttpRequest = httpRequest;
this.mUserBean = userBean;
httpRequest.setUrl(url);
httpRequest.setListener(callbackListener);
String content = JSON.toJSONString(requestData);
try {
httpRequest.setData(content.getBytes("utf-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try{
mIHttpRequest.execute(mUserBean);
}catch (Exception e){
/**
* 將失敗的任務添加到重試隊列中
*/
ThreadPoolManager.getInstance().addDelayTask(this);
}
}
private long delayTime;
private int retryCount;
public int getRetryCount(){
return retryCount;
}
public void setRetryCount(int retryCount) {
this.retryCount = retryCount;
}
public long getDelayTime() {
return delayTime;
}
public void setDelayTime(long delayTime) {
// 設置延遲時間 3000
this.delayTime = System.currentTimeMillis()+delayTime;
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(this.delayTime - System.currentTimeMillis(),TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
return 0;
}
}
IJsonDataListener
package com.tydfd.tydfdokhttp.util;
public interface IJsonDataListener<T> {
/**
* 請求成功
* @param m
*/
void onSuccess(T m);
/**
* 請求失敗
*/
void onFailure();
}
請求方法封裝
package com.tydfd.tydfdokhttp.http;
import com.tydfd.tydfdokhttp.util.CallbackListener;
import com.tydfd.tydfdokhttp.util.HttpTask;
import com.tydfd.tydfdokhttp.util.IHttpRequest;
import com.tydfd.tydfdokhttp.util.IJsonDataListener;
import com.tydfd.tydfdokhttp.util.JsonCallbackListener;
import com.tydfd.tydfdokhttp.util.JsonHttpRequest;
import com.tydfd.tydfdokhttp.util.ThreadPoolManager;
/**
* @author liudo
*/
public class NeOkHttp {
/**
*
* @param requestData 請求數據
* @param url 請求url
* @param userBean Basic認證
* @param response
* @param listener
* @param <T>
* @param <M>
*/
public static<T,M> void sendJsonRequest(T requestData, String url,UserBean userBean,
Class<M> response, IJsonDataListener listener){
IHttpRequest httpRequest = new JsonHttpRequest();
CallbackListener callbackListener = new JsonCallbackListener<>(response,listener);
HttpTask httpTask = new HttpTask(requestData,url,userBean,httpRequest,callbackListener);
ThreadPoolManager.getInstance().addTask(httpTask);
}
}
MainActivity
package com.tydfd.httputils;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.util.TimeUtils;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.tydfd.tydfdokhttp.http.NeOkHttp;
import com.tydfd.tydfdokhttp.http.ResponseBean;
import com.tydfd.tydfdokhttp.http.TimeDateUtils;
import com.tydfd.tydfdokhttp.http.UserBean;
import com.tydfd.tydfdokhttp.util.IJsonDataListener;
/**
* @author liudo
*/
public class MainActivity extends AppCompatActivity {
private Button mButton;
private TextView mTextView;
/**
* http://www.mxnzp.com/api/holiday/single/20181208 獲取今日運勢http://www.mxnzp.com/api/holiday/single/
*/
private String url = "http://www.mxnzp.com/api/holiday/single/";
// private String url2 = "http://192.168.120.194:8080/Demo/ResponseData";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = findViewById(R.id.button);
mTextView = findViewById(R.id.textView);
String time = TimeDateUtils.getCurrentDateStr(TimeDateUtils.FORMAT_TYPE_1);
url = url+ time;
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(MainActivity.this, "點擊了", Toast.LENGTH_SHORT).show();
UserBean userBean = new UserBean();
userBean.setUsername("");
userBean.setPassword("123456");
NeOkHttp.sendJsonRequest(null, url,userBean, ResponseBean.class, new IJsonDataListener<ResponseBean>() {
@Override
public void onSuccess(final ResponseBean rb) {
runOnUiThread(new Runnable() {
@Override
public void run() {
mTextView.setText(rb.getData());
}
});
Log.i("===> ",rb.toString());
}
@Override
public void onFailure() {
}
});
}
});
}
public void Test(){
TimeUtils.getTimeZoneDatabaseVersion();
}
}
該http請求框架使用線程池控制線程訪問隊列中的請求,如果請求失敗使用延遲隊列將失敗的請求加入,進而重新發起請求。大致思路就是上面的代碼。不明白的歡迎交流。