废话前言
我们在平时使用第三方框架的时候总觉得就那样使用就行了,然而如果第三方框架不存在了,就会感觉不知所措了。其实我们应该了解第三方访问框架的设计思路,进而学习其中的思想,不管到哪里,用什么语言都能应对。
市面上常用的网络访问框架是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请求框架使用线程池控制线程访问队列中的请求,如果请求失败使用延迟队列将失败的请求加入,进而重新发起请求。大致思路就是上面的代码。不明白的欢迎交流。