Okhttp3基本使用

目录

1.OkHttp简介

2.使用

2.1Get同步阻塞调用

2.2Get异步调用

2.3访问Header(请求头或者响应头)

2.4post提交String

2.5Post方式提交流

2.6Post方式提交一个文件

2.7Post方式提交Form 表单

2.8Post方式提交分块请求

3.OkHttp定义的拦截器Interceptors

3.1全局拦截器:

3.2网络拦截器:

4.其他


1.OkHttp简介

HTTP是一种现代常用的交换数据和媒体信息的网络方式。高效的使用HTTP将使数据加载更快,同时节省带宽。

OkHttp是一个HTTP客户端,拥有如下特性:

支持HTTP/2,并且允许对同一个主机的所有请求共享一个Socket连接;

连接池减少请求延迟(如果HTTP/2不支持);

透明的GZIP压缩减少响应数据的大小;

当网络出现问题时OkHttp将坚持自己的职责;它会自动恢复一般的连接问题。如果你的服务有多个IP地址,OkHttp将尝试切换地址在遇到第一个连接失败的情况下。这对于IPv4+IPv6和冗余数据中心中托管的服务是必要的。OkHttp启动了与现代TLS特性(SNI、ALPN)的新连接,如果握手失败,则返回到TLS 1.0。

使用OkHttp很容易。它的请求/响应API是用连贯的构建器和不变性来设计的。它支持同步阻塞调用和带有回调的异步调用。

OkHttp支持Android 2.3及以上版本。对于Java,最低要求是1.7。

2.使用

使用OkHttp很容易。它的请求/响应API是用连贯的构建器和不变性来设计的。它支持同步阻塞调用和带有回调的异步调用。

2.1Get同步阻塞调用

-创建OkHttpClient对象;

-创建Request请求;

-执行newCall创建Call;

-执行Call.execute();执行网络请求;

说明:当前执行是阻塞式调用,只有当前网络请求完成才会继续向下执行,Android3.0以后是不允许在主线程访问网络请求;

    /**
     * Get方式同步请求
     */
    public void syncGetReqest() throws IOException {
        //1.创建OkHttp客户端
        OkHttpClient client = new OkHttpClient();
        //2.创建网络请求
        Request request = new  Request.Builder()
        .url("http://192.168.2.10:8080/ShiroTest/code/list")
        .build();
        //3.创建Call
        Call call = client.newCall(request);
        //执行网络请求
        Response response= call.execute();
        if(!response.isSuccessful()){
            throw new IOException("Unexpected code " + response);
        }
        //获取响应头
        Headers responseHeaders = response.headers();
        for(int i=0; i < responseHeaders.size(); i++){
            System.out.println(responseHeaders.name(i)+" : "+ responseHeaders.value(i));
        }
        //打印返回内容
        System.out.println(response.body().string());
    }

2.2Get异步调用

Get异步调用和Get同步调用方式类似,只是执行网络请求调用时Call.execute()方法调用换做Call.enqueue()通过接口回调的方式监听网络请求结果,

 /**
     * Get方式异步请求
     */
    public void asyncGetReqest() throws IOException {
        //1.创建OkHttp客户端
        OkHttpClient client = new OkHttpClient();
        //2.创建网络请求
        Request request = new  Request.Builder()
                .url("http://publicobject.com/helloworld.txt")
                .build();
        //3.创建Call
        Call call = client.newCall(request);
        //4.执行网络请求
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                System.out.println("异步网络请求失败...");
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if(!response.isSuccessful()){
                    throw new IOException("Unexpected code " + response);
                }
                Headers headers = response.headers();
                for(int i=0;i<headers.size();i++){
                    System.out.println(headers.name(i)+" : ");
                }
                System.out.println("异步网络请求成功..."+response.body().string());
            }
        });
        System.out.println("异步网络请求回调之后...");

Call.enqueue执行异步网络请求线程会加入到Dispatcher类中的runningAsyncCalls(维护正在运行的异步请求线程,方便取消当前执行的线程使用和维护异步请求线程状态)双端队列中,线程会加入到线程池中执行;

 synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

2.3访问Header(请求头或者响应头)

