博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
常用轮子之Okhttp基本使用及原理
阅读量:5795 次
发布时间:2019-06-18

本文共 21671 字,大约阅读时间需要 72 分钟。

不忘初心 砥砺前行, Tomorrow Is Another Day !

相关文章

本文概要:

  1. 基本使用
  2. 基本原理

一. 基本使用

okhttp的请求和响应大多数采用建造者模式设计.

1. GET同步请求

public void syncGetRequest() {        String url = "http://api.k780.com/?app=weather.future&weaid=1&&appkey=10003" +                "&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json";        final OkHttpClient okHttpClient = new OkHttpClient();        final Request request = new Request.Builder()                .get()                .url(url)                .build();        new Thread(new Runnable() {            @Override            public void run() {                try {                    Response response = okHttpClient.newCall(request).execute();                    Log.d(TAG, "syncGetRequest: " + response.body().string());                } catch (IOException e) {                    e.printStackTrace();                }            }        }).star复制代码

2. GET异步请求

public void asyncGetRequest() {        String url = "http://api.k780.com/?app=weather.future&weaid=1&&appkey=10003" +                "&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json";        final OkHttpClient okHttpClient = new OkHttpClient();        Request request = new Request.Builder()                .get()                .url(url)                .build();        okHttpClient.newCall(request).enqueue(new Callback() {            @Override            public void onFailure(Call call, IOException e) {            }            @Override            public void onResponse(Call call, Response response) throws IOException {                Log.d(TAG, "Callback-Thread: " + Thread.currentThread());                String result = response.body().string();                Log.d(TAG, "asyncGetRequest: " + result);                //通过handler或者runOnUiThread方式切换线程.                runOnUiThread(new Runnable() {                    @Override                    public void run() {                        idtv.setText("jasonhww");                    }                });                /*                 *如果获取的是文件/图片                 */                //方式一:通过获取流                //InputStream inputStream = response.body().byteStream();                //方式二:通过获取字节数组                //byte[] bytes = response.body().bytes();            }        });    }复制代码

注意事项:

  • Callback的回调方法是在子线程执行的,如需更新UI,可通过runjOnUiThread或者handler切换到主线程.

3. POST提交表单

可以使用FormBody.Builder构建一个表单请求体

public void asyncPostForm(){        String url = "http://api.k780.com/";        final OkHttpClient okHttpClient = new OkHttpClient();        //构建表单请求体        RequestBody requestBody = new FormBody.Builder()                .add("app","weather.future")                .add("weaid","1")                .add("appkey","10003")                .add("sign","b59bc3ef6191eb9f747dd4e83c99f2a4")                .add("format","json")                .build();        //创建POST请求        Request request = new Request.Builder()                .url(url)                .post(requestBody)                .build();        okHttpClient.newCall(request).enqueue(new Callback() {            @Override            public void onFailure(Call call, IOException e) {            }            @Override            public void onResponse(Call call, Response response) throws IOException {                Log.d(TAG, "asyncPostForm: "+response.body().string());            }        });    }复制代码

4. POST提交JSON字符串

指定请求体的媒体类型为"application/json"

public void asyncJson() {        String url = "http://api.k780.com/?app=weather.future&weaid=1&&appkey=10003" +                "&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json";        String json = "{code:1,result:null}";        OkHttpClient okHttpClient = new OkHttpClient();        //指定媒体类型        MediaType mediaType = MediaType.parse("application/json,charset=utf-8");        RequestBody requestBody = RequestBody.create(mediaType, json);        Request request = new Request.Builder()                .url(url)                .post(requestBody)                .build();        okHttpClient.newCall(request).enqueue(new Callback() {            @Override            public void onFailure(Call call, IOException e) {            }            @Override            public void onResponse(Call call, Response response) throws IOException {                Log.d(TAG, "asyncJson: " + response.body().string());            }        });    }复制代码

5. 缓存

缓存使用相对比较简单,只需指定一下缓存目录及大小即可.

//伪代码 Cache cache = new Cache(cacheDirectory,cacheSize); OkHttpClient okHttpClient = new OkHttpClient.Builder()                      .cache(cache)                      .build();  复制代码

6. 拦截器

通过拦截器我们可以很方便的去修改请求和响应的相关信息,如修改请求头,请求体等.

  • 应用拦截器
    拦截应用层与okhttp之间的请求和响应
  • 网络拦截器
    拦截okhttp与网络层之间的请求和响应,此时网络连接已经建立
public class LoggingInterceptor implements Interceptor {    private static final String TAG = "LoggingInterceptor";    @Override    public Response intercept(Chain chain) throws IOException {        //获取请求        Request request = chain.request();        //打印url,连接状态,请求头        Log.d(TAG, "LoggingInterceptor: url = " + request.url() +                "\nconnectionStatus = " + chain.connection() +                "\nrequestHeaderInfo = " + request.headers());        //执行请求        Response response = chain.proceed(request);         //打印url,响应头        Log.d(TAG, "LoggingInterceptor: url = " + response.request() +                "\nresponseHeaderInfo = " + response.headers());        return response;    }}public class NetInterceptor implements Interceptor {    private static final String TAG = "LoggingInterceptor";    @Override    public Response intercept(Chain chain) throws IOException {            Request request = chain.request();        Response response = chain.proceed(request);        //演示设置响应头        CacheControl.Builder builder = new CacheControl.Builder()          .maxAge(10,TimeUnit.MINUTES);        return response.newBuilder()                        .header("Cache-Control",builder.build().toString)                        .build();    }}复制代码
public void asyncCacheInterceptor() {        String url = "http://api.k780.com/?app=weather.future&weaid=1&&appkey=10003" +                "&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json";        OkHttpClient okHttpClient = new OkHttpClient.Builder()                .addInterceptor(new LoggingInterceptor())//应用拦截                .addNetworkInterceptor(new NetInterceptor())//网络拦截器                .build();        //通过拦截器修改请求头        Request request = new Request.Builder()                .get()                .header("user-Agent","Interceptor example")                .url(url)                .build();        okHttpClient.newCall(request).enqueue(new Callback() {            @Override            public void onFailure(Call call, IOException e) {            }            @Override            public void onResponse(Call call, Response response) throws IOException {                Log.d(TAG, "asyncCacheInterceptor: " + response.body().string());            }        });     }    复制代码

二. 基本原理

采用OkHttp源码版本为3.8.1

1. OkHttpClient的创建

OkHttpClient作为okhttp的入口,一般将OkHttpClient采用单例模式.

初始化两种方式

  1. 采用直接"new"的方式
  2. 采用建造者模式

对应源码

//内部也是通过建造者进行初始化OkHttpClient okhttpClient = new OkHttpClient();OkHttpClient okhttpClient = new OkHttpClient.Builder()                            .build();复制代码

通过初始化okhttpclient,完成对许多成员变量的初始化工作.

对应源码

OkHttpClient(Builder builder) {    //分发器对象,记录请求执行情况,内部维护一个线程池来执行异步请求    this.dispatcher = builder.dispatcher;    this.proxy = builder.proxy;    this.protocols = builder.protocols;    this.connectionSpecs = builder.connectionSpecs;    //应用拦截器集合    this.interceptors = Util.immutableList(builder.interceptors);    //网络拦截器集合    this.networkInterceptors = Util.immutableList(builder.networkInterceptors);    this.eventListenerFactory = builder.eventListenerFactory;    this.proxySelector = builder.proxySelector;    //Cookie瓶    this.cookieJar = builder.cookieJar;    //磁盘缓存    this.cache = builder.cache;    this.internalCache = builder.internalCache;    this.socketFactory = builder.socketFactory;      //HTTPS相关成员变量初始化    ......        }复制代码

2. Call的创建

当okhttpclient初始化后,再通过okHttpClient.newCall(request),创建一个Call对象,最终实际返回了一个RealCall对象.

对应源码

@Override public Call newCall(Request request) {        return new RealCall(this, request, false /* for web socket */);  }复制代码
2.1 同步请求时

调用RealCall的execute方法

对应源码

/**   * 1. 首先看RealCall的execute方法.   */@Override public Response execute() throws IOException {    synchronized (this) {      if (executed) throw new IllegalStateException("Already Executed");      executed = true;    }    captureCallStackTrace();    try {      //实际调用了分发器的executed方法,传入RealCall对象.      client.dispatcher().executed(this);      //最终通过拦截器链获取响应.      //最终通过拦截器链获取响应.      //最终通过拦截器链获取响应.      //重要的话说三遍.      Response result = getResponseWithInterceptorChain();      if (result == null) throw new IOException("Canceled");      return result;    } finally {      client.dispatcher().finished(this);    }  }  /** * 2. 接着看dispatcher的executed方法 */synchronized void executed(RealCall call) {    //标识已经执行了请求    runningSyncCalls.add(call);  }复制代码
2.1 异步请求时

同样调用RealCall的enqueue方法

对应源码

/**   * 1.首先看RealCall的enqueue方法.   */@Override public void enqueue(Callback responseCallback) {    synchronized (this) {      if (executed) throw new IllegalStateException("Already Executed");      executed = true;    }    captureCallStackTrace();    //实际调用了分发器的enqueue方法,传入AsyncCall对象    client.dispatcher().enqueue(new AsyncCall(responseCallback));  }    /**   * 2.接着看dispatcher对象的enqueue方法.   */  synchronized void enqueue(AsyncCall call) {    //检测请求是否超过最大请求数和一个host对应最大的请求数    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {      //标识已经执行了请求(这里和同步方法一样操作,可以对比上一点方法)      runningAsyncCalls.add(call);      //使用线程池执行请求      executorService().execute(call);    } else {      readyAsyncCalls.add(call);    }  }     /**    * 3.最后我们看AsyncCall的实现.    */   //AsyncCall是RealCall的内部类,继承自NamedRunnable   //NamedRunnable的实现比较简单就是修改线程名.提供一个execute方法在run用调用,这里就不再晒出具体源码了.   //AsyncCall的实现主是获取响应,进行回调.      final class AsyncCall extends NamedRunnable {    private final Callback 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 {         //回调失败          responseCallback.onFailure(RealCall.this, e);        }      } finally {        client.dispatcher().finished(this);      }复制代码

最后将Realcall对同步与异步请求处理流程做个小结:

  • 同步时.

    1. 调用RealCall的execute,实际调用了分发器的executed方法,传入RealCall对象.
      • 分发器的executed方法,仅仅标识已经执行了请求.
    2. 最终通过拦截器链获取响应.
  • 异步时.

    • 调用RealCall的enqueue,实际调用了分发器的enqueue方法,传入AsyncCall对象.
      • 分发器的executed方法,先检查请求,满足则标识已经执行了请求,最终使用线程池执行请求.不满足则进人等待队列
    • AsyncCall的实现
      • AsyncCall是一个NamedRunnable子类
      • 通过拦截器链获取响应,回调成功与失败.

3.拦截器链的构建与启动

整个okhttp的核心设计之处

  1. 遵循单一原则,每个拦截器只做一项处理.
  2. 拦截器之间通过拦截器链(RealInterceptorChain)进行连接,构建出处理请求和响应的流水链.

请求通过层层拦截器处理,最后发送出去,反之响应.

对应源码

//通过拦截器链获取响应 Response getResponseWithInterceptorChain() throws IOException {     /**     * 初始化拦截器集合     */    List
interceptors = new ArrayList<>(); //添加7种类型拦截器 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); //启动拦截器链 return chain.proceed(originalRequest); } /** * 拦截器链的启动 */ public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException { //......省略部分代码 // 创建一个新的拦截器链,拦截器位置指向将index+1.即下一个. RealInterceptorChain next = new RealInterceptorChain( interceptors, streamAllocation, httpCodec, connection, index + 1, request); //通过下标index获取当前要执行的拦截器 Interceptor interceptor = interceptors.get(index); //执行拦截器intercept方法 //如果当前拦截器执行完后,则调用传入的next(新链对象)的proceed方法,执行下一个拦截器.依次类推. Response response = interceptor.intercept(next); if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) { throw new IllegalStateException("network interceptor " + interceptor + " must call proceed() exactly once"); } if (response == null) { throw new NullPointerException("interceptor " + interceptor + " returned null"); } return response; }复制代码

这7种拦截器执行顺序为:

  1. 应用拦截器
  2. retryAndFollowUpInterceptor
  3. BridgeInterceptor
  4. CacheInterceptor
  5. ConnectInterceptor
  6. 网络拦截器
  7. CallServerInterceptor

其中带下划线的拦截器,就是我们之前自定义的拦截器.接下来就依次讲解7种拦截器的作用.

3.1 应用拦截器

不再讲述

3.2 retryAndFollowUpInterceptor

处理错误重试和重定向

对应源码

@Override public Response intercept(Chain chain) throws IOException {    Request request = chain.request();        while (true) {      if (canceled) {        streamAllocation.release();        throw new IOException("Canceled");      }      Response response = null;      boolean releaseConnection = true;      try {        //执行下一个拦截器,获取响应        response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);        releaseConnection = false;      } catch (RouteException e) {        // The attempt to connect via a route failed. The request will not have been sent.        if (!recover(e.getLastConnectException(), false, request)) {          throw e.getLastConnectException();        }        releaseConnection = false;        //满足条件,则重试        continue;      } catch (IOException e) {      // An attempt to communicate with a server failed. The request may have been sent.        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);        if (!recover(e, requestSendStarted, request)) throw e;        releaseConnection = false;        //满足条件,则重试        continue;      } finally {        //出现异常释放资源,continue在finally语句块执行后才执行        // We're throwing an unchecked exception. Release any resources.        if (releaseConnection) {          streamAllocation.streamFailed(null);          streamAllocation.release();        }      }           //检测是否满足定向的要求代码     ......           //重新赋值为重定向的请求      request = followUp;      priorResponse = response;    }  }复制代码
3.3 BridgeInterceptor

桥接应用层和网络层

A. 将请求进行深加工,使其成为真正请求

  • 对一些请求头的设置

B. 并且也对网络响应做响应处理.

  • 解压缩,去掉不必要的响应头

对应源码

@Override public Response intercept(Chain chain) throws IOException {    //获取请求    Request userRequest = chain.request();    Request.Builder requestBuilder = userRequest.newBuilder();    //对一些请求头的设置.    RequestBody body = userRequest.body();    if (body != null) {      MediaType contentType = body.contentType();      if (contentType != null) {        requestBuilder.header("Content-Type", contentType.toString());      }      long contentLength = body.contentLength();      if (contentLength != -1) {        requestBuilder.header("Content-Length", Long.toString(contentLength));        requestBuilder.removeHeader("Transfer-Encoding");      } else {        requestBuilder.header("Transfer-Encoding", "chunked");        requestBuilder.removeHeader("Content-Length");      }    }    if (userRequest.header("Host") == null) {      requestBuilder.header("Host", hostHeader(userRequest.url(), false));    }    if (userRequest.header("Connection") == null) {      requestBuilder.header("Connection", "Keep-Alive");    }    // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing    // the transfer stream.    boolean transparentGzip = false;    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {      transparentGzip = true;      //如果没有配置,默认配置gzip.获取响应时需要解压缩.      requestBuilder.header("Accept-Encoding", "gzip");    }    List
cookies = cookieJar.loadForRequest(userRequest.url()); if (!cookies.isEmpty()) { requestBuilder.header("Cookie", cookieHeader(cookies)); } if (userRequest.header("User-Agent") == null) { requestBuilder.header("User-Agent", Version.userAgent()); } //执行下一个拦截器获取响应 Response networkResponse = chain.proceed(requestBuilder.build()); HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers()); Response.Builder responseBuilder = networkResponse.newBuilder() .request(userRequest); if (transparentGzip && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding")) && HttpHeaders.hasBody(networkResponse)) { //用于解压缩 GzipSource responseBody = new GzipSource(networkResponse.body().source()); //返回应用层去掉不需要的响应头 Headers strippedHeaders = networkResponse.headers().newBuilder() .removeAll("Content-Encoding") .removeAll("Content-Length") .build(); responseBuilder.headers(strippedHeaders); responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody))); } return responseBuilder.build(); }复制代码
3.4 CacheInterceptor

承担缓存的查找和保存职责

A. 不需要网络请求,缓存可用,直接返回

B. 缓存不可用,执行下一个拦截器获取响应;

如果用户配置了需要缓存,将响应写入缓存,并返回.

对应源码

@Override  public Response intercept(Chain chain) throws IOException {    Response cacheCandidate = cache != null        ? cache.get(chain.request())        : null;    long now = System.currentTimeMillis();    //检查缓存策略    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();    //networkRequest不为空,需要发送请求    Request networkRequest = strategy.networkRequest;    //cacheResponse不为空,则缓存可用    Response cacheResponse = strategy.cacheResponse;   //......省略部分代码    // If we don't need the network, we're done.    //不需要网络请求,缓存可用,直接返回    if (networkRequest == null) {      return cacheResponse.newBuilder()          .cacheResponse(stripBody(cacheResponse))          .build();    }    Response networkResponse = null;    try {      //缓存不可用,执行下一个拦截器获取响应      networkResponse = chain.proceed(networkRequest);    } finally {      //......省略部分代码    }    //......省略部分代码        //包装网络响应    Response response = networkResponse.newBuilder()        .cacheResponse(stripBody(cacheResponse))        .networkResponse(stripBody(networkResponse))        .build();        //用户配置了缓存    if (cache != null) {      //判断条件      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {        // Offer this request to the cache.        CacheRequest cacheRequest = cache.put(response);        //网络响应写入缓存,并返回        return cacheWritingResponse(cacheRequest, response);      }      if (HttpMethod.invalidatesCache(networkRequest.method())) {        try {          cache.remove(networkRequest);        } catch (IOException ignored) {          // The cache cannot be written.        }      }    }    return response;  }复制代码
3.5 ConnectInterceptor

给请求提供一个连接

  • TCP的连接,HTTPS的连接全部在此完成.

对应源码

@Override  public Response intercept(Chain chain) throws IOException {    RealInterceptorChain realChain = (RealInterceptorChain) chain;    Request request = realChain.request();    StreamAllocation streamAllocation = realChain.streamAllocation();    // We need the network to satisfy this request. Possibly for validating a conditional GET.    boolean doExtensiveHealthChecks = !request.method().equals("GET");    //获取HTTP编码解码器    HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);    //获取一个链接    RealConnection connection = streamAllocation.connection();    //执行下一个拦截器    return realChain.proceed(request, streamAllocation, httpCodec, connection);  }复制代码
3.6 网络拦截器

不再讲述

3.7 CallServerInterceptor

将请求发送出去

对应源码

@Override   public Response intercept(Chain chain) throws IOException {       //.......省略部分代码      //发送网络请求,httpCodec则是在上一个ConnectInterceptor拦截器获取到的    httpCodec.finishRequest();    if (responseBuilder == null) {      responseBuilder = httpCodec.readResponseHeaders(false);    }        //构建网络响应对象    Response response = responseBuilder        .request(request)        .handshake(streamAllocation.connection().handshake())        .sentRequestAtMillis(sentRequestMillis)        .receivedResponseAtMillis(System.currentTimeMillis())        .build();    int code = response.code();    if (forWebSocket && code == 101) {      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.      response = response.newBuilder()          .body(Util.EMPTY_RESPONSE)          .build();    } else {      //写入响应体      response = response.newBuilder()          .body(httpCodec.openResponseBody(response))          .build();    }    if ("close".equalsIgnoreCase(response.request().header("Connection"))        || "close".equalsIgnoreCase(response.header("Connection"))) {      streamAllocation.noNewStreams();    }    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {      throw new ProtocolException(          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());    }    return response;  }复制代码

由于本人技术有限,如有错误的地方,麻烦大家给我提出来,本人不胜感激,大家一起学习进步.

转载地址:http://itbfx.baihongyu.com/

你可能感兴趣的文章
我的友情链接
查看>>
linux用户家目录无损迁移到独立硬盘
查看>>
文件查找
查看>>
shell编程前言(一)
查看>>
5、centos7.*配置yum的EPEL源及其它源
查看>>
JSON前后台简单操作
查看>>
shell中一些常见的文件操作符
查看>>
第一次作业
查看>>
ssh连接提示问题
查看>>
CentOS 7 装vim遇到的问题和解决方法
查看>>
JavaScript基础教程1-20160612
查看>>
django项目配置
查看>>
使用第三方类、库需要注意的正则类RegexKitLite的使用
查看>>
iOS \U7ea2 乱码 转换
查看>>
FCN图像分割
查看>>
ios xmpp demo
查看>>
设计模式之-工厂模式、构造函数模式
查看>>
python matplotlib 中文显示参数设置
查看>>
数据库事务隔离级别
查看>>
os模块大全详情
查看>>