OkHttp3使用(一)-基本使用

对整个OkHttp框架的介绍,会分为使用篇和源码分析篇两个部分进行介绍:
这里是使用篇的目录:
(一)-基本使用
(二)-常用类介绍
(三)-Interceptor

一、简介

OkHttp3(后续简称为OkHttp)是一个处理网络请求的开源库,由Square公司贡献。由于其高效的特性,所以非常流行。

1.1 优点

为什么其能被广泛的使用,并且有替代HttpUrlConnetion之势呢,这就不得不说其具有的几个优点:

  • 支持HTTP2(SPDY)/HTTPS(SPDY是Google开发的基于TCP的传输层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验,当前版本(3.10.0)已经移除了SPDY,但是HTTP2同SPDY有很多相似的特性。)
  • socket自动选择最好路线,并支持自动重连,拥有自动维护的socket连接池,减少握手次数,减少了请求延迟,共享Socket,减少对服务器的请求次数。
  • 基于Headers的缓存策略减少重复的网络请求。
  • 拥有Interceptors轻松处理请求与响应。
  • 提供了对 GZIP 的默认支持来降低传输内容的大小

1.2 整体设计思路(请求流程)

通过OkHttp进行网络请求的时候,一般会通过如下流程

在这里插入图片描述
这里列出流程,只是让大家心里大致有个印象就成。后面的文章会详细分析执行过程。

我们可以看到OkHttp的请求信息(如url,method,header等)都是封装在Request中的,而且也是通过Builder模式进行构建。

在我们发起请求后,通过一系列的Interceptor操作,最终会返回Response里面就封装了返回信息,里面包含了code, message,header,body等信息。

到这里,我们队OkHttp有了一个大致的认识后,就进入主题,怎么样去使用吧。

二、基本使用

2.1 引入库

compile 'com.squareup.okhttp3:okhttp:3.10.0'

2.2 构造OkHttpClint实例

OkHttpClient是一个生产Call的工厂,通过Call发起Http请求,并获取返回信息。
而且,绝大多数情况使用一个全局的单例OkHttpClient实例,便可以满足整个应用的Http请求。

这里创建OkHttpClient实例有三种方法:

  • 创建一个默认配置OkHttpClient,可以使用默认的构造函数,如下:
OkHttpClient client = new OkHttpClient();
  • 通过new OkHttpClient.Builder()方法来一步一步配置一个OkHttpClient实例。
new OkHttpClient.Builder()
            .addInterceptor(new TokenInterceptor())
            .addInterceptor(new CacheHeaderInterceptor())
            .addInterceptor(new loggingInterceptor)
            .cache(cache)
            .connectTimeout(DEFAULT_TIMEOUT.toLong(), TimeUnit.SECONDS)
            .build()

这里通过Builder添加了一些自定义的Interceptor、超时时间配置、缓存配置等。具体用法后续会有详细讲解。

  • 如果要求使用现有的实例,可以通过newBuilder()方法来进行构造。
OkHttpClient client = createClient(); // 模拟之前创建好的client
……
// 由于某个接口需要重新设置超时时间(其他配置不变)
client.newBuilder()
        .connectTimeout(DEFAULT_TIMEOUT.toLong(), TimeUnit.SECONDS)

上述例子中,假设之前通过createClient()方法已经创建了OkHttpClient实例,但是某个接口需要单独设置超时时间,但是其他配置不变,这个时候,我们不需要重新再去配置以便,只需要通过client.newBuilder()方法就能获取Builder对象,并且复用之前的配置,然后再重新配置需要单独处理的配置项即可。

HttpClient创建好之后,就可以发起请求了,后续的例子中很多都是用的官方示例,所以推荐大家还是多看看官方文档

2.3 发起GET请求

public class GetExample {
  OkHttpClient client = new OkHttpClient();

  String run(String url) throws IOException {
    Request request = new Request.Builder()
        .url(url)
        .build();

    try (Response response = client.newCall(request).execute()) {
      return response.body().string();
    }
  }

  public static void main(String[] args) throws IOException {
    GetExample example = new GetExample();
    String response = example.run("https://raw.github.com/square/okhttp/master/README.md");
    System.out.println(response);
  }
}

这里直接通过new OkHttpClient创建Client对象后,通过newCall(request)获得Call对象,然后通过excute()方法执行请求,获得返回结果。
这里的client.newCall(request).execute()调用就是OkHttpClient发起请求的过程了。

2.4 发起POST请求

POST请求和GET请求,请求的流程都一样,都是通过client.newCall(request).execute()发起请求。只是在唯一的差别是在构建Request的时候。对比如下:

// GET请求
Request request = new Request.Builder()
        .url(url)
        .build();
// POST请求
Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody)) // 通过post方法讲过请求体封装进Request中。
        .build();

接下来我们就针对平时开发中使用POST提交的常用数据格式分别介绍POST请求。

2.4.1 提交字符串

public final class PostString {
  public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.get("text/x-markdown; charset=utf-8");

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    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(MEDIA_TYPE_MARKDOWN, postBody)) 
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
      System.out.println(response.body().string());
    }
  }

  public static void main(String... args) throws Exception {
    new PostString().run();
  }
}

