为什么要手写框架?
- 岗位需求
- 提升必要
- 减少资源浪费
- 进阶的必经之路
掌握网络框架的原理
搭建网络访问框架
-
首先创建一个网络Moudle模块
-
主项目依赖我们的网络框架
搭建程序员请求类(入参,接受返回)JyfHttp
package com.example.http;
/**
* 供程序员使用的类
*/
public class JyfHttp {
// url
// 请求参数
// 接收结果的回调接口
// 接收结果的参数类型
public static void senRequest(String url, T requestData, Class response, IJsonDataListener iJsonDataListener) {
// 请求对象的封装
// 为什么使用父类呢?通过接口的父类的引用去指向子类的实例
IHttpRequest httpRequest = new JsonHttpRequest();
// 创建一个内部回调接口的实现类
CallBackListener callBackListener = new JsonCallBackListener(response, iJsonDataListener);
// 请求对象的二次封装,将请求对象封装成线程
HttpTask httpTask = new HttpTask(httpRequest, callBackListener, url, requestData);
// 将请求线程加入到请求队列中
ThreadManager.getInstance().addTask(httpTask);
}
}
(1)创建请求对象
package com.example.http;
/**
* 请求对象的顶层接口
* 封装我们的请求对象
*/
public interface IHttpRequest {
// 设置Url
void setUrl(String url);
// 设置请求参数
void setData(byte[] data);
// 设置回调接口 两个回调接口来会调用,实现服务器返回的数据流直接解析为程序员需要的数据类型
void setLisener(CallBackListener callBackListener);
// 执行请求的方法
void execute();
}
package com.example.http;
import java.io.BufferedOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* 请求接口的实现类 请求参数的封装类
*/
public class JsonHttpRequest implements IHttpRequest{
private String url;
private byte[] data;
private CallBackListener callBackListener;
// 执行请求的对象
private HttpURLConnection httpURLConnection;
@Override
public void setUrl(String url) {
this.url = url;
}
@Override
public void setData(byte[] data) {
this.data = data;
}
@Override
public void setLisener(CallBackListener callBackListener) {
this.callBackListener = callBackListener;
}
@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();
// 先回调内部的回调接口 让内部接口去转换为程序员需要的Class
callBackListener.onSuccess(in);
} else {
throw new RuntimeException("请求失败!");
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("请求失败!");
} finally {
// 关闭 HTTPConnection 对象
httpURLConnection.disconnect();
}
}
public String getUrl() {
return url;
}
public byte[] getData() {
return data;
}
public CallBackListener getCallBackListener() {
return callBackListener;
}
public HttpURLConnection getHttpURLConnection() {
return httpURLConnection;
}
}
(2)创建一个内部回调接口
package com.example.http;
import java.io.InputStream;
/**
* 请求接口的回调 给框架层调用的接口
* @param
*/
public interface CallBackListener {
// 请求成功
void onSuccess(InputStream inputStream);
// 请求失败
void onFailed();
}
package com.example.http;
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;
/**
* 内部接口的实现类 将流转为用户想要的类型
* 实现接口,则接口调用的时候,持有的实例就直接是接口的实现类
*/
public class JsonCallBackListener implements CallBackListener{
// 知道用户需要的请求结果的类型
private Class response;
// 外部的回调接口类
private IJsonDataListener iJsonDataListener;
// 切换线程的Handler
private Handler handler = new Handler(Looper.getMainLooper());
/**
* 构造方法
* 在创建的时候就把需要的参数传递进来
* @param response
* @param iJsonDataListener
*/
public JsonCallBackListener(Class response, IJsonDataListener iJsonDataListener) {
this.response = response;
this.iJsonDataListener = iJsonDataListener;
}
@Override
public void onSuccess(InputStream inputStream) {
// 解析结束 回调给外部的回调接口
// 使用阿里巴巴的fastjson来解析即可
// 第一步 将流转化为json字符串
String content = getContent(inputStream);
// 第二步 使用阿里巴巴的库将JSON字符串转化为程序员想要的对象类型
final T t = JSON.parseObject(content, response);
// 第三部 通过主线程将数据返回出去供外界使用
handler.post(new Runnable() {
@Override
public void run() {
iJsonDataListener.onSuccess(t);
}
});
}
@Override
public void onFailed() {
handler.post(new Runnable() {
@Override
public void run() {
iJsonDataListener.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", "");
}
}
(3)请求对象再次封装为Task继承Runable() 任务即可,也就是一个任务就是为一个线程
package com.example.http;
import com.alibaba.fastjson.JSON;
import java.io.UnsupportedEncodingException;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
/**
* 请求对象的二次封装,将请求对象封装成线程,在线程中来执行请求
*/
public class HttpTask implements Runnable, Delayed {
public IHttpRequest getiHttpRequest() {
return iHttpRequest;
}
public void setiHttpRequest(IHttpRequest iHttpRequest) {
this.iHttpRequest = iHttpRequest;
}
// 请求对象
private IHttpRequest iHttpRequest;
// 当前失败的次数
private int failNum;
public long getDelayTime() {
return delayTime;
}
public void setDelayTime(long delayTime) {
this.delayTime = delayTime + System.currentTimeMillis(); // 把当前的时间以时间戳的形式记录下来
}
// 重试的延迟时间
private long delayTime;
/**
*
* @param iHttpRequest 请求对象
* @param callBackListener 请求结果的回调接口
* @param url 请求路径
* @param requestData 请求数据的对象
*/
public HttpTask(IHttpRequest iHttpRequest, CallBackListener callBackListener, String url, T requestData) {
this.iHttpRequest = iHttpRequest;
this.iHttpRequest.setUrl(url);
this.iHttpRequest.setLisener(callBackListener);
// 判断是否拥有请求数据
if (requestData != null) {
// 将请求对象转为Json字符串
String dataStr = JSON.toJSONString(requestData);
try {
this.iHttpRequest.setData(dataStr.getBytes("utf-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
try {
this.iHttpRequest.execute();
}catch (Exception e) {
// 捕捉到异常,然后将请求线程丢入到重试机制中
ThreadManager.getInstance().addFailedTask(this);
}
this.iHttpRequest.execute();
}
/**
* 队列的执行间隔时间
* @param unit
* @return
*/
@Override
public long getDelay(TimeUnit unit) {
// 取出时间 减去时间戳
return unit.convert(getDelayTime() - System.currentTimeMillis(), TimeUnit.SECONDS);
}
@Override
public int compareTo(Delayed o) {
return 0;
}
public int getFailNum() {
return failNum;
}
public void setFailNum(int failNum) {
this.failNum = failNum;
}
}
(4)直接使用task.execute执行即可
(5)创建队列管理类,添加进队列即可 ThreadManager().getInstance().addTask(task)
package com.example.http;
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;
/**
* 队列管理类 线程管理类
* 使用单例来实现
*/
public class ThreadManager {
private static ThreadManager threadManager = new ThreadManager();
// 请求线程的阻塞队列
// 为什么使用Runnable而不是HttpTask是因为后面更好的扩展,比如再有一个其他的继承Runnable类
private LinkedBlockingDeque mQueue = new LinkedBlockingDeque<>();
// 线程池
private ThreadPoolExecutor threadPoolExecutor;
// 创建重试队列
// 用到DelayQueue,必须要实现一个Delay的接口,有一个时间的概念
private DelayQueue failedQueue = new DelayQueue<>();
private ThreadManager(){
// 初始化线程池
// 解释一下线程池各个参数的意思
/**
* 1: 核心线程数
* 2:最大线程数
* 3:空闲线程的保留时间
* 4:单位:秒
* 5:队列对象,它决定了缓存任务的排队策略,用来装载被拒绝的对象,以及最大数量
* 6:被拒绝之后
*/
threadPoolExecutor = new ThreadPoolExecutor(3, 10, 15, TimeUnit.SECONDS, new ArrayBlockingQueue(4),
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 被拒绝后再次加入到线程池中,保证每一个请求都执行到位
addTask(r);
}
});
// 通过线程池,来执行我们的核心线程
threadPoolExecutor.execute(runnable);
// 执行重试队列的核心线程
threadPoolExecutor.execute(failedRunnable);
}
public static ThreadManager getInstance() {
return threadManager;
}
/**
* 将请求线程加入到队列中的方法
*/
public void addTask(Runnable runnable) {
if (runnable == null) return;
try {
mQueue.put(runnable);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 将失败的请求线程加入到重试队列中
* @param httpTask
*/
public void addFailedTask(HttpTask httpTask) {
if (runnable == null) return;
failedQueue.offer(httpTask);
}
/**
* 核心线程 一直去队列中获取到请求线程,然后让线程池去执行
*/
public Runnable runnable = new Runnable() {
@Override
public void run() {
while (true) {
try {
// 拿出请求线程
Runnable take = mQueue.take();
// 通过请求线程去执行
threadPoolExecutor.execute(take);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
/**
* 重试的核心线程
*/
public Runnable failedRunnable = new Runnable() {
@Override
public void run() {
while (true) {
try {
HttpTask httpTask = failedQueue.take();
if (httpTask.getFailNum() < 3) {
// 通过线程池执行
httpTask.setFailNum(httpTask.getFailNum() + 1);
threadPoolExecutor.execute(httpTask);
Log.e("请求重试机制==========", "我是次数" + httpTask.getFailNum());
} else {
JsonHttpRequest iHttpRequest = (JsonHttpRequest) httpTask.getiHttpRequest();
iHttpRequest.getCallBackListener().onFailed();
// 重试了三次依然失败
Log.e("请求重试机制=============", "失败超过3次");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
}
- 定义回调接口 CallBackListener(代码参照上面)
- 内部接口接受返回并转化为程序员需要的类 JsonCallBackListener(参数为用户想要的数据类型、以及回调的Listener)
(1) 将流转化为JSON字符串,再将JSON转为用户想要的类型
(2) 使用Hander回到主线程回调给CallBackListener(代码参照上面)
高并发的实现思路
- 上面把请求对象封装进入了一个线程进行执行,那么我们就可以随处来创建线程了么?不,我们需要创建一个线程池来管理所有的线程,即线程池
- 创建请求队列管理类以及线程管理类
- 我们一个框架,只需要创建一个队列即可
- 使用单例来创建一个线程池管理器,ThreadManager(参照上面)
- 创建一个LinkBlockingQueue
队列,来管理Task即可 - 创建线程池 ThreadPoorExecutor 来管理队列,(参数为核心线程数、最大线程数、存活时间)线程池可以处理任何线程情况,线程执行成功了,进行标记,回收掉。如果没有执行成功,则感知到没有执行成功,那么就会通过rejuctedExecution回调回来,我们再次添加task到队列中,重新执行即可,那么这个线程永远不会因为框架原因而执行失败。
- 再写一个核心线程,一直去队列中获取请求人物,然后交给线程池去执行
- 使用mQueue.take() 获取每个Task,交给线程池执行,threadPoorExector.execute(),核心线程什么时候执行?
- 执行100次?闪退了?是因为高并发的情况下,往往造成有的请求执行不成功,那么我们怎么错?就是接下来的重试机制!!
断网或者弱网重试机制
- 网络波动的时候
- 各种意外被拒绝的请求再次执行
- 三次机会之后,直接销毁
(1)HttpTask的run方法中抛出了异常
(2)使用try catch 捕捉到异常,再次添加到我们的线程池进行任务重试即可
@Override
public void run() {
try {
this.iHttpRequest.execute();
}catch (Exception e) {
// 捕捉到异常,然后将请求线程丢入到重试机制中
ThreadManager.getInstance().addFailedTask(this);
}
this.iHttpRequest.execute();
}
(2)不能使用之前的线程池来装,要使用DelayQueue,为什么呢?因为,这么队列,有延迟时间,也就是隔一段时间,我们去调用。所以使用这个队列来装之前失败的任务。
(3)让我们的HttpTask实现我们Delay方法,实现延迟时间方法,即可加入到DelayQueue
(4)使用offer方法将失败的请求放入到队列中。
/**
* 将失败的请求线程加入到重试队列中
* @param httpTask
*/
public void addFailedTask(HttpTask httpTask) {
if (runnable == null) return;
failedQueue.offer(httpTask);
}
(5)注意设置的延迟时间为,当前延迟时间+系统时间
(6)我们还需要一个核心的线程来一直执行失败的队列即可
/**
* 重试的核心线程
*/
public Runnable failedRunnable = new Runnable() {
@Override
public void run() {
while (true) {
try {
HttpTask httpTask = failedQueue.take();
if (httpTask.getFailNum() < 3) {
// 通过线程池执行
httpTask.setFailNum(httpTask.getFailNum() + 1);
threadPoolExecutor.execute(httpTask);
Log.e("请求重试机制==========", "我是次数" + httpTask.getFailNum());
} else {
JsonHttpRequest iHttpRequest = (JsonHttpRequest) httpTask.getiHttpRequest();
iHttpRequest.getCallBackListener().onFailed();
// 重试了三次依然失败
Log.e("请求重试机制=============", "失败超过3次");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
(7)我们需要设置一个参数,最多失败几次,比如3次
(8)每次拿到任务的时候,判断次数,每执行一次,修改次数,如果小于3次,则执行。
Demo地址:https://github.com/jiayuanfa/JyfNetwork/tree/master/http/src