OKhttp使用详解(一)

参考okhttp官方wiki

https://github.com/square/okhttp/wiki/Calls

okhttp3的设计思路

API部分简介

OkHttpClient.Builder:OkHttpClient可通过Builder采用建造者模式构建,通过Builder可以方便灵活的设置通用参数

  • public Builder connectTimeout(long timeout, TimeUnit unit) 为连接设置超时时间
  • public Builder readTimeout(long timeout, TimeUnit unit) 设置默认读时间
  • public Builder writeTimeout(long timeout, TimeUnit unit) 设置默认写时间
  • public Builder proxy(Proxy proxy) 设置代理
  • public Builder proxySelector(ProxySelector proxySelector)
  • public Builder cookieJar(CookieJar cookieJar) 设置处理cookies的处理者(Sets the handler that can accept cookies from incoming HTTP responses and provides cookies to outgoing HTTP requests)
  • void setInternalCache(InternalCache internalCache) 设置内部缓存
  • public Builder cache(Cache cache) 设置缓存
  • public Builder dns(Dns dns) 设置DNS 服务
  • public Builder socketFactory(SocketFactory socketFactory)
  • public Builder sslSocketFactory(SSLSocketFactory sslSocketFactory) 配置证书用的
  • public Builder sslSocketFactory( SSLSocketFactory sslSocketFactory, X509TrustManager trustManager) 配置证书用的
  • public Builder hostnameVerifier(HostnameVerifier hostnameVerifier) Sets the verifier used to confirm that response certificates apply to requested hostnames for HTTPS connections. 应该也是是配置https的
  • public Builder certificatePinner(CertificatePinner certificatePinner) 不太懂,没用过
  • public Builder authenticator(Authenticator authenticator) Sets the authenticator used to respond to challenges from origin servers.
  • public Builder proxyAuthenticator(Authenticator proxyAuthenticator) Sets the authenticator used to respond to challenges from proxy servers.
  • public Builder connectionPool(ConnectionPool connectionPool) Sets the connection pool used to recycle HTTP and HTTPS connections. 设置连接池
  • public Builder followSslRedirects(boolean followProtocolRedirects) Configure this client to follow redirects from HTTPS to HTTP and from HTTP to HTTPS.不懂
  • public Builder followRedirects(boolean followRedirects) Configure this client to follow redirects. If unset, redirects be followed. 不懂
  • public Builder retryOnConnectionFailure(boolean retryOnConnectionFailure) 连接失败是否重连
  • public Builder dispatcher(Dispatcher dispatcher) Sets the dispatcher used to set policy and execute asynchronous requests. Must not be null.
  • public Builder protocols(List<Protocol> protocols) 设置协议
  • public Builder connectionSpecs(List<ConnectionSpec> connectionSpecs) 不懂
  • public List<Interceptor> interceptors() 应该是获取拦截器列表
  • public Builder addInterceptor(Interceptor interceptor) 添加拦截器
  • public List<Interceptor> networkInterceptors() 返回网络拦截器
  • public Builder addNetworkInterceptor(Interceptor interceptor) 添加网络拦截器
  • public OkHttpClient build() 构建OkHttpClient,并为它设置参数

由上面的设计图可知,OkhttpClient是核心,他就像一个浏览器一样,什么工作,比如请求,接受响应都是由它做的,关键就是怎么给他配置http协议的那些配置了,因为配置太多,所以用了Builder帮助配置

翻翻Builder的源码可以知道,即时你没有配置啥参数,Builder也为你默认设置一些参数,比如他默认设置支持 Protocol.HTTP_2, Protocol.SPDY_3, Protocol.HTTP_1_1这三种协议,默认连接,读写超时都是10s,DNS用系统的,默认失败重连。。。。

Dispatcher dispatcher;
    Proxy proxy;
    List<Protocol> protocols;
    List<ConnectionSpec> connectionSpecs;
    final List<Interceptor> interceptors = new ArrayList<>();
    final List<Interceptor> networkInterceptors = new ArrayList<>();
    ProxySelector proxySelector;
    CookieJar cookieJar;
    Cache cache;
    InternalCache internalCache;
    SocketFactory socketFactory;
    SSLSocketFactory sslSocketFactory;
    CertificateChainCleaner certificateChainCleaner;
    HostnameVerifier hostnameVerifier;
    CertificatePinner certificatePinner;
    Authenticator proxyAuthenticator;
    Authenticator authenticator;
    ConnectionPool connectionPool;
    Dns dns;
    boolean followSslRedirects;
    boolean followRedirects;
    boolean retryOnConnectionFailure;
    int connectTimeout;
    int readTimeout;
    int writeTimeout;

    public Builder() {
      dispatcher = new Dispatcher();
      protocols = DEFAULT_PROTOCOLS;
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      proxySelector = ProxySelector.getDefault();
      cookieJar = CookieJar.NO_COOKIES;
      socketFactory = SocketFactory.getDefault();
      hostnameVerifier = OkHostnameVerifier.INSTANCE;
      certificatePinner = CertificatePinner.DEFAULT;
      proxyAuthenticator = Authenticator.NONE;
      authenticator = Authenticator.NONE;
      connectionPool = new ConnectionPool();
      dns = Dns.SYSTEM;
      followSslRedirects = true;
      followRedirects = true;
      retryOnConnectionFailure = true;
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
    }

