美文网首页
OkHttp讲解(一)

OkHttp讲解(一)

作者: 涛涛123759 | 来源:发表于2020-06-12 11:40 被阅读0次

    OkHttp讲解(一)
    OkHttp讲解(二)
    OkHttp讲解(三)

    一、OKHTTP使用

    1、GET请求
      OkHttpClient client = new OkHttpClient();
    
      Request request = new Request.Builder()
          .url(url)
          .build();
    
      Response response = client.newCall(request).execute();
      return response.body().string();
    
    2、POST请求
       public static final MediaType JSON
        = MediaType.parse("application/json; charset=utf-8");
    
      OkHttpClient client = new OkHttpClient();
    
      RequestBody body = RequestBody.create(JSON, json);
    
      Request request = new Request.Builder()
          .url(url)
          .post(body)
          .build();
    
      Response response = client.newCall(request).execute();
    
      return response.body().string();
    

    二、Request、Response类详解

    • 1、Request、Response分别抽象成请求和相应。
    • 2、其中Request包括Headers和RequestBody,而RequestBody是abstract类,他的子类是有FormBody (表单提交的)和 MultipartBody(文件上传),分别对应了两种不同的MIME类型
      FormBody :"application/x-www-form-urlencoded"
      MultipartBody:"multipart/"+xxx.
    • 3、其中Response包括Headers和RequestBody,而ResponseBody是abstract类,所以他的子类也是有两个:RealResponseBody和CacheResponseBody,分别代表真实响应和缓存响应。
    • 4、由于RFC协议规定,所以所有的头部信息不是随便写的,request的header与response的header的标准都不同。OKHttp的封装类Request和Response为了应用程序编程方便,会把一些常用的Header信息专门提取出来,作为局部变量。比如contentType,contentLength,code,message,cacheControl,tag...它们其实都是以name-value对的形势,存储在网络请求的头部信息中。

    三、Call类详解

    interface Factory {
        Call newCall(Request request);
      }
    

    OKHttpClient实现了Call.Factory接口,通过调用newCall(),创建并返回了一个RealCall对象。

    四、RealCall类详解

    • 1、OkHttpClient的newCall方法里面new了RealCall的对象,但是RealCall的构造函数需要传入一个OKHttpClient对象和Request对象(PS:第三个参数false表示不是webSokcet).因此RealCall包装了Request对象。所以RealCall可以很方便地使用这两个对象。
    • 2、RealCall里面的两个关键方法是:execute 和 enqueue。分别用于同步和异步得执行网络请求。
      2.1、同步请求
      @Override public Response execute() throws IOException {
        //首先出现一个同步代码块,对当前对象加锁,通过一个标志位executed判断该对象的execute
        //方法是否已经执行过,如果执行过就抛出异常;这也就是同一个Call只能执行一次的原因
        synchronized (this) {
          if (executed) throw new IllegalStateException("Already Executed");
          executed = true;
        }
        //这个是用来捕获okhttp的请求堆栈信息
        captureCallStackTrace();
        try {
          //调用Dispatcher的executed方法,将请求放入分发器
          client.dispatcher().executed(this);
          //通过拦截器连获取返回结果Response
          Response result = getResponseWithInterceptorChain();
          if (result == null) throw new IOException("Canceled");
          return result;
        } finally {
          //调用dispatcher的finished方法,回收同步请求
          client.dispatcher().finished(this);
        }
      }
    

    2.2、异步请求
    首先也是加同步,判断这个Call是否执行过;然后实例化了一个AsyncCall,最后调用分发器的enqueue方法

      @Override 
      public void enqueue(Callback responseCallback) {
        synchronized (this) {
          if (executed) throw new IllegalStateException("Already Executed");
          executed = true;
        }
        captureCallStackTrace();
          //实例化一个线程AsyncCall交给分发器,由分发器中的线程池执行这个线程
        client.dispatcher().enqueue(new AsyncCall(responseCallback));
      }
      ...
      //异步请求线程
      final class AsyncCall extends NamedRunnable {
        private final Callback responseCallback;
    
        AsyncCall(Callback responseCallback) {
          super("OkHttp %s", redactedUrl());
          this.responseCallback = responseCallback;
        }
    
        //主机名
        String host() {
          return originalRequest.url().host();
        }
    
        Request request() {
          return originalRequest;
        }
    
        RealCall get() {
          return RealCall.this;
        }
    
        //当分发器的线程池执行该对象时,该方法被调用
        @Override 
        protected void execute() {
          //保证onFailure只被回调一次
          boolean signalledCallback = false;
          try {
            //通过拦截器获取返回结果
            Response response = getResponseWithInterceptorChain();
            if (retryAndFollowUpInterceptor.isCanceled()) {
              //如果请求被取消,回调onFailure
              signalledCallback = true;
              responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
            } else {
              // 正常情况,调用onResponse
              signalledCallback = true;
              responseCallback.onResponse(RealCall.this, response);
            }
          } catch (IOException e) {
            // 如果上面回调过,这里就不再进行回调,保证onFailure只会被调用一次
            if (signalledCallback) {
              Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
            } else {
              responseCallback.onFailure(RealCall.this, e);
            }
          } finally {
            //通知分发器请求结束,从队列中移除该请求
            client.dispatcher().finished(this);
          }
        }
      }
    

    NamedRunnable实现了Runnable接口,从这里可以看出AsyncCall确实是在子线程执行网络请求

    /**
     * Runnable implementation which always sets its thread name.
     */
    public abstract class NamedRunnable implements Runnable {
      protected final String name;
    
      public NamedRunnable(String format, Object... args) {
        this.name = Util.format(format, args);
      }
    
      @Override public final void run() {
        String oldName = Thread.currentThread().getName();
        Thread.currentThread().setName(name);
        try {
          execute();
        } finally {
          Thread.currentThread().setName(oldName);
        }
      }
    
      protected abstract void execute();
    }
    
    • 3、RealCall还有一个重要方法是:getResponseWithInterceptorChain,添加拦截器,通过拦截器可以将一个流式工作分解为可配置的分段流程,既增加了灵活性也实现了解耦,关键还可以自有配置,非常完美。
    //依次执行拦截器链中的拦截器获取结果
      Response getResponseWithInterceptorChain() throws IOException {
        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());
        }
        //添加连接服务器拦截器,主要负责将我们的Http请求写进网络的IO流中
        interceptors.add(new CallServerInterceptor(forWebSocket));
    
        //构建拦截器链依次执行每一个拦截器
        Interceptor.Chain chain = new RealInterceptorChain(
            interceptors, null, null, null, 0, originalRequest);
        return chain.proceed(originalRequest);
      }
    

    RealCall的代码并不复杂,也没有做过多的操作,而且同步异步的请求在RealCall中的操作基本差不多,只不过同步请求是在当前线程执行,而异步请求是封装了一个子线程AsyncCall去执行请求。

    五、Dispatcher

    在OKHttp中,它使用Dispatcher作为任务的调度器,作用是为维护请求的状态,并维护一个线程池,用于执行请求。

      // 最大并发请求数为64
      private int maxRequests = 64;
     //每个主机最大请求数为5
      private int maxRequestsPerHost = 5;
      private @Nullable Runnable idleCallback;
      //线程池
      private @Nullable ExecutorService executorService;
     //就绪状态的异步请求队列
      private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
     //运行状态的异步请求队列
      private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
     //运行状态的同步请求队列
      private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
    
    /**创建线程池,并保证线程安全
    * 第一个 corePoolSize 一直存在于线程池中的线程数量(除非allowCoreThreadTimeOut为true),
    * 当是0,说明该线程池没有核心线程,所有线程都是工作线程,即所有线程超过一定空闲时间会被回收
    * 第二个参数是Integer.MAX_VALUE,即最大线程数,虽然设置值这么大,但是无须担心性能消耗过大问题,因为有队列去维护请求数
    * 第三个参数是60,即工作线程空闲60s后就会被回收
    * 第四个参数 unit 上面那时间的单位
     * 第五个参数 workQueue 通过execute方法发送的任务,会先被缓存在这个队列中
     * 第六个参数threadFactory 创建线程的工厂
    */
      public synchronized ExecutorService executorService() {
        if (executorService == null) {
          executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
              new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
        }
        return executorService;
      }
    
    /**
    发送异步请求
    此方法为同步方法,因为runningAsyncCalls和readyAsyncCalls使用的ArrayDeque,然而ArrayDeque是非线程安全的,所以需要同步。
    如果运行中的异步请求队列的请求数小于最大请求数且当前请求对应的host下对应的请求数小于maxRequestsPerHost,那么就进队列,并且通过线程池立即执行。
    */
    synchronized void enqueue(AsyncCall call) {
      // 运行队列中的请求数小于maxRequests且相同host的运行中请求数小于maxRequestsPerHost,下面会贴runningCallsForHost()的代码
      if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
        // 加入到运行中队列
        runningAsyncCalls.add(call);
        // 使用线程池执行请求,下面会贴出executorService初始化的过程。
        executorService().execute(call);
      } else {
        // 加入就绪队列中
        readyAsyncCalls.add(call);
      }
    }
    
      /*获取同一hostde的最大请求数 */
      private int runningCallsForHost(AsyncCall call) {
        int result = 0;
        for (AsyncCall c : runningAsyncCalls) {
          if (c.host().equals(call.host())) result++;
        }
        return result;
      }
    
    /**
      此方法也为同步方法
      直接加入到运行中同步请求队列中
    */
    synchronized void executed(RealCall call) {
      //加入到同步运行中队列
      runningSyncCalls.add(call);
    }
    

    调整请求(就绪/运行)

    /**
       根据maxRequests(最大请求数)和maxRequestsPerHost(相同host最大请求数)来调整
       runningAsyncCalls(运行中的异步请求)队列和readyAsyncCalls(就绪状态的异步请求)队列。
       使运行中的异步请求不超过两种最大值,并且如果队列有空闲,将就绪状态的请求归类为运行中。
    */
    private void promoteCalls() {
        //运行中的异步请求队列的请求数大于最大请求数,那么就没必要去将就绪状态的请求移动到运行中。
        //其实就是说,如果有超过最大请求数的请求正在运行,是不需要将其移出队列的,继续运行完即可。
        if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
        //如果就绪的队列为空,那就更没有必要移动了,因为都没有。
        if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
    
        //遍历就绪队列
        for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
    
          //取出一个请求
          AsyncCall call = i.next();
          //如果当前请求对应的host下,没有超过maxRequestsPerHost,那么将其从就绪队列中移除,并加入在运行队列。
          if (runningCallsForHost(call) < maxRequestsPerHost) {
            //移除
            i.remove();
            //加入运行队列
            runningAsyncCalls.add(call);
            //立即执行该请求
            executorService().execute(call);
          }
    
          //如果运行队列已经到达了最大请求数上限,如果还有就绪中的请求,也不管了。
          if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
        }
      }
    

    请求结束

    /**
        同步请求结束
        当该同步请求结束的时候,会调用此方法,用于将运行中的同步请求队列中的该请求移除
        今后系列中,分析RealCall的时候,会知道何时调用此方法的。
     */
    synchronized void finished(Call call) {
      if (!runningSyncCalls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
    }
    
    /**
        异步请求结束
        当该异步请求结束的时候,会调用此方法,用于将运行中的异步请求队列中的该请求移除并调整请求队列,此时就绪队列中的请求可以
    
        今后系列中,分析AsyncCall的时候,会知道何时调用此方法的。
     */
    synchronized void finished(AsyncCall call) {
      if (!runningAsyncCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");
      promoteCalls();
    }
    

    六、OKHTTP 执行过程

    OkHttpClient client = new OkHttpClient();
    
    String run(String url) throws IOException {
      Request request = new Request.Builder()
          .url(url)
          .build();
    
      Response response = client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
            }
            @Override
            public void onResponse(Call call, Response response) throws IOException {
            }
        });
    
    • RealCall.java
      @Override public void enqueue(Callback responseCallback) {
        synchronized (this) {
          if (executed) throw new IllegalStateException("Already Executed");
          executed = true;
        }
        captureCallStackTrace();
        client.dispatcher().enqueue(new AsyncCall(responseCallback));
      }
    
    • Dispatcher.java
      private int maxRequests = 64;
      private int maxRequestsPerHost = 5;
    
      synchronized void enqueue(AsyncCall call) {
        //判断是否满足入队的条件(立即执行)
         //如果正在执行的请求小于设定值即64,并且请求同一个主机的request小于设定值即5
        if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
          //正在运行的异步集合添加call
          runningAsyncCalls.add(call);
          //执行这个call
          executorService().execute(call);
        } else {
          //不满足入队(立即执行)条件,则添加到等待集合中
          readyAsyncCalls.add(call);
        }
      }
    
    • 大体流程图

    七、OKHTTP类详解

    • 大体核心类主要下图:


      核心类.png
    • 最后给大家看一下整体的流程图


      流程.png

    相关文章

      网友评论

          本文标题:OkHttp讲解(一)

          本文链接:https://www.haomeiwen.com/subject/uklgtktx.html