这里通过RequestBody.create()方法创建请求体。这里第一个参数是MediaType类型,这个就是HTPP协议的知识了,这里不展开说,可以通过MediaType.get()方法获得。
另外,create()方法有多个重载方法,也很简单,这里就不展开介绍了,感兴趣的朋友可以自己去看看。
其实POST请求的整体流程就是这样,

  1. 通过MediaType.get()获取到合适的MediaType类型
  2. 通过RequestBody.create()方法,创建请求体(RequestBody)
  3. 通过Request.Builderpost()方法,将请求体封装进Request中。
  4. 最后通过Client发起请求即可。
    后续如果没有其他特殊知识点,就不再展开说明了,相信大家看一遍代码就理解了。

2.4.2 提交表单

public final class PostForm {
  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    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();

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

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

  public static void main(String... args) throws Exception {
    new PostForm().run();
  }
}

这里对比提交字符串,更显简单,直接通过FormBody.Builder()来构建表单提交的内容,然后post提交即可。

2.4.3 提交流

public final class PostStreaming {
  public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.get("text/x-markdown; charset=utf-8");

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    RequestBody requestBody = new RequestBody() {
    	// 返回合适的MediaType
      @Override public MediaType contentType() {
        return MEDIA_TYPE_MARKDOWN;
      }
		
	// 重写该方法,将需要提交的流写入sink中(这里使用Okio的东西,不熟悉的请自行百度)
      @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();

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

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

  public static void main(String... args) throws Exception {
    new PostStreaming().run();
  }
}

流的提交方式稍显复杂一点,一把我们需要自定义一个RequestBody,然后重写RequestBody中的几个方法,具体看上述代码和注释。代码中是直接采用匿名类的形式,你也可以按照你自己的方式自定义。最后也是通过post提交即可。
这里既然可以直接提交流,所以也就可以通过该方式上传文件了。我们只是需要将文件流写入sink即可。
但是该方式上传文件有个注意点,由于该方法会将所有的流写入内存,所以不能上传大文件

2.4.4 分块提交

public final class PostMultipart {
  /**
   * The imgur client ID for OkHttp recipes. If you're using imgur for anything other than running
   * these examples, please request your own client ID! https://api.imgur.com/oauth2
   */
  private static final String IMGUR_CLIENT_ID = "9199fdef135c122";
  private static final MediaType MEDIA_TYPE_PNG = MediaType.get("image/png");

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    // 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();

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

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

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

  public static void main(String... args) throws Exception {
    new PostMultipart().run();
  }
}

也比较简单,通过MultipartBody.Builder()添加不同部分的请求体,最后通过post提交即可。

2.4.5 提交文件

public final class PostFile {
  public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.get("text/x-markdown; charset=utf-8");

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    File file = new File("README.md");

    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
        .build();

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

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

  public static void main(String... args) throws Exception {
    new PostFile().run();
  }
}

起始在上面提交分块提交中,已经使用到了提交文件的东西。就是通过RequestBody.create()这个重载方法(第二个参数是File类型)来创建RequestBody对象,然后通过post提交即可。
这里只是上传了文件,如果你需要制制定该文件流的key的话,还需要通过以下方式进行封装:

MultipartBody.Part.createFormData(String name, @Nullable String filename, RequestBody body)

这里的第一个参数就是你需要为该文件流指定的paramKey, 第二个参数是文件名,第三个参数就是通过RequestBody.create(MEDIA_TYPE_MARKDOWN, file)方法创建的RequestBody对象了。
如果你在上传文件的时候,同时还需要提交其他普通参数,就可以通过2.4.4的分块提交的方式了。

2.5 设置请求头

HTTP 头的数据结构是Map<String, List<String>>类型。也就是说,对于每个 HTTP 头,可能有多个值。但是大部分 HTTP 头都只有一个值,只有少部分 HTTP 头允许多个值。至于name的取值说明,可以自行搜索,网上有很多资料。

OkHttp的处理方式是:

使用header(name,value)来设置HTTP头的唯一值,如果请求中已经存在响应的信息那么直接替换掉。
使用addHeader(name,value)来补充新值,如果请求头中已经存在name的name-value,那么还会继续添加,请求头中便会存在多个name相同而value不同的“键值对”。
使用header(name)读取唯一值或多个值的最后一个值
使用headers(name)获取所有值

2.6 异步请求和同步请求

我们在上面GET和POST请求方式的介绍中,都是直接使用excute()方式发起请求,这个就是OkHttp中的同步请求方式。我们实际开发中大多数时候都是需要异步的,异步请求非常简单,只需要将excute()方法变为enqueue()即可,具体使用参考:

public final class AsynchronousGet {
  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://publicobject.com/helloworld.txt")
        .build();

    client.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 {
        try (ResponseBody responseBody = response.body()) {
          if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

          Headers responseHeaders = response.headers();
          for (int i = 0, size = responseHeaders.size(); i < size; i++) {
            System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
          }

          System.out.println(responseBody.string());
        }
      }
    });
  }

  public static void main(String... args) throws Exception {
    new AsynchronousGet().run();
  }
}

这里也有个注意点,虽然是异步的,但是其回调并不一定在主线程哦,所以我们在Android开发的时候要特别注意这个问题,不要在回调线程中直接进行UI操作。

这里我们的基本使用就介绍完了,上面在使用的时候涉及到了很多类如(Request,RequestBody,Response,ResponseBody)等,下篇文章就会进步对其一些使用过程中涉及到重要的类做一下说明。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值