创建OkhttpHelper

因为只要有请求就必须有okhttpclient参与,一般项目是分模块的,每个模块基本都有网络请求,总不能每个请求都要构造一个okhttpclient去请求吧,所以okhttpclient设计为单例,不用new ,到处都可以用它,方便快捷,代码得到了复用,也节省了内存开销

这里采用内部静态类实现单例

public class OkhttpHelper {
    public static final int DEFAULTITMEOUT=5;
    private OkHttpClient okHttpClient;

    private OkhttpHelper(){
        OkHttpClient.Builder builder=new OkHttpClient.Builder()
                .connectTimeout(DEFAULTITMEOUT, TimeUnit.SECONDS);
        okHttpClient=builder.build();
    }
    public OkHttpClient getOkHttpClient() {
        return okHttpClient;
    }
    private static class OkhttpHelperHolder{
        public static OkhttpHelper INSTANCE=new OkhttpHelper();
    }
}

有Http协议,每个连接必有请求头,是供服务端或客户端读取的,这里Okhttp封装了请求,并且也为它提供builder类,再翻翻看

这些是默认的参数

 private HttpUrl url;
    private String method;
    private Headers.Builder headers;
    private RequestBody body;
    private Object tag;

    public Builder() {
      this.method = "GET";
      this.headers = new Headers.Builder();
    }

    private Builder(Request request) {
      this.url = request.url;
      this.method = request.method;
      this.body = request.body;
      this.tag = request.tag;
      this.headers = request.headers.newBuilder();
    }
  • public Builder url(HttpUrl url) 设置请求的URl,这个方法相当于浏览器上的地址输入框
  • public Builder url(String url) 重载方法
  • public Builder url(URL url) 也是重载,所以这里有三种url的表达格式
  • public Builder header(String name, String value) 这里是更新header字段的,就是设置键值对,里面的header应该用了个Map来记录的,我还在看
  • public Builder addHeader(String name, String value) 添加header字段
  • public Builder removeHeader(String name) 删除header字段
  • public Builder headers(Headers headers) 看吧,这里直接设置Headers,headers肯定是把所有的字段都包起来了
  • public Builder cacheControl(CacheControl cacheControl) 设置缓存控制,它其实也只设置header字段,不信看它的内部实现
   public Builder cacheControl(CacheControl cacheControl) {
      String value = cacheControl.toString();
      if (value.isEmpty()) return removeHeader("Cache-Control");
      return header("Cache-Control", value);
    }
  • public Builder method(String method, RequestBody body) 设置请求的方法和请求体,为什么先说这个呢?
  • public Builder get() 设置请求方法

其实它的具体实现是调用method()方法

 public Builder get() {
      return method("GET", null);
    }

怎么使用?

HTTP客户端的工作是接受您的请求并产生响应。这在理论上很简单,但在实践中变得棘手。

Requests 请求

每个HTTP请求包含一个URL,一个请求方法(像GET,POST),和一个头部列表,请求也包含一个body:特殊内容类型的数据流,比如文件,json数据...

Responses 响应

响应使用代码(如200获得成功或404找不到)来应答请求,标题及其自己的可选正文。

重写请求

当你提供给OKhttp一个HTTP请求,他就会按你定义的请求去执行,重写请求上面已给出了源码,直接用builder去set就是,可见okhttp支持很多header,如Content-Length, Transfer-Encoding, User-Agent, Host, Connection, and Content-Type

重写响应

响应是okhttp制造的,我们只是处理响应就行了。

后续请求

当您请求的URL已经移动时,网络服务器将返回一个302类型的响应代码,以指示该文档的新URL。 OkHttp将遵循重定向来检索最终的响应。

重试请求

有时连接失败:池连接过时并断开连接,或者无法访问Web服务器本身。 OkHttp将使用不同的路由重试该请求(如果有)。

Calls 呼叫

通过重写,重定向,后续跟踪和重试,您的简单请求可能会产生许多请求和响应,OkHttp使用Call来建立满足您的请求的任务,但是需要许多中间请求和响应。
有两种执行呼叫的方式

  • Synchronous 同步:你的线程被阻塞,直到响应可读。
  • Asynchronous 异步:您在任何线程上排队请求,并在响应可读时在另一个线程获取回调
    呼叫可以在任何线程被取消,如果呼叫尚未完成,这将呼叫失败!编写请求正文或读取响应主体的代码在其呼叫被取消时将抛出IOException。

