Android 架构之OkHttp源码解读(下)

1,340

小知识,大挑战!本文正在参与“  程序员必备小知识  ”创作活动

本文同时参与 「掘力星计划」  ,赢取创作大礼包,挑战创作激励金

前言

在前两篇中主要讲解了OkHttp源码解析,在本篇中,将会结合前两篇所有的知识点,从零开始手写一份阉割版的OkHttp框架。因此,读者也可以按照本章的方式从零开始一步一步手仿造出OkHttp框架。

在开始之前,我们先整理一下,需要按照什么样的步骤才能仿造一个阉割版的OkHttp。

  1. 依葫芦画瓢,先创造身体,复制一份,再注入灵魂
  2. 创造Request 对象,再造 Response 对象
  3. 流程图:分发器、责任链、拦截器
  4. 分发器:执行队列、等待队列、线程池、逻辑判断、线程结束
  5. 拦截器:对应拦截器的职责干什么 (专一,只做自己的事)
  6. 责任链模式:肯定有一个chain接口和其他实现类,遵循对类隐藏,对接口暴露
  7. 辅助类完成

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 ,顾名思义,这个类主要封装的请求相关的信息,既然是请求相关的信息,我们先整理下,请求接口时,需要哪些东西?

  1. 请求方式 String requestMethod
  2. 请求地址 String url
  3. 请求头 Map<String, String> mHeaderList
  4. 请求体 RequestBody2 requestBody2
  5. 重定向地址 String redictUrl
  6. 是否缓存等等

这里 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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 到底做了些什么事。

图片6.png

如图所示 OkHttpClient2 通过 newCall方法 返回对应Call的实现类对象 RealCall,然后再通过 RealCall 分别调用 Dispatcher对象的 execute 和 enqueue方法,接着依次调用对应的拦截器,最终产生 Response对象。在上一篇讲解拦截器的时候,除了系统的五大拦截器,开发者可以自定义拦截器,这里再贴一下源码。

  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> 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<Interceptor2> 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<Interceptor2> 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<Interceptor2> 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第一篇中,主要讲解了分发器,以及双管队列是如何实现的,这里不多叙述,直接上图。

图片5.png

所图所示

在Dispatcher中,里面是有俩个队列,于是乎。

public class Dispatcher2 {

    private int maxRequests = 64;//同时访问任务,不同域名最大限制64个
    private int maxRequestsPerHost = 5;//同时访问同一个域名服务器,最大限制5个
    //真正执行者是call(包工头),call然后交给拦截器去执行具体任务
    //RealCall2
    private Deque<RealCall2.AsyncCall2> runningAsyncCalls = new ArrayDeque<>();
    //等待的队列
    private Deque<RealCall2.AsyncCall2> readyAsyncCalls = new ArrayDeque<>();

    //同步方案

    //异步方案
    public void enqueue(RealCall2.AsyncCall2 call){
        //小于64个,同一域名请求小于5个
        if(runningAsyncCalls.size()<maxRequests && runningCallsForHost(call)<maxRequestsPerHost){
            runningAsyncCalls.add(call);
            //创建一个线程,从线程池找
            executorService().execute(call);
        }else{
            readyAsyncCalls.add(call);
        }
    }

    //计算当前域名有没有超过5个
    private int runningCallsForHost(RealCall2.AsyncCall2 call) {
        int count = 0;
        if(runningAsyncCalls.isEmpty()){
            return 0;
        }
        SocketRequestServer srs = new SocketRequestServer();
        for(RealCall2.AsyncCall2 runningAsyncCall:runningAsyncCalls){
            if(srs.getHost(runningAsyncCall.getRequest()).equals(call.getRequest())){
                count++;
            }
        }
        return count;
    }

    //在线程池中获取线程
    public ExecutorService executorService(){
        ExecutorService service = ThreadPoolManager.getInstance().getExecutor();
        return service;
    }



    /*
      //1个okhttp请求结束
     * 1.移除运行完成的任务
    * 2.把等待队列里面所有的任务取出来【执行】  AsyncCall2.run finished
    * @param call2
    */
    public void finished(RealCall2.AsyncCall2 call2){
        runningAsyncCalls.remove(call2);
        //如果准备中的任务为空,直接返回
        if(readyAsyncCalls.isEmpty()){
            return;
        }
        for(RealCall2.AsyncCall2 readyAsyncCall:readyAsyncCalls){
            readyAsyncCalls.remove(readyAsyncCall);
            runningAsyncCalls.add(readyAsyncCall);
            //开始执行任务
            executorService().execute(readyAsyncCall);
        }
    }
}

这里用了两个辅助类,里面没啥逻辑而言,直接贴出来

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<String,String> mapList = request2.getmHeaderList();
            for (Map.Entry<String, String> 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<Runnable>(),然后maximumPoolSize,但是它的数量是包含了corePoolSize的
                60L, //表示的是maximumPoolSize当中等待任务的存活时间
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(), //缓冲队列,用于存放等待任务,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<Interceptor2> interceptors;//我的节点必须要实现Interceptor2,才是责任链里面的一员

    public Call2 getCall(){
        return call;
    }

    public ChainManager(int index, Call2 call, Request2 request2, List<Interceptor2> 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<String,String> 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<String,String> 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<String, String> headers = httpCodec.readHeaders(is);
            for (Map.Entry<String, String> 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、使用拦截器

图片6.png

依然用这张流程图,我们看到使用拦截器是在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<Interceptor2> 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框架快要接近尾声了,我们先跑一遍,看下效果如何。

QQ截图20211006231400.png

看到这结果,还是翻车了哇,我们来分析一波,日志里面报的是302,响应头 Location 里面也有对应的属性,这无非就是典型的被重定向了。但是仔细看了下,却发现重定向的地址,和请求的地址完全一样。这就有点奇怪了,将这个地址拿到浏览器试试。

QQ截图20211006231949.png

在浏览器里面发现这根本就没有重定向操作。所以根本原因根本就不是因为重定向而导致的问题。 那会不会是没有适配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。接着在运行试试。

QQ截图20211006232907.png

到这里,一个阉割版的OkHttp框架结束了。相信看到这里的小伙伴,再结合前俩篇文章,应该能完全吃透OkHttp源码了。

8、Demo地址 点我下载

欢迎在评论区讨论,掘金官方将在掘力星计划活动结束后,在评论区抽送100份掘金周边