前言
在前两篇中主要讲解了OkHttp源码解析,在本篇中,将会结合前两篇所有的知识点,从零开始手写一份阉割版的OkHttp框架。因此,读者也可以按照本章的方式从零开始一步一步手仿造出OkHttp框架。
视频教程:Android百大框架源码解析Retrofit/OkHttp/Glide/RxJava/EventBus...._哔哩哔哩_bilibili:https://www.bilibili.com/video/BV1mT4y1R7F4?spm_id_from=333.999.0.0
在开始之前,我们先整理一下,需要按照什么样的步骤才能仿造一个阉割版的OkHttp。
- 依葫芦画瓢,先创造身体,复制一份,再注入灵魂
- 创造Request 对象,再造 Response 对象
- 流程图:分发器、责任链、拦截器
- 分发器:执行队列、等待队列、线程池、逻辑判断、线程结束
- 拦截器:对应拦截器的职责干什么 (专一,只做自己的事)
- 责任链模式:肯定有一个chain接口和其他实现类,遵循对类隐藏,对接口暴露
- 辅助类完成
1、先创造对应雏形
我们先看原本OkHttp使用是怎样的
Request request = new Request.Builder().url(PATH).build();
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) {
content = response.body().toString();
mHandler.sendEmptyMessage(1);
}
});
Ok,原本OkHttp使用是这样的,我们先复制一份,然后在每一个变量都加上属于自己的后缀,表示是我们自己的,于是就成了。
Request2 request2 = new Request2.Builder().url(PATH).build();
OkHttpClient2 okHttpClient2 = new OkHttpClient2.Builder().build();
Call2 call2 = okHttpClient2.newCall(request2);
call2.enqueue(new Callback2() {
@Override
public void onFailure(Call2 call, IOException e) {
}
@Override
public void onResponse(Call2 call, Response2 response) {
content = response.body().toString();
mHandler.sendEmptyMessage(1);
}
});
现在肯定代码全报红,因为没有对应的类,既然没有那我们一个一个的创造,代码报红也一个个解决。先按从上到下的顺序解决,创建一个全新的类 Request2 ,先不写任何逻辑,先把报红的问题解决了,于是乎对应的Request2 为:
Request2.class
public class Request2 {
public static class Builder {
public Builder() {
}
public Builder url(String url) {
return this;
}
public Request2 build() {
return new Request2();
}
}
}
这个不用多说了吧,典型的建造者模型。接着我们分析 OkHttpClient2 类和 Request2 使用方式几乎一样,只不过少了 url方法,于是乎 OkHttpClient2 雏形也出来了。
OkHttpClient2.class
public class OkHttpClient2 {
public Call2 newCall(Request2 request2) {
return new RealCall2();
}
public static class Builder {
public Builder() {
}
public OkHttpClient2 build() {
return new OkHttpClient2();
}
}
}
接下来就是待实现的Call2 ,以及 Call2 对应的实现类 RealCall2。
Call2.class
public interface Call2 {
void enqueue(Callback2 callback2);
}
RealCall2.class
public class RealCall2 implements Call2{
@Override
public void enqueue(Callback2 callback2) {
}
}
现在 Callback2 没有,继续创建
Callback2.class
public interface Callback2 {
public void onFailure(Call2 call, IOException e);
public void onResponse(Call2 call, Response2 response);
}
现在 只有 Response2 相关的报错了,继续创建
Response2.class
public class Response2 {
public int getStatusCode() {
return statusCode;
}
public void setStatusCode(int statusCode) {
this.statusCode = statusCode;
}
public ResponseBody body() {
return body;
}
public void setBody(ResponseBody body) {
this.body = body;
}
//1、code
//2、响应体
private int statusCode;
private ResponseBody body;
}
这里就需要创建响应体 ResponseBody 。
ResponseBody.class
public class ResponseBody {
private InputStream inputStream;
private String bodyString;
private long contentLength;
private byte[] bytes;
public InputStream getInputStream() {
return inputStream;
}
public void setInputStream(InputStream inputStream) {
this.inputStream = inputStream;
}
public String string() {
return bodyString;
}
public ResponseBody setBodyString(String bodyString) {
this.bodyString = bodyString;
return this;
}
public long getContentLength() {
return contentLength;
}
public void setContentLength(long contentLength) {
this.contentLength = contentLength;
}
public byte[] getBytes() {
return bytes;
}
public void setBytes(byte[] bytes) {
this.bytes = bytes;
}
}
到这里,手写OkHttp的外壳已经写好了,代码也没有报红了。接下来该慢慢注入灵魂了。
2、向 Request2.class 注入灵魂
这里我们还是按照从上到下的顺序依次注入灵魂。先Request2 ,顾名思义,这个类主要封装的请求相关的信息,既然是请求相关的信息,我们先整理下,请求接口时,需要哪些东西?
- 请求方式 String requestMethod
- 请求地址 String url
- 请求头 Map
mHeaderList - 请求体 RequestBody2 requestBody2
- 重定向地址 String redictUrl
- 是否缓存等等
这里 RequestBody2 是没有的,那么。
RequestBody2.class
public class RequestBody2 {
// 表单提交Type application/x-www-form-urlencoded
public static final String TYPE = "application/x-www-form-urlencoded";
private final String ENC = "utf-8";
// 请求体集合 a=123&b=666
Map bodys = new HashMap<>();
/**
* 添加请求体信息
* @param key
* @param value
*/
public void addBody(String key, String value) {
// 需要URL编码
try {
bodys.put(URLEncoder.encode(key, ENC), URLEncoder.encode(value, ENC));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
/**
* 得到请求体信息
*/
public String getBody() {
StringBuffer stringBuffer = new StringBuffer();
for (Map.Entry stringStringEntry : bodys.entrySet()) {
// a=123&b=666&
stringBuffer.append(stringStringEntry.getKey())
.append("=")
.append(stringStringEntry.getValue())
.append("&");
}
// a=123&b=666& 删除&
if (stringBuffer.length() != 0 ) {
stringBuffer.deleteCharAt(stringBuffer.length() -1);
}
return stringBuffer.toString();
}
}
这没啥可说的,就一个很简单的添加获取的方式,那么注入灵魂后的 Request2 为:
Request2.class
public class Request2 {
public static final String GET = "GET";
public static final String POST = "POST";
private String url;
private String requestMethod = GET; // 默认请求下是GET
private Map mHeaderList = new HashMap<>(); // 请求头 之请求集合
private RequestBody2 requestBody2;
private Builder builder;
private boolean isCache;
private String redictUrl;//重定向URL
public boolean isCache() {
return isCache;
}
public void setCache(boolean cache) {
isCache = cache;
}
public String getRedictUrl() {
return redictUrl;
}
public void setRedictUrl(String redictUrl) {
this.redictUrl = redictUrl;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getRequestMethod() {
return requestMethod;
}
public Map getmHeaderList() {
return mHeaderList;
}
public RequestBody2 getRequestBody2() {
return requestBody2;
}
public Request2() {
this(new Builder());
}
public Builder builder(){
return builder;
}
public Request2(Builder builder) {
this.builder = builder;
this.url = builder.url;
this.requestMethod = builder.requestMethod;
this.mHeaderList = builder.mHeaderList;
this.requestBody2 = builder.requestBody2;
}
public static class Builder {
private String url;
private String requestMethod = GET; // 默认请求下是GET
private Map mHeaderList = new HashMap<>();
private RequestBody2 requestBody2;
public Builder url(String url) {
this.url = url;
return this;
}
public Builder get() {
requestMethod = GET;
return this;
}
public Builder post(RequestBody2 requestBody2) {
requestMethod = POST;
this.requestBody2 = requestBody2;
return this;
}
/**
* Connection: keep-alive
* Host: restapi.amap.com
* @return
*/
public Builder addRequestHeader(String key, String value) {
mHeaderList.put(key, value);
return this;
}
public Builder removeRequestHeader(String key) {
if(mHeaderList!=null) {
mHeaderList.remove(key);
}
return this;
}
public Request2 build() {
return new Request2(this);
}
}
}
到这里请求体Request2.class差不多结束了,没啥难度,接下来轮到 OkHttpClient2 了
3、给OkHttpClient2.class注入灵魂
在开始之前,先上流程图,看看OkHttpClient2 到底做了些什么事。
如图所示 OkHttpClient2 通过 newCall方法 返回对应Call的实现类对象 RealCall,然后再通过 RealCall 分别调用 Dispatcher对象的 execute 和 enqueue方法,接着依次调用对应的拦截器,最终产生 Response对象。在上一篇讲解拦截器的时候,除了系统的五大拦截器,开发者可以自定义拦截器,这里再贴一下源码。
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
从源码上看,开发者自定义的拦截器 是通过 OkHttpClient 对象添加的,也就是说,我们手写的对应 OkHttpClient2 也要有开发者可自定义拦截器的功能。
OkHttpClient2.class
public class OkHttpClient2 {
//1、分发器
//2、重试次数
//3、自定义的拦截器数组
//重试次数
int recount;
//分发器初始化
Dispatcher2 dispatcher;
List myInterceptors=new ArrayList<>();
public OkHttpClient2(Builder builder) {
this.recount = builder.recount;
this.dispatcher = builder.dispatcher;
this.myInterceptors = builder.myInterceptors;
}
public Dispatcher2 dispatcher(){
return dispatcher;
}
public int getRecount() {
return recount;
}
public Call2 newCall(Request2 request){
return new RealCall2(this,request);
}
public static class Builder{
List myInterceptors=new ArrayList<>();
int recount = 3; // 重试次数
//分发器初始化
Dispatcher2 dispatcher;
/**
* 构造函数
*/
public Builder(){
dispatcher = new Dispatcher2();
}
public Builder addInterceptor(Interceptor2 interceptor2){
myInterceptors.add(interceptor2);
return this;
}
public void setRecount(int recount) {
this.recount = recount;
}
public OkHttpClient2 build(){
return new OkHttpClient2(this);
}
}
public List getMyInterceptors() {
return myInterceptors;
}
}
这里创建了全新的对象 Interceptor2拦截器接口、Dispatcher2 分发器,根据上面的流程图可知,分发器中有同步和异步方法,这里就只实现异步方法
Dispatcher2.class
public class Dispatcher2 {
public void enqueue(RealCall2.AsyncCall2 call){
}
}
最新的RealCall2.class
public class RealCall2 implements Call2{
public RealCall2(OkHttpClient2 okHttpClient2, Request2 request) {
}
@Override
public void enqueue(Callback2 callback2) {
}
class AsyncCall2 implements Runnable{
@Override
public void run() {
}
}
}
Interceptor2.class
public interface Interceptor2 {
Response2 doNext(Chain2 chain2);
}
因为拦截器是多个,并且每一个功能点都不同,然后每一个拦截器都与下一个相互关联,所以这里又扯出了责任链,在上一篇文章我们可知,所有责任链拦截器的功能就是将 Request 转 变成 getResponse,所以先定义一个实现类 Chain2。
Chain2.class
public interface Chain2 {
//责任链干的事情是封装Request,返回Response
Request2 getRequest();
Response2 getResponse(Request2 request2);
}
Chain2实现类ChainManager.class
public class ChainManager implements Chain2{
@Override
public Request2 getRequest() {
return null;
}
@Override
public Response2 getResponse(Request2 request2) {
return null;
}
}
到这OkHttpClient2.class相关的灵魂也注入完毕,接下来该写高并发分发器了。
4、高并发分发器实现
在关于OkHttp第一篇中,主要讲解了分发器,以及双管队列是如何实现的,这里不多叙述,直接上图。
所图所示
在Dispatcher中,里面是有俩个队列,于是乎。
public class Dispatcher2 {
private int maxRequests = 64;//同时访问任务,不同域名最大限制64个
private int maxRequestsPerHost = 5;//同时访问同一个域名服务器,最大限制5个
//真正执行者是call(包工头),call然后交给拦截器去执行具体任务
//RealCall2
private Deque runningAsyncCalls = new ArrayDeque<>();
//等待的队列
private Deque readyAsyncCalls = new ArrayDeque<>();
//同步方案
//异步方案
public void enqueue(RealCall2.AsyncCall2 call){
//小于64个,同一域名请求小于5个
if(runningAsyncCalls.size()
这里用了两个辅助类,里面没啥逻辑而言,直接贴出来
SocketRequestServer.class
public class SocketRequestServer {
private final String K = " ";
private final String VIERSION = "HTTP/1.1";
private final String GRGN = "\r\n";
/**
* todo 通过Request对象,寻找到域名HOST
* @param request2
* @return
*/
public String getHost(Request2 request2) {
try {
// http://restapi.amap.com/v3/weather/weatherInfo?city=110101&key=13cb58f5884f9749287abbead9c658f2
URL url = new URL(request2.getUrl());
return url.getHost(); // restapi.amap.com
} catch (MalformedURLException e) {
e.printStackTrace();
}
return null;
}
/**
* todo 端口
* @param request2
* @return
*/
public int getPort(Request2 request2) {
try {
URL url = new URL(request2.getUrl());
int port = url.getPort();
return port == -1 ? url.getDefaultPort() : port;
} catch (MalformedURLException e) {
e.printStackTrace();
}
return -1;
}
/**
* todo 获取请求头的所有信息
* @param request2
* @return
*/
public String getRequestHeaderAll(Request2 request2) {
// 得到请求方式
URL url = null;
try {
url = new URL(request2.getUrl());
} catch (MalformedURLException e) {
e.printStackTrace();
}
String file = url.getFile();
// TODO 拼接 请求头 的 请求行 GET /v3/weather/weatherInfo?city=110101&key=13cb58f5884f9749287abbead9c658f2 HTTP/1.1\r\n
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(request2.getRequestMethod()) // GET or POST
.append(K)
.append(file)
.append(K)
.append(VIERSION)
.append(GRGN);
// TODO 获取请求集 进行拼接
/**
* Content-Length: 48\r\n
* Host: restapi.amap.com\r\n
* Content-Type: application/x-www-form-urlencoded\r\n
*/
if (!request2.getmHeaderList().isEmpty()) {
Map mapList = request2.getmHeaderList();
for (Map.Entry entry: mapList.entrySet()) {
stringBuffer.append(entry.getKey())
.append(":").append(K)
.append(entry.getValue())
.append(GRGN);
}
// 拼接空行,代表下面的POST,请求体了
stringBuffer.append(GRGN);
}
// TODO POST请求才有 请求体的拼接
if ("POST".equalsIgnoreCase(request2.getRequestMethod())) {
stringBuffer.append(request2.getRequestBody2().getBody()).append(GRGN);
}
return stringBuffer.toString();
}
}
这里就是请求体的拼接,很简单一看就能明白的那种。
ThreadPoolManager.class
public class ThreadPoolManager {
/**
* 单例设计模式(饿汉式)
* 单例首先私有化构造方法,然后饿汉式一开始就开始创建,并提供get方法
*/
private static ThreadPoolManager mInstance = new ThreadPoolManager();
public static ThreadPoolManager getInstance() {
return mInstance;
}
private int corePoolSize;//核心线程池的数量,同时能够执行的线程数量
private int maximumPoolSize;//最大线程池数量,表示当缓冲队列满的时候能继续容纳的等待任务的数量
private long keepAliveTime = 1;//存活时间
private TimeUnit unit = TimeUnit.HOURS;
private ThreadPoolExecutor executor;
private ThreadPoolManager() {
/**
* 给corePoolSize赋值:当前设备可用处理器核心数*2 + 1,能够让cpu的效率得到最大程度执行
*/
corePoolSize = Runtime.getRuntime().availableProcessors() * 2 + 1;
maximumPoolSize = corePoolSize; //虽然maximumPoolSize用不到,但是需要赋值,否则报错
executor = new ThreadPoolExecutor(
0, //当某个核心线程数
Integer.MAX_VALUE, //先corePoolSize,然后new LinkedBlockingQueue(),然后maximumPoolSize,但是它的数量是包含了corePoolSize的
60L, //表示的是maximumPoolSize当中等待任务的存活时间
TimeUnit.SECONDS,
new LinkedBlockingQueue(), //缓冲队列,用于存放等待任务,Linked的先进先出
Executors.defaultThreadFactory(), //创建线程的工厂
new ThreadPoolExecutor.AbortPolicy() //用来对超出maximumPoolSize的任务的处理策略
);
}
public ThreadPoolExecutor getExecutor() {
return executor;
}
/**
* 执行任务
*/
public void execute(Runnable runnable) {
if (runnable == null) return;
executor.execute(runnable);
}
/**
* 从线程池中移除任务
*/
public void remove(Runnable runnable) {
if (runnable == null) return;
executor.remove(runnable);
}
}
这个类,就是线程池管理类。
最新的RealCall2.class
public class RealCall2 implements Call2{
private OkHttpClient2 okHttpClient2;
private Request2 request2;
//包工头执行流程
public OkHttpClient2 getOkHttpClient2(){
return okHttpClient2;
}
public RealCall2(OkHttpClient2 okHttpClient2, Request2 request2) {
this.okHttpClient2 = okHttpClient2;
this.request2 = request2;
}
@Override
public void enqueue(Callback2 callback2) {
//准备要干事情的地方
//分发出去
okHttpClient2.dispatcher().enqueue(new AsyncCall2(callback2));
}
class AsyncCall2 implements Runnable{
private Callback2 callback2;
public AsyncCall2(Callback2 callback) {
this.callback2 = callback;
}
Request2 getRequest(){
return RealCall2.this.request2;
}
@Override
public void run() {
}
}
}
到这里分发器已经写完了,现在该是责任链了。
5、责任链实现
上面我们通过 ChainManager 实现了 Chain2 接口,但并没有做任何处理,现在就轮到给责任链注入灵魂了。
**最新的ChainManager **
public class ChainManager implements Chain2 {
//链节点
//一种是指定下一任领导,index
private int index;//表示当前执行的链节点的角标
private Call2 call;//表示整个责任链给谁用的
private Request2 request2;//每个节点都是在组装Request2
private List interceptors;//我的节点必须要实现Interceptor2,才是责任链里面的一员
public Call2 getCall(){
return call;
}
public ChainManager(int index, Call2 call, Request2 request2, List interceptors) {
this.index = index;
this.call = call;
this.request2 = request2;
this.interceptors = interceptors;
}
@Override
public Request2 getRequest() {
return request2;
}
@Override
public Response2 getResponse(Request2 request2) {
//就是讲Request进行封装,想办法返回Response
if(interceptors==null || interceptors.isEmpty()){
return new Response2();
}
if(index>=interceptors.size()){
//如果index超过了size,就直接返回,不会走下一个节点了
return new Response2();
}
//当前责任链
Interceptor2 interceptor2 = interceptors.get(index);
//初始化下一个责任链管理器
ChainManager manager = new ChainManager(index+1,call,request2,interceptors);
//当前责任链调用下一任责任链
Response2 response2 = interceptor2.doNext(manager);
//责任链写完了
return response2;
}
}
所谓的责任链,你可以把它看成类似于铁链的一种东西,上一环扣着下一环,上一环的终点表示下一环 的起点。责任链实现就这么简单。接下来就轮到 拦截器的实现了。
6、拦截器的实现
在OkHttp第二篇中,已经讲过,每个拦截器的作用,在这也将会一个个从零手写出对应的拦截器。 我这准备了
- RetryAndFollowInterceptor 重定向拦截器
- BridgeInterceptor 桥接拦截器
- CacheInterceptor 缓存拦截器
- CallServerInteceptor 连接+读写拦截器
现在准备依次实现
6.1 实现重定向拦截器
RetryAndFollowInterceptor.class
public class RetryAndFollowInterceptor implements Interceptor2 {
@Override
public Response2 doNext(Chain2 chain2){
//1、要从网络拦截器下手,去拿Response
System.out.println("我是重试重定向拦截器,执行了");
ChainManager chainManager = (ChainManager) chain2;
RealCall2 realCall2 = (RealCall2) chainManager.getCall();
OkHttpClient2 client2 = realCall2.getOkHttpClient2();
IOException ioException = null;
// 重试次数
if (client2.getRecount() != 0) {
for (int i = 0; i < client2.getRecount(); i++) { // 3
try {
// Log.d(TAG, "我是重试拦截器,我要Return Response2了");
System.out.println("我是重试拦截器,我要Return Response2了");
// 如果没有异常,循环就结束了
Response2 response2 = chain2.getResponse(chainManager.getRequest()); // 执行下一个拦截器(任务节点)
Request2 request2 = chainManager.getRequest();//这里是拿到网络拦截器的网络返回的结果
//如果说RedictUrl有数据,就代表是重定向
if(!TextUtils.isEmpty(request2.getRedictUrl())){
//就把url替换的
request2.setUrl(request2.getRedictUrl());
return chain2.getResponse(request2);
}
return response2;
} catch (Exception e) {
e.printStackTrace();
}
}
}
return new Response2();
}
}
这里就只处理了重定向的逻辑,只要在重试范围内,就能重定向。
6.2 实现桥接拦截器
BridgeInterceptor.class
public class BridgeInterceptor implements Interceptor2 {
@Override
public Response2 doNext(Chain2 chain2){
/**
* Request封装
*/
ChainManager chainManager = (ChainManager) chain2;
Request2 request2 = chain2.getRequest();
Map mHeadList = request2.getmHeaderList();
mHeadList.put("Host",new SocketRequestServer().getHost(chainManager.getRequest()));
if("POST".equalsIgnoreCase(request2.getRequestMethod())){
// 请求体 type lan
/**
* Content-Length: 48
* Content-Type: application/x-www-form-urlencoded
*/
mHeadList.put("Content-Length", request2.getRequestBody2().getBody().length()+"");
mHeadList.put("Content-Type", RequestBody2.TYPE);
}
return chain2.getResponse(request2);
}
}
这里也只做了 很简单的 请求头的封装处理
6.3 实现缓存拦截器
CacheInterceptor.class
public class CacheInterceptor implements Interceptor2 {
@Override
public Response2 doNext(Chain2 chain){
Request2 request = chain.getRequest();
//http 1.0 的版本只有pragma
//Cache-Control 1.1版本有的
//设置响应的缓存时间为60秒,即设置Cache-Control头,
// 并移除pragma消息头,因为pragma也是控制缓存的一个消息头属性
//关于Pragma:no-cache,跟Cache-Control: no-cache相同。
// Pragma: no-cache兼容http 1.0 ,Cache-Control: no-cache是http 1.1提供的。
// 因此,Pragma: no-cache可以应用到http 1.0 和http 1.1,
// 而Cache-Control: no-cache只能应用于http 1.1.
//一般用于访问量大的接口并且不会实时改变的接口,列表页,拼多多,60s,
request = request.builder()
.removeRequestHeader("pragma")
.addRequestHeader("Cache-Control", "max-age=60")
.build();
String json = CacheTemp.cacheMap.get(request.getUrl());
if(!TextUtils.isEmpty(json)){
Response2 response2 = new Response2();
ResponseBody body = new ResponseBody();
body.setBodyString(json);
response2.setBody(body);
return response2;
}
//只有Get请求才能去拿缓存数据
if(request.getRequestMethod().equals("GET")) {
CacheTemp.isCache = false;
}else{
CacheTemp.isCache = false;
}
return chain.getResponse(request);
}
}
这里用到了缓存辅助类 CacheTemp
CacheTemp.class
public class CacheTemp {
public static boolean isCache;
public static Map cacheMap=new HashMap<>();
}
我这里就没有像OkHttp那样写了个SD卡文件流缓存。毕竟阉割版,当然读者这里可以吧 磁盘缓存,软缓存都用上。
6.4 连接+读写拦截器
CallServerInteceptor.class
public class CallServerInteceptor implements Interceptor2 {
@Override
public Response2 doNext(Chain2 chain2){
Response2 response2 = new Response2();
try {
SocketRequestServer srs = new SocketRequestServer();
Request2 request2 = chain2.getRequest();
Socket socket = new Socket(srs.getHost(request2), srs.getPort(request2));
// todo 请求
// output
OutputStream os = socket.getOutputStream();
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(os));
String requestAll = srs.getRequestHeaderAll(request2);
// Log.d(TAG, "requestAll:" + requestAll);
System.out.println("requestAll:" + requestAll);
bufferedWriter.write(requestAll); // 给服务器发送请求 --- 请求头信息 所有的
bufferedWriter.flush(); // 真正的写出去...
// todo 响应
//final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
InputStream is = socket.getInputStream();
HttpCodec httpCodec = new HttpCodec();
//读一行 响应行
String responseLine = httpCodec.readLine(is);
System.out.println("响应行:" + responseLine);
//读响应头
Map headers = httpCodec.readHeaders(is);
for (Map.Entry entry : headers.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
if (headers.containsKey("Location")) {//Location就代表这个请求是重定向的
request2.setRedictUrl(headers.get("Location"));
}
//读响应体 ? 需要区分是以 Content-Length 还是以 Chunked分块编码
if (headers.containsKey("Content-Length")) {
int length = Integer.valueOf(headers.get("Content-Length"));
byte[] bytes = httpCodec.readBytes(is, length);
String content = new String(bytes);
System.out.println("响应:" + content);
if (request2.isCache()) {
CacheTemp.cacheMap.put(request2.getUrl(), content);
}
ResponseBody responseBody = new ResponseBody();
//responseBody.setInputStream(is);
responseBody.setContentLength(length);
responseBody.setBytes(bytes);
response2.setBody(responseBody);
//response2.setBody(new ResponseBody().setBodyString(content.replaceAll("\\r\\n", "")));
//mHandler.sendEmptyMessage(1);
} else {
//分块编码 chunk 分块返回数据,耗时返回的链接可以快速返回
String response = httpCodec.readChunked(is);
if (CacheTemp.isCache) {
CacheTemp.cacheMap.put(request2.getUrl(), response);
}
response2.setBody(new ResponseBody().setBodyString(response.replaceAll("\\r\\n", "")));
System.out.println("响应:" + response);
}
is.close();
socket.close();
//response2 = chain2.getResponse(request2); // 执行下一个拦截器(任务节点)
}catch (Exception e){
e.printStackTrace();
}
// response2.setBody("流程走通....");
return response2;
}
}
我这里就没有完全像OkHttp那样,这里就偷点懒吧连接拦截器和读写拦截器写在一起了。到这里所有的拦截器实现已经全部写完了。接下来就开始组装拦截器了。
7、使用拦截器
依然用这张流程图,我们看到使用拦截器是在Dispatcher后才使用的,而真正同步异步是在RealCall触发的,也就是说,在RealCall就要把拦截器组装好。那么最终的RealCall代码。
RealCall2.class
public class RealCall2 implements Call2{
private OkHttpClient2 okHttpClient2;
private Request2 request2;
//包工头执行流程
public OkHttpClient2 getOkHttpClient2(){
return okHttpClient2;
}
public RealCall2(OkHttpClient2 okHttpClient2, Request2 request2) {
this.okHttpClient2 = okHttpClient2;
this.request2 = request2;
}
@Override
public void enqueue(Callback2 callback2) {
//准备要干事情的地方
//分发出去
okHttpClient2.dispatcher().enqueue(new AsyncCall2(callback2));
}
class AsyncCall2 implements Runnable{
private Callback2 callback2;
public AsyncCall2(Callback2 callback) {
this.callback2 = callback;
}
Request2 getRequest(){
return RealCall2.this.request2;
}
@Override
public void run() {
//这里才是真正开始干活的地方,就要吊起责任链
//callback2返回结果,要么成功要么失败
try {
//1、得到责任链
Response2 response2 = getResponseChain();
callback2.onResponse(RealCall2.this, response2);
}catch (Exception e){
callback2.onFailure(RealCall2.this, new IOException("OKHTTP getResponseWithInterceptorChain 错误... e:" + e.toString()));
}finally {
okHttpClient2.dispatcher().finished(this);
}
}
private Response2 getResponseChain() {
//2、在链里面添加元素,即拦截器
List interceptor2s = new ArrayList<>();
interceptor2s.add(new BridgeInterceptor());
interceptor2s.add(new RetryAndFollowInterceptor());
interceptor2s.add(new CacheInterceptor());
//这里添加自己的拦截器
interceptor2s.addAll(okHttpClient2.getMyInterceptors());
interceptor2s.add(new CallServerInteceptor());
ChainManager chainManager = new ChainManager(0,RealCall2.this,request2,interceptor2s);
return chainManager.getResponse(request2);
}
}
}
到这里,手写OkHttp框架快要接近尾声了,我们先跑一遍,看下效果如何。
看到这结果,还是翻车了哇,我们来分析一波,日志里面报的是302,响应头 Location 里面也有对应的属性,这无非就是典型的被重定向了。但是仔细看了下,却发现重定向的地址,和请求的地址完全一样。这就有点奇怪了,将这个地址拿到浏览器试试。
在浏览器里面发现这根本就没有重定向操作。所以根本原因根本就不是因为重定向而导致的问题。 那会不会是没有适配Https的网络请求? 我们都知道Http和Https之间是有一层SSL证书,而我们连接拦截器里面根本就没有响应的处理,而是直接连接Socket。于是乎
//http的创建socket直接new,而我们https的socket
// Socket socket = new Socket(srs.getHost(request2), srs.getPort(request2));
//https和http
Socket socket = null;
if(request2.getUrl().startsWith("https://")){
//获得一个ssl上下文
SSLContext sslContext = SSLContext.getInstance("TLS");
//信任本机所有证书
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
//初始化证书
trustManagerFactory.init((KeyStore) null);
//信任证书设置
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
//证书管理器初始化
sslContext.init(null, trustManagers, null);
//由sslContext得到SSLSocket工厂
SSLSocketFactory socketFactory = sslContext.getSocketFactory();
//创建socket
socket = socketFactory.createSocket();
socket.connect(new InetSocketAddress(srs.getHost(request2), srs.getPort(request2)));
}else {
socket = new Socket(srs.getHost(request2), srs.getPort(request2));
}
我在连接拦截器里面加了 这个判断,如果请求为https的话,将会自动加上证书认证,http请求的话,就直接初始化socket。接着在运行试试。
到这里,一个阉割版的OkHttp框架结束了。相信看到这里的小伙伴,再结合前俩篇文章,应该能完全吃透OkHttp源码了。
8、Demo地址 点我下载
视频教程:Android百大框架源码解析Retrofit/OkHttp/Glide/RxJava/EventBus...._哔哩哔哩_bilibili:https://www.bilibili.com/video/BV1mT4y1R7F4?spm_id_from=333.999.0.0
本文转自 https://juejin.cn/post/7015980958788616199,如有侵权,请联系删除。