Dispatch

对于同步调用,您可以自己创建线程,并负责管理同时发出的请求数。同时连接太多浪费资源;太少的危害延迟。在安卓开发显然不能阻塞主线程,得在另一个线程执行。

对于异步调用,Dispatcher实现最大同时请求的策略。您可以设置每个网络服务器的最大值(默认值为5),总体(默认值为64)。

连接

1.它使用URL并配置了OkHttpClient来创建一个地址。该地址指定我们如何连接到Web服务器。
2.它尝试从连接池检索与该地址的连接.
3.如果在池中没有找到连接,则会选择尝试的路由。这通常意味着进行DNS请求以获取服务器的IP地址。然后,如果需要,可以选择TLS版本和代理服务器。
4.如果它是一个新路由,它通过构建直接套接字连接,TLS隧道(通过HTTP代理的HTTPS)或直接TLS连接进行连接。它需要TLS握手。
5.它发送HTTP请求并读取响应

如果连接有问题,OkHttp将选择另一个路由,然后重试。这允许OkHttp在服务器地址的子集不可达时恢复。

在接收到响应后,连接将返回到池中,以便将来可以重用该请求。连接在一段时间不活动之后从游泳池逐出。

使用实例

由于多处都会使用okhttpClient,所以我把它封装在一个单例类里,便于随处获取


/**
 * Created by Newtrek on 2017/4/12.
 */
public class HttpHelper {
    public static int DEFAULT_TIMEOUT=5;
    private OkHttpClient okHttpClient;
    private HttpHelper(){
        OkHttpClient.Builder builder=new OkHttpClient.Builder();
//        set time out
        builder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
        okHttpClient=builder.build();
    }

    public static HttpHelper getInstance(){
        return HttpHelperHolder.instance;
    }

    public OkHttpClient getOkHttpClient() {
        return okHttpClient;
    }

    private static class HttpHelperHolder{
        private static  HttpHelper instance=new HttpHelper();
    }
}

同步请求 GET

 /**
     * 下载一个文件,并以字符串形式打印响应里的头部,正文
     */
    public static  void SynchGet(){
//        获取client
        OkHttpClient okHttpClient= HttpHelper.getInstance().getOkHttpClient();
//         构造请求,builder源码已分析,不用设置methos就是默认的get方法
        Request request=new  Request.Builder()
                .url("http://publicobject.com/helloworld.txt")
                .build();
        try {
//            获取响应
            Response response=okHttpClient.newCall(request).execute();
//            响应成功,就打印相关内容,其实他是判断code是否属于【200,300)
            if (response.isSuccessful()){
                Headers headers=response.headers();
                for (int i=0;i<headers.size();i++){
                    System.out.println(headers.name(i)+":"+headers.value(i));
                }
            System.out.println(response.body().string());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

结果:


title
title

title
title

异步请求 GET


    public static  void AsynchGet(){
        //        获取client
        OkHttpClient okHttpClient=HttpHelper.getInstance().getOkHttpClient();
        //         构造请求
        Request request=new Request.Builder()
                .url("http://publicobject.com/helloworld.txt")
                .build();
//        请求加入队列
        okHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                e.printStackTrace();
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response.isSuccessful()){
                    Headers headers=response.headers();
                    for (int i=0;i<headers.size();i++){
                        System.out.println(headers.name(i)+":"+headers.value(i));
                    }
                    System.out.println(response.body().string());
                }
            }
        });
    }

访问HTTP标头

通常,HTTP标头的工作方式与Map <String,String>类似,但是有些标题允许多个值,如Guava的Multimap。
当写请求头时,使用头(名称,值)来设置名称唯一出现的值。
如果存在现有值,则在添加新值之前将删除它们。使用addHeader(name,value)来添加标题,而不会删除已经存在的标题。

private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("https://api.github.com/repos/square/okhttp/issues")
        .header("User-Agent", "OkHttp Headers.java")
        .addHeader("Accept", "application/json; q=0.5")
        .addHeader("Accept", "application/vnd.github.v3+json")
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println("Server: " + response.header("Server"));
    System.out.println("Date: " + response.header("Date"));
    System.out.println("Vary: " + response.headers("Vary"));
  }

post a String

使用HTTP POST将请求体发送到服务