通常HTTP报头的工作方式类似于Map<String, String>:每个字段都有一个值或一个都没有。但有些报头允许多个值,比如Guava的Multimap。例如,对于一个HTTP响应提供多个Vary报头是合规和常见的。OkHttp的api试图让这两种情况都变得有用。
在编写请求保头时,使用header(name、value)仅仅设置出现的名称的值。如果存在现有值,则在添加新值之前将删除已存在的值。使用addHeader(name, value)添加报头,而不删除已经出现的报头。
在读取一个响应报头时,使用header(name)返回最后一次出现的命名值。通常这也是唯一的发生!如果不存在值,header(name)将返回null。要将字段的所有值作为列表读取,请使headers(name)。
要访问所有headers,请使用支持按索引访问的Headers。

//访问Header
    public void accessHeaders() throws IOException {
        OkHttpClient client = new OkHttpClient();
        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();
        Call call = client.newCall(request);
        Response response = call.execute();
        if(!response.isSuccessful()){
            throw new IOException("Unexpected code " + response);
        }
        Headers headers = response.headers();

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

2.4post提交String

post方式提交数据和get方式提交的主要区别是Request需要添加RequestBody(需要提交给服务器的信息),创建RequestBody需要指定内容类型MediaType,MediaType主要用于描述请求/响应内容的类型,这个例子将post提交一个markdown文档给web服务去渲染markdown做为HTML;

RequestBody.create重载多个创建RequestBody的方法:

public static RequestBody create(MediaType contentType, String content) 
public static RequestBody create(final MediaType contentType, final ByteString content) 
public static RequestBody create(final MediaType contentType, final byte[] content)
public static RequestBody create(final MediaType contentType, final byte[] content,
      final int offset, final int byteCount) 
public static RequestBody create(final MediaType contentType, final File file)

注意事项:因为整个RequestBody(请求体)同时在内存中,所以避免使用此API发布较大(大于1 MiB)的文档。

    //post请求字符串
    public void postAString() throws IOException {
        MediaType mediaType = MediaType.parse(
                "text/x-markdown; charset=utf-8");

        OkHttpClient client = new OkHttpClient();

        String postBody = ""
                + "Releases\n"
                + "--------\n"
                + "\n"
                + " * _1.0_ May 6, 2013\n"
                + " * _1.1_ June 15, 2013\n"
                + " * _1.2_ August 11, 2013\n";

        Request request = new Request.Builder()
                .url("https://api.github.com/markdown/raw")
                .post(RequestBody.create(mediaType, postBody))
                .build();
        Response response = client.newCall(request).execute();
        if(!response.isSuccessful()){
            throw new IOException("Unexpected code " + response);
        }
        System.out.println(response);
    }

响应内容

Server  :  GitHub.com
Date  :  Wed, 12 Sep 2018 02:47:41 GMT
Content-Type  :  text/html;charset=utf-8
Transfer-Encoding  :  chunked
Status  :  200 OK
X-RateLimit-Limit  :  60
X-RateLimit-Remaining  :  58
X-RateLimit-Reset  :  1536723315
X-CommonMarker-Version  :  0.17.11
Access-Control-Expose-Headers  :  ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval
Access-Control-Allow-Origin  :  *
Strict-Transport-Security  :  max-age=31536000; includeSubdomains; preload
X-Frame-Options  :  deny
X-Content-Type-Options  :  nosniff
X-XSS-Protection  :  1; mode=block
Referrer-Policy  :  origin-when-cross-origin, strict-origin-when-cross-origin
Content-Security-Policy  :  default-src 'none'
X-Runtime-rack  :  0.012857
X-GitHub-Request-Id  :  5FA1:2DF0:524E91:C493B1:5B987E4C
OkHttp-Sent-Millis  :  1536720461639
OkHttp-Received-Millis  :  1536720461895

2.5Post方式提交流

在这里,我们将请求主体作为流进行POST提交。这个请求主体的内容在编写时生成。这个示例直接放到Okio缓冲接收器中。您的程序可能更喜欢OutputStream,您可以从BufferedSink.outputStream()中获得它。

Okio是一个使java.io和java.nio访问、存储和处理数据变得更加容易组件;

    public void postStream() throws IOException {
        MediaType mediaType = MediaType.parse("text/x-markdown; charset=utf-8");
        OkHttpClient client = new OkHttpClient();
        RequestBody requestBody = new RequestBody() {
            @Override
            public MediaType contentType() {
                return mediaType;
            }

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

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

2.6Post方式提交一个文件

将文件封装到RequestBody中,提交给服务器

public void postAFile() throws IOException {
        MediaType mediaType = MediaType.parse("text/x-markdown; charset=utf-8"); //MediaType.parse("image/png");
        OkHttpClient client = new OkHttpClient();
        File file = new File("/Users/fandong/Desktop/normal.jpg");
        Request request = new Request.Builder()
                .url("https://api.github.com/markdown/raw")
                .post(RequestBody.create(mediaType, file))
                .build();

        Response response = client.newCall(request).execute();

        if(!response.isSuccessful()){
            throw new IOException("Unexpected code " + response);
        }

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

2.7Post方式提交Form 表单

使用FormBody.Builder去构建一个类似HTML中<form>标签一样工作的请求体。名称和值将使用HTML中编译form的URL编码方式编码;

public void postFormParams() throws IOException {
        OkHttpClient client = new OkHttpClient();
        RequestBody formBody = new FormBody.Builder()
                .add("search", "Jurassic Park")
                .build();
        Request request = new Request.Builder()
                .url("https://en.wikipedia.org/w/index.php")
                .post(formBody)
                .build();

        Response response = client.newCall(request).execute();
        
        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());
    }

FormBody中存放key和value定义; 

  private final List<String> encodedNames;
  private final List<String> encodedValues;

2.8Post方式提交分块请求

MultipartBody.Builder 能构建与HTML文件上传表单兼容的复杂的请求体。一个multipart request body的每一部分都是一个她自己的请求体,并且能够定义它自己的headers。这些请求头可以描述部分body,例如Content-Disposition。这个Content-Length和Content-Type头如果合适的话将自动被添加。

    public void postMultipartBody() throws IOException {
        OkHttpClient client = new OkHttpClient();
        String IMGUR_CLIENT_ID = "...";
        MediaType mediaType = MediaType.parse("image/png");
        RequestBody requestBody = new MultipartBody.Builder()
                 //设置form表单为multipart/form-data类型,multipart/form-data是将文件以二进制的形式上传,这样可以实现多种类型的文件上传
                .setType(MultipartBody.FORM)
                //说明每一部分都是一个请求体,并且定义自己的Headers
                .addPart(Headers.of("Content-Disposition", "form-data; name=\"title\""),
                        RequestBody.create(null, "Square Logo"))
                //form表单数据
                .addFormDataPart("title", "Square Logo")
                //form表单文件数据
                .addFormDataPart("image", "logo-square.png",
                        RequestBody.create(mediaType, new File("/Users/fandong/Desktop/normal.jpg")))
                .build();

        Request request = new Request.Builder()
                .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
                .url("https://api.imgur.com/3/image")
                .post(requestBody)
                .build();

//        Call call = client.newCall(request);
//        call.enqueue(new Callback() {
//            @Override
//            public void onFailure(Call call, IOException e) {
//
//            }
//
//            @Override
//            public void onResponse(Call call, Response response) throws IOException {
//                System.out.println(response.body().string());
//            }
//        });
        Response response = client.newCall(request).execute();

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

简单的MultipartBody源码分析:

a.MultipartBody继承自RequestBody,是一个复杂的请求体类,他可以包含多个请求体和对应的头;

b.MultipartBody定义了Part内部类,Part用于存放添加的表单信息,Part定义的请求体和请求体对应的Headers;

    private Part(Headers headers, RequestBody body) {
      this.headers = headers;
      this.body = body;
    }

c.MultipartBody定义了private final List<Part> parts;变量存放添加的表单数据,说明可以添加多个请求体和对应的Headers;

d.MultipartBody调用所有的add...方法最终都会调用如下方法最终添加到parts中;

/** Add a part to the body. */
    public Builder addPart(Part part) {
      if (part == null) throw new NullPointerException("part == null");
      parts.add(part);
      return this;
    }

3.OkHttp定义的拦截器Interceptors

拦截器的作用:拦截器是一种强大的机制,可以监视、重写和重试调用。

拦截器分为(Application Interceptors)全局拦截器和Network Interceptors网络拦截器;

3.1全局拦截器:

a.定义拦截器

class LoggingInterceptor implements Interceptor {
  @Override public Response intercept(Interceptor.Chain chain) throws IOException {
    Request request = chain.request();
    //1.请求前--打印请求信息
    long t1 = System.nanoTime();
    logger.info(String.format("Sending request %s on %s%n%s",
        request.url(), chain.connection(), request.headers()));
    //2.执行网络请求
    Response response = chain.proceed(request);
    //3.网络响应后--打印响应信息
    long t2 = System.nanoTime();
    logger.info(String.format("Received response for %s in %.1fms%n%s",
        response.request().url(), (t2 - t1) / 1e6d, response.headers()));

    return response;
  }
}

b.全局拦截器添加,使用addInterceptor()方法添加拦截器

OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(new LoggingInterceptor())
    .build();

Request request = new Request.Builder()
    .url("http://www.publicobject.com/helloworld.txt")
    .header("User-Agent", "OkHttp Example")
    .build();

Response response = client.newCall(request).execute();
response.body().close();

这个URL地址http://www.publicobject.com/helloworld.txt重定向到https://publicobject.com/helloworld.txt地址, 并且OkHttp将跟随自动重定向。我们(application interceptor)全局拦截器只被调用一次并且chain.proceed()返回重定向响应:

INFO: Sending request http://www.publicobject.com/helloworld.txt on null
User-Agent: OkHttp Example

INFO: Received response for https://publicobject.com/helloworld.txt in 1179.7ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive

我们可以看到我们被重定向了,因为response.request().url()与request.url()不同。两个日志语句记录两个不同的url。

3.2网络拦截器:

a.添加拦截器方法addNetworkInterceptor()

OkHttpClient client = new OkHttpClient.Builder()
    .addNetworkInterceptor(new LoggingInterceptor())
    .build();

Request request = new Request.Builder()
    .url("http://www.publicobject.com/helloworld.txt")
    .header("User-Agent", "OkHttp Example")
    .build();

Response response = client.newCall(request).execute();
response.body().close();

当我们运行这段代码时,拦截器会运行两次。第一次是请求http://www.publicobject.com/helloworld.txt,第二次是重定向到https://publicobject.com/helloworld.txt。

INFO: Sending request http://www.publicobject.com/helloworld.txt on Connection{www.publicobject.com:80, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=none protocol=http/1.1}
User-Agent: OkHttp Example
Host: www.publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip

INFO: Received response for http://www.publicobject.com/helloworld.txt in 115.6ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/html
Content-Length: 193
Connection: keep-alive
Location: https://publicobject.com/helloworld.txt

INFO: Sending request https://publicobject.com/helloworld.txt on Connection{publicobject.com:443, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA protocol=http/1.1}
User-Agent: OkHttp Example
Host: publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip

INFO: Received response for https://publicobject.com/helloworld.txt in 80.9ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive

网络请求还包含更多的数据,比如Accept-Encoding:由OkHttp添加的gzip头来声明对响应压缩的支持。网络拦截器的链具有非空连接,可用于查询用于连接到web服务器的IP地址和TLS配置。

拦截器更多介绍:https://github.com/square/okhttp/wiki/Interceptors

4.其他

4.1推荐使用单例模式维护OkHttpClient对象,所有请求使用单例中创建的OkHttpClient发送网络请求,OkHttpClient自己会维护一套请求网络的线程池,不需要我们每次网络请求都创建OkHttpClient,每次都创建OkHttpClient会造成资源的浪费;

当然,也可以使用如下的方式来创建一个新的 OkHttpClient 实例,它们共享连接池、线程池和配置信息。

OkHttpClient eagerClient = client.newBuilder()
        .readTimeout(500, TimeUnit.MILLISECONDS)
        .build();
    Response response = eagerClient.newCall(request).execute();

4.2每一个Call(其实现是RealCall)只能执行一次,否则会报异常,具体参见 RealCall#execute()

 

参考:

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

https://www.jianshu.com/p/da4a806e599b

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值