美文网首页
OkHttp基本使用和流程浅析

OkHttp基本使用和流程浅析

作者: HuiRan | 来源:发表于2020-05-27 16:15 被阅读0次

    一、简介

    OkHttp作为目前android开发最主流的最流行的网络请求框架
    有以下优点:

    • 允许连接到同一个主机地址的所有请求共享Socket,提高请求效率 ,减少对服务器的请求次数
    • 通过连接池,减少了请求延迟
    • 缓存响应数据来减少重复的网络请求
    • 减少了对数据流量的消耗
    • 自动处理GZip压缩

    二、基本使用

    创建OkHttpClient对象

    private OkHttpClient client() {
            if (client == null) {
                HttpsUtils.SSLParams sslParams = HttpsUtils.getSslSocketFactory();
                client = new OkHttpClient.Builder()
                        .readTimeout(10L, TimeUnit.SECONDS)
                        .writeTimeout(10L, TimeUnit.SECONDS)
                        .connectTimeout(10L, TimeUnit.SECONDS)
                        //cookie持久化存储
                        .cookieJar(new CookieJarImpl(new PersistentCookieStore(this)))
                        //https请求主机名验证,该实现默认返回true
                        .hostnameVerifier(HttpsUtils.UnSafeHostnameVerifier)
                        //https请求ssl证书信任,该实现默认信任所有证书
                        .sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager)
                        //添加自定义拦截器(可自定义拦截器添加公共参数,公共请求头信息等)
                        //.addInterceptor()
                        .build();
            }
            return client;
        }
    

    GET请求

    1、创建HttpUrl对象

    • 添加参数
    HttpUrl httpUrl = HttpUrl.get(url)
                    .newBuilder()
                    //添加query参数
                    .addQueryParameter("id", "10")
                    .build();
    
    • 无参数
    HttpUrl httpUrl = HttpUrl.get(url);
    

    2、创建Request对象

    Request request = new Request.Builder()
                    .url(httpUrl)
                    //添加请求头(若该请求有请求头)
                    .addHeader("Connection", "Keep-Alive")
                    .get()
                    .build();
    

    3、获取Call对象

    Call call = client().newCall(request);
    
    异步GET请求

    执行enqueue方法

            call.enqueue(new Callback() {
                @Override
                public void onFailure(@NonNull Call call, @NonNull IOException e) {
                    //请求失败
                    Log.e(TAG, "onFailure: e=" + e.toString());
                }
    
                @Override
                public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
                    //请求成功,获取到响应信息Response
                    ResponseBody body = response.body();
                    if (body != null) {
                        String data = body.string();
                        Log.d(TAG, "onResponse: data=" + data);
                    }
                }
            });
    
    同步GET请求

    执行execute方法;
    注意:同步请求不应在主线程执行,应在后台线程中执行,网络请求会阻塞当前主线程,可能会造成应用anr

            try {
                Response response = call.execute();
                //请求成功,获取到响应信息Response
                ResponseBody body = response.body();
                if (body != null) {
                    String data = body.string();
                    Log.d(TAG, "onResponse: data=" + data);
                }
            } catch (IOException e) {
                e.printStackTrace();
                //请求失败
                Log.e(TAG, "onFailure: e=" + e.toString());
            }
    

    POST请求

    1、创建RequestBody对象

    • 表单提交键值对参数
    FormBody requestBody = new FormBody.Builder()
                    //添加id参数
                    .add("id", "10")
                    .build();
    
    • 提交json参数
            Map<String,String> map = new HashMap<>(1);
            //添加id参数
            map.put("id", "10");
            String json = new Gson().toJson(map);
            RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=UTF-8"), json);
    
    • 提交id和上传图片
            File file = new File(getCacheDir().getAbsolutePath() + File.separator + "123.png");
            MultipartBody requestBody = new MultipartBody.Builder()
                    //设置为表单提交
                    .setType(MultipartBody.FORM)
                    //添加id参数
                    .addFormDataPart("id", "10")
                    //添加图片
                    .addFormDataPart("fileKey", file.getName(), RequestBody.create(MediaType.parse("image/png"), file))
                    .build();
    

    2、创建Request对象

    Request request = new Request.Builder()
                    .url(url)
                    //添加请求头(若该请求有请求头)
                    .addHeader("Connection", "Keep-Alive")
                    .get()
                    .build();
    

    3、获取Call对象

    Call call = client().newCall(request);
    
    异步POST请求

    执行enqueue方法

            call.enqueue(new Callback() {
                @Override
                public void onFailure(@NonNull Call call, @NonNull IOException e) {
                    //请求失败
                    Log.e(TAG, "onFailure: e=" + e.toString());
                }
    
                @Override
                public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
                    //请求成功,获取到响应信息Response
                    ResponseBody body = response.body();
                    if (body != null) {
                        String data = body.string();
                        Log.d(TAG, "onResponse: data=" + data);
                    }
                }
            });
    
    同步POST请求

    执行execute方法;
    注意:同步请求不应在主线程执行,应在后台线程中执行,网络请求会阻塞当前主线程,可能会造成应用anr

            try {
                Response response = call.execute();
                //请求成功,获取到响应信息Response
                ResponseBody body = response.body();
                if (body != null) {
                    String data = body.string();
                    Log.d(TAG, "onResponse: data=" + data);
                }
            } catch (IOException e) {
                e.printStackTrace();
                //请求失败
                Log.e(TAG, "onFailure: e=" + e.toString());
            }
    

    三、流程分析

    由以上使用方式我们可以发现网络请求是由Call发起的,异步请求是通过enqueue方法发起,同步请求是通过execute方法,而Call是一个接口,我们需要找到Call的实现类,首先我们我们看下Call的创建如下:

    /**
       * Prepares the {@code request} to be executed at some point in the future.
       */
      @Override public Call newCall(Request request) {
        return RealCall.newRealCall(this, request, false /* for web socket */);
      }
    

    发现该方法再次调用了RealCall的静态方法,查看如下:

      static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
        // Safely publish the Call instance to the EventListener.
        RealCall call = new RealCall(client, originalRequest, forWebSocket);
        call.eventListener = client.eventListenerFactory().create(call);
        return call;
      }
    

    由此可以发现Call接口的实现类是RealCall对象。

    RealCall

      private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
        this.client = client;
        this.originalRequest = originalRequest;
        this.forWebSocket = forWebSocket;
        this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
      }
    

    由构造方法可以看出RealCall对象中持有了OkHttpClient对象和Request对象,并且创建了一个RetryAndFollowUpInterceptor拦截器对象

    • 异步请求enqueue
      @Override public void enqueue(Callback responseCallback) {
        synchronized (this) {
          //判断该RealCall对象是否被执行过,若被执行过则抛出异常,未被执行过则标记为已执行过
          if (executed) throw new IllegalStateException("Already Executed");
          executed = true;
        }
        captureCallStackTrace();
        eventListener.callStart(this);
        //通过OkHttpClient对象中的dispatcher把AsyncCall对象添加到队列
        client.dispatcher().enqueue(new AsyncCall(responseCallback));
      }
    

    由上可看出AsyncCall被添加到OkHttpClient对象中的dispatcher中(dispatcher我们稍后在分析),我们在看下AsyncCall:

    final class AsyncCall extends NamedRunnable {
        //省略若干代码
      }
    

    AsyncCall继承于NamedRunnable ,我们在看下NamedRunnable:

    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();
    }
    

    由此我们可以看出AsyncCall其实实现了Runnable接口,我们只需关注AsyncCall中的execute方法逻辑,故我们可以得出结论,AsyncCall被OkHttpClient中的dispatcher放入到线程当中去执行,我们在看下AsyncCall的execute方法:

      final class AsyncCall extends NamedRunnable {
        private final Callback responseCallback;
    
        AsyncCall(Callback responseCallback) {
          super("OkHttp %s", redactedUrl());
          this.responseCallback = responseCallback;
        }
       //省略若干代码...
    
        @Override protected void execute() {
          boolean signalledCallback = false;
          try {
            //获取网络请求响应结果信息
            Response response = getResponseWithInterceptorChain();
            if (retryAndFollowUpInterceptor.isCanceled()) {
              signalledCallback = true;
              //若该任务被取消直接回调失败
              responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
            } else {
              signalledCallback = true;
              //回调成功,并把响应结果信息回调出去
              responseCallback.onResponse(RealCall.this, response);
            }
          } catch (IOException e) {
            if (signalledCallback) {
              // Do not signal the callback twice!
              Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
            } else {
              eventListener.callFailed(RealCall.this, e);
              //网络请求出现异常,回调失败
              responseCallback.onFailure(RealCall.this, e);
            }
          } finally {
            //请求成功或者失败通过OkHttpClient对象中的dispatcher销毁AsyncCall对象
            client.dispatcher().finished(this);
          }
        }
      }
    

    AsyncCall的execute方法中会调用RealCall的getResponseWithInterceptorChain方法获取网络请求的响应结果信息,并回调出去,并且在请求成功或者失败都会通过OkHttpClient对象中的dispatcher销毁AsyncCall对象

    • 同步请求execute
      @Override public Response execute() throws IOException {
        synchronized (this) {
           //判断该RealCall对象是否被执行过,若被执行过则抛出异常,未被执行过则标记为已执行过
          if (executed) throw new IllegalStateException("Already Executed");
          executed = true;
        }
        captureCallStackTrace();
        eventListener.callStart(this);
        try {
          //通过OkHttpClient对象中的dispatcher把RealCall对象添加到队列
          client.dispatcher().executed(this);
          //获取网络请求响应结果信息
          Response result = getResponseWithInterceptorChain();
          if (result == null) throw new IOException("Canceled");
          return result;
        } catch (IOException e) {
          eventListener.callFailed(this, e);
          throw e;
        } finally {
          //请求成功或者失败通过OkHttpClient对象中的dispatcher销毁RealCall对象
          client.dispatcher().finished(this);
        }
      }
    

    由上可看出RealCall被添加到OkHttpClient对象中的dispatcher中(dispatcher我们稍后在分析),然后执行了getResponseWithInterceptorChain方法获取网络请求响应结果信息,我们看下getResponseWithInterceptorChain:

      Response getResponseWithInterceptorChain() throws IOException {
        // Build a full stack of interceptors.
        List<Interceptor> interceptors = new ArrayList<>();
        //添加OkHttpClient中的自定义拦截器
        interceptors.addAll(client.interceptors());
        //添加当前对象RealCall中创建的重试和重定向拦截器
        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);
      }
    

    可以看似请求响应信息是通过RealInterceptorChain的proceed方法返回的,RealInterceptorChain实现了Interceptor.Chain接口

    @Override public Response proceed(Request request) throws IOException {
        return proceed(request, streamAllocation, httpCodec, connection);
      }
    
      public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
          RealConnection connection) throws IOException {
        if (index >= interceptors.size()) throw new AssertionError();
        //省略若干代码....
    
        // Call the next interceptor in the chain.
        //创建下一个RealInterceptorChain对象(index+1)
        RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
            connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
            writeTimeout);
        //获取当前拦截器
        Interceptor interceptor = interceptors.get(index);
        //当前拦截器执行intercept方法传入下一个RealInterceptorChain对象
        Response response = interceptor.intercept(next);
        //省略若干代码....
        return response;
      }
    

    可以看出该方法基于责任链模式,interceptor.intercept(Chain)的实现中只需要执行Chain.proceed(Request)方法,就可以执行到下一个拦截器的intercept方法,如此层层传递:

    public final class ConnectInterceptor implements Interceptor {
       //省略若干代码....
      @Override public Response intercept(Chain chain) throws IOException {
        RealInterceptorChain realChain = (RealInterceptorChain) chain;
        Request request = realChain.request();
        //省略若干代码....
        return realChain.proceed(request, streamAllocation, httpCodec, connection);
      }
    }
    

    此拦截器是OkHttp内置的一个拦截器,可以看出内部执行proceed方法执行下一个拦截器的intercept方法,通过最后一个拦截器CallServerInterceptor读取到请求的响应结果信息后从下往上传递,最终getResponseWithInterceptorChain方法获取到Response

    Dispatcher

    接下来我们看下Dispatcher这个类:

    public final class Dispatcher {
      //允许的最大请求数64,该值可以自定义(一般使用不会修改该值)
      private int maxRequests = 64;
      //允许的每个主机并发执行的最大请求数5,该值可以自定义(一般使用不会修改该值)
      private int maxRequestsPerHost = 5;
      //省略若干代码...
    
       //线程池
      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<>();
    }
    

    该类内部主要维护了一个线程池和两个异步任务队列和一个同步任务队列,我们先看下该线程池的创建:

      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;
      }
    

    这里描述下线程池创建各个参数的含义:

    • corePoolSize:线程池中核心线程数量
    • maximumPoolSize:线程池中线程总数最大值=核心线程数+非核心线程数
    • keepAliveTime:非核心线程闲置超时时长
    • unit:keepAliveTime的单位
    • workQueue:线程池中的任务队列
      常见的workQueue有四种:
      SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现<线程数达到了maximumPoolSize而不能新建线程>的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大。
      LinkedBlockingQueue:这个队列接收到任务的时候,如果当前已经创建的核心线程数小于线程池的核心线程数上限,则新建线程(核心线程)处理任务;如果当前已经创建的核心线程数等于核心线程数上限,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize
      ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误,或是执行实现定义好的饱和策略。
      DelayQueue:队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务。
    • threadFactory:创建线程的工厂,可以用线程工厂给每个创建出来的线程设置名字等信息

    由此我们可以看出该线程池的策略是当接收到任务时,判断有无空闲线程,没有就新建一个线程去执行任务,线程空闲超过60s后即被销毁(注意:由于Dispatcher中的maxRequests为64,所以线程池中的任务不可能超过maxRequests)

    异步任务在RealCall中会调用Dispatcher中的enqueue方法,我们先看下Dispatcher中的enqueue方法:

    synchronized void enqueue(AsyncCall call) {
        //判断当前正在执行的异步任务队列数量是否小于maxRequests且该任务的主机下正在执行的任务数量是否小于maxRequestsPerHost
        if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
          //满足条件把该任务添加到正在执行的异步任务队列中
          runningAsyncCalls.add(call);
          //把该任务放到线程池当中去执行
          executorService().execute(call);
        } else {
          //不满足条件把该任务添加到准备执行的异步任务队列中
          readyAsyncCalls.add(call);
        }
      }
    

    故得出结论线程池当中的任务最多不会超过maxRequests,且AsyncCall 若被放到线程池当中执行,最终会执行AsyncCall中的execute方法,该方法会执行RealCall中的getResponseWithInterceptorChain获取网络请求响应结果信息
    我们在看下runningCallsForHost方法是如何根据当前任务获取正在执行的同一个主机任务数量:

      /** Returns the number of running calls that share a host with {@code call}. */
      private int runningCallsForHost(AsyncCall call) {
        int result = 0;
        //遍历正在运行的异步任务队列
        for (AsyncCall c : runningAsyncCalls) {
          //webSocket我们不关注,忽略
          if (c.get().forWebSocket) continue;
          //队列中的任务主机名等于当前任务的主机,result++
          if (c.host().equals(call.host())) result++;
        }
        //返回同一主机名下的正在执行的任务数量
        return result;
      }
    

    同步任务在RealCall中会调用Dispatcher中的executed方法,我们看下Dispatcher中的executed方法:

      /** Used by {@code Call#execute} to signal it is in-flight. */
      synchronized void executed(RealCall call) {
        runningSyncCalls.add(call);
      }
    

    此方法只是把RealCall对象添加到正在执行的同步任务队列中

    上面描述RealCall的时候说到执行getResponseWithInterceptorChain之后无论同步或异步任务均会调用Dispatcher的销毁方法finish,我们看下同步异步任务的finish方法:

      //异步任务销毁finish方法
      void finished(AsyncCall call) {
        //传入正在执行的异步任务队列,AsyncCall任务对象,promoteCalls为true
        finished(runningAsyncCalls, call, true);
      }
    
      //同步任务销毁finish方法
      void finished(RealCall call) {
        //传入正在执行的同步任务队列,RealCall任务对象,promoteCalls为false
        finished(runningSyncCalls, call, false);
      }
    
      private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
        //省略若干代码...
        synchronized (this) {
          //队列中移除call任务对象
          if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
          //若销毁的是异步任务执行promoteCalls方法
          if (promoteCalls) promoteCalls();
          //省略若干代码...
        }
    
        //省略若干代码...
      }
    

    可以看到同步和异步销毁方法最终都是执行finished三个参数的方法,区别是异步任务还会执行promoteCalls:

      private void promoteCalls() {
        //若正在执行的异步任务数量大于等于maxRequests(64),return
        if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
        //若准备执行的异步任务队列为空(也就是没有准备执行的异步任务了),return
        if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
        //遍历准备执行的异步任务队列
        for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
          AsyncCall call = i.next();
          //根据当前任务获取正在执行的同一个主机任务数量,小于maxRequestsPerHost(5)则满足条件
          if (runningCallsForHost(call) < maxRequestsPerHost) {
            //把该异步任务从准备执行的异步任务队列中移除
            i.remove();
            //把该异步任务添加到正在执行的异步任务队列
            runningAsyncCalls.add(call);
            //并把该异步任务放到线程池当中去执行
            executorService().execute(call);
          }
          // 若正在执行的异步任务数量大于等于maxRequests(64),跳出循环
          if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
        }
      }
    

    从以上可以看出,promoteCalls方法主要是把准备执行的异步任务队列中的异步任务移除,同时添加到正在执行的异步任务队列,并把该任务放入线程池当中去执行,结合finished方法来看,就是每当一个异步任务执行完毕后,就会调用promoteCalls方法,满足条件的情况下,就去执行还未执行被放入准备执行的异步任务队列中的任务

    本篇文章到此已经结束,主要是介绍了OkHttp的简单用法和简单的分析了工作流程,具体的网络连接,数据的写入和读取,都在OkHttp几个内置的拦截器当中,后续有时间在分析这几个拦截器。

    相关文章

      网友评论

          本文标题:OkHttp基本使用和流程浅析

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