public static void postAString() {
        MediaType MEDIA_TYPE_MARKDOWN
                = MediaType.parse("text/x-markdown; charset=utf-8");
        String postBody = ""
                + "Releases\n"
                + "--------\n"
                + "\n"
                + " * _1.0_ May 6, 2013\n"
                + " * _1.1_ June 15, 2013\n"
                + " * _1.2_ August 11, 2013\n";

        OkHttpClient okHttpClient = HttpHelper.getInstance().getOkHttpClient();
        Request request = new Request.Builder()
                .url("https://api.github.com/markdown/raw")
                .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
                .build();
        try {
            Response response = okHttpClient.newCall(request).execute();
               System.out.println( response.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

post Streaming

 public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.parse("text/x-markdown; charset=utf-8");

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    RequestBody requestBody = new RequestBody() {
      @Override public MediaType contentType() {
        return MEDIA_TYPE_MARKDOWN;
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        sink.writeUtf8("Numbers\n");
        sink.writeUtf8("-------\n");
        for (int i = 2; i <= 997; i++) {
          sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
        }
      }

      private String factor(int n) {
        for (int i = 2; i < n; i++) {
          int x = n / i;
          if (x * i == n) return factor(x) + " × " + i;
        }
        return Integer.toString(n);
      }
    };

    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(requestBody)
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());
  }

post a file

直接 .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file)),file是File类型

post form parameters (post表单)

RequestBody formBody = new FormBody.Builder()
.add("search", "Jurassic Park")
.build();

Posting a multipart request

MultipartBody.Builder可以构建与HTML文件上传表单兼容的复杂请求体。多部分请求体的每个部分本身就是一个请求体,并且可以定义自己的头。如果存在,这些标题应该描述零件体,例如它的Content-Disposition。如果Content-Length和Content-Type标题可用,则会自动添加。

// Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
    RequestBody requestBody = new MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart("title", "Square Logo")
        .addFormDataPart("image", "logo-square.png",
            RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
        .build();

用Gson解析json响应

  Gist gist = gson.fromJson(response.body().charStream(), Gist.class);
    for (Map.Entry<String, GistFile> entry : gist.files.entrySet()) {
      System.out.println(entry.getKey());
      System.out.println(entry.getValue().content);
    }

响应缓存

要缓存响应,您需要一个可以读取和写入的缓存目录,并对缓存的大小有限制。缓存目录应该是私有的,不受信任的应用程序不能读取其内容!

响应缓存使用HTTP头进行所有配置,你可以添加请求头,如Cache-Control:max-stale = 3600,OkHttp的缓存将遵守它们。您的网络服务器使用自己的响应头配置响应缓存的时间长短像Cache-Control:max-age = 9600。有缓存标头强制缓存的响应,强制网络响应,或强制使用条件GET验证网络响应

//        set cache
        int cacheSize=10*1024*1024;//10MB
        File cacheFile=new File("E://java/");
        if (!cacheFile.exists()){
            cacheFile.mkdir();
        }
        Cache cache=new Cache(cacheFile,cacheSize);
        builder.cache(cache);

public static void ResponseCache(){
        OkHttpClient okHttpClient = HttpHelper.getInstance().getOkHttpClient();
        Request request = new Request.Builder()
                .url("http://publicobject.com/helloworld.txt")
                .build();
        try {
            Response response1 = okHttpClient.newCall(request).execute();
            String response1Body = response1.body().string();
            System.out.println("Response 1 response:          " + response1);
            System.out.println("Response 1 cache response:    " + response1.cacheResponse());
            System.out.println("Response 1 network response:  " + response1.networkResponse());
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            Response response2 = okHttpClient.newCall(request).execute();
            String response2Body = response2.body().string();
            System.out.println("Response 2 response:          " + response2);
            System.out.println("Response 2 cache response:    " + response2.cacheResponse());
            System.out.println("Response 2 network response:  " + response2.networkResponse());

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
title
title

title
title

可以验证到我的E://java/目录里多了四个文件,其实他们就是缓存文件

cancel Call

每次client.newCall()的时候会返回一个Call,所以取消的话就调用call.cancel()就行,如果请求任务未完成,会抛出IO异常

Timeout

 .connectTimeout(10, TimeUnit.SECONDS)
        .writeTimeout(10, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)

处理身份验证authentication

OkHttp可以自动重试未经身份验证的请求。当答复为401未授权时,将要求身份验证者提供凭证。实现应该构建一个包含缺少凭据的新请求。如果没有凭据可用,返回null以跳过重试。使用Response.challenges()来获取任何认证挑战的方案和领域。在履行基本挑战时,请使用Credentials.basic(用户名,密码)对请求标头进行编码。

 client = new OkHttpClient.Builder()
        .authenticator(new Authenticator() {
          @Override public Request authenticate(Route route, Response response) throws IOException {
            System.out.println("Authenticating for response: " + response);
            System.out.println("Challenges: " + response.challenges());
            String credential = Credentials.basic("jesse", "password1");
            return response.request().newBuilder()
                .header("Authorization", credential)
                .build();
          }
        })
        .build();
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容