[关闭]
@cxm-2016 2016-12-25T12:49:06.000000Z 字数 7279 阅读 4725

OkHttp3 (二)——请求

OkHttp3

版本:3
作者:陈小默
声明:禁止商业,禁止转载

发布于:作业部落简书CSDN



请求对象

在OkHttp3中,所有的网络请求都由一个Request对象指定,而客户端的任务就是执行这个请求。Request对象不能被直接创建,必须通过Builder模式构建。这个请求对象在网络访问过程中的作用有四:

  1. 指定访问地址:无论何种网络访问,我们都需要知道对方的URL地址。
  2. 指定请求方式:常见的请求方式有GET,POST,PUT,DELETE,PATCH等。
  3. 指定请求头:对于大部分场景,我们需要在请求头中声明例如 字符集(Accept-Charset)接受的压缩格式(Accept-Encoding)语言环境(Accept-Language)是否保持连接(Connection) 等信息。
  4. 携带请求数据:在某些请求场景中,例如:POST,我们可能需要向服务器提交一些资源,这些资源就可以被存放在请求对象中。

Get请求

一般来说创建一个Get请求的过程如下所示,这里服务器是我自己搭建的返回,其会返回一段json字符串:

  1. val HOST = "http://192.168.1.112:8080/smart"
  2. val GET = "$HOST/okhttp/get?message=我是客户端"
  3. fun main(args: Array<String>) {
  4. val client = OkHttpClient()
  5. val request = Request.Builder()
  6. .url(GET)
  7. .get()
  8. .build()
  9. val call = client.newCall(request)
  10. val response = call.execute()
  11. if (response.isSuccessful) {
  12. val body = response.body()
  13. val string = body.string()
  14. println(string)
  15. }
  16. response.close()
  17. }

由于GET请求是OkHttp的默认请求方式,所以可以忽略get声明

  1. val request = Request.Builder()
  2. .url(NARUTO)
  3. .build()

执行这段方法,得到响应如下:

{"success":true,"message":"我是客户端","data":"服务器接收到了客户端的来信"}

Post请求

当我们进行Post请求时,OkHttp要求我们提交一个RequestBody对象。

RequestBody及其子类

我们查看RequestBody的源代码可以发现这些静态方法,其中MediaType表示这个参数的数据类型,比如"text","image", "audio", "video", 或者 "application"等等。

  1. public static RequestBody create(MediaType contentType, String content);
  2. public static RequestBody create(final MediaType contentType, final ByteString content);
  3. public static RequestBody create(final MediaType contentType, final byte[] content);
  4. public static RequestBody create(final MediaType contentType, final byte[] content, final int offset, final int byteCount)
  5. public static RequestBody create(final MediaType contentType, final File file);

但是,通常情况下我们并不会直接使用这些create方法来创建一个RequestBody对象。而是使用RequestBody的子类。它的两个直接子类如下:

  • FormBody
  • MultipartBody

FormBody与表单提交

首先我们先看一下直接使用RequestBody来提交参数的方法:

  1. val POST_FORM = "$HOST/okhttp/post/form"
  2. fun main(args: Array<String>) {
  3. val client = OkHttpClient()
  4. val requestBody = RequestBody.create(MediaType.parse("text"), "password=123456&username=Chen-XiaoMo")
  5. val request = Request.Builder()
  6. .url(POST_FORM)
  7. .post(requestBody)
  8. .build()
  9. val call = client.newCall(request)
  10. val response = call.execute()
  11. if (response.isSuccessful) {
  12. val body = response.body()
  13. val string = body.string()
  14. println(string)
  15. }
  16. response.close()
  17. }

直接使用RequestBody提交参数的方式看起来并不是那么的直观,并且修改起来也不是很方便。RequestBody有一个直接子类FormBody,是专门用来提交表单数据类。那么上述设置请求体的代码就变成了Key-Value的形式:

  1. val requestBody = FormBody.Builder()
  2. .add("username", "Chen-XiaoMo")
  3. .add("password", "123456")
  4. .build()

上述程序运行后可以得到服务器返回的数据

{"success":true,"message":"登录成功","data":"Chen-XiaoMo"}

MultipartBody与文件上传

现在有这么一个需求,本地有一张图片,需要上传给服务器,那么如何使用OkHttp实现?

  1. val POST_IMAGE = "$HOST/okhttp/post/image"
  2. val FILE_IMAGE = "$LOCAL_PATH/Naruto.jpg"
  3. fun main(args: Array<String>) {
  4. val client = OkHttpClient()
  5. val requestBody = MultipartBody.Builder()
  6. //携带一个表单参数
  7. .addFormDataPart("username", "Chen-XiaoMo")
  8. //设置参数名、文件名和文件
  9. .addFormDataPart("image", "Naruto.jpg", RequestBody.create(MediaType.parse("image"), File(FILE_IMAGE)))
  10. .build()
  11. val request = Request.Builder()
  12. .url(POST_IMAGE)
  13. .post(requestBody)
  14. .build()
  15. val call = client.newCall(request)
  16. val response = call.execute()
  17. if (response.isSuccessful) {
  18. val body = response.body()
  19. val string = body.string()
  20. println(string)
  21. }
  22. response.close()
  23. }

我们可以注意到MultipartBody.addFormDataPart有两个重载方法

  1. public Builder addFormDataPart(String name, String value);
  2. public Builder addFormDataPart(String name, String filename, RequestBody body);

第一个方法从参数列表可以看出,它和FormBody的add方法一样,用来提交表单数据。

当需要提交文件的时候,我们就会用到第二个重载方法了。

  1. RequestBody.create(MediaType.parse("image"), File(FILE_IMAGE))

通常情况下MediaType.parse("image")里的参数需要和后台保持一致,但这里由于我自己的后台允许接受任何类型的数据,所以就直接写了"image"

带进度上传文件

有时候,我们需要上传一些比较大的文件,这可能会占用相当长的时间,一款交互友好的产品一定会为用户显示当前的进度,以免让用户盲目等地。

1,创建回调接口
我们需要一个能够回调当前状态的接口,就像下面这样:

  1. /**
  2. * OkHttp3文件上传时回调接口。
  3. * @author cxm
  4. */
  5. interface ProgressListener {
  6. /**
  7. * 上传开始时回调
  8. */
  9. fun onStart()
  10. /**
  11. * 上传过程中回调
  12. * @param current 当前上传的大小
  13. * @param max 文件的总大小
  14. */
  15. fun progress(current: Long, max: Long)
  16. /**
  17. * 上传完成时回调
  18. */
  19. fun onComplete()
  20. /**
  21. * 上传过程中出现错误时回调
  22. * @param e 异常对象
  23. */
  24. fun onError(e: Exception)
  25. }

2,自定义RequestBody类

  1. /**
  2. * 带有进度回调功能的RequestBody类
  3. * @param contentType 数据类型
  4. * @param file 上传的文件
  5. * @param listener 回调监听对象
  6. * @author cxm
  7. */
  8. class ProgressRequestBody(private val contentType: MediaType?,
  9. private val file: File,
  10. private val listener: ProgressListener) : RequestBody() {
  11. override fun contentType(): MediaType? {
  12. return contentType
  13. }
  14. override fun contentLength(): Long {
  15. return file.length()
  16. }
  17. override fun writeTo(sink: BufferedSink) {
  18. try {
  19. val max = contentLength()
  20. var current = 0L
  21. listener.onStart()
  22. //每上传100KB数据就回调一次
  23. file.forEachBlock(100 * 1024, { bytes, count ->
  24. current += count
  25. listener.progress(current, max)
  26. sink.write(bytes, 0, count)
  27. })
  28. listener.onComplete()
  29. } catch (e: Exception) {
  30. listener.onError(e)
  31. }
  32. }
  33. }

3,使用自定义的RequestBody对象上传文件

  1. fun main(args: Array<String>) {
  2. val client = OkHttpClient()
  3. val listener = object : ProgressListener {
  4. override fun onStart() {
  5. println("开始上传")
  6. }
  7. override fun progress(current: Long, max: Long) {
  8. println("当前进度:${(current * 100 / max)}%")
  9. }
  10. override fun onComplete() {
  11. println("上传完成")
  12. }
  13. override fun onError(e: Exception) {
  14. e.printStackTrace()
  15. }
  16. }
  17. val requestBody = MultipartBody.Builder()
  18. .addFormDataPart("username", "Chen-XiaoMo")
  19. .addFormDataPart("image", "Naruto.jpg",
  20. ProgressRequestBody(MediaType.parse("image"),
  21. File(FILE_IMAGE), listener))
  22. .build()
  23. val request = Request.Builder()
  24. .url(POST_IMAGE)
  25. .post(requestBody)
  26. .build()
  27. val call = client.newCall(request)
  28. val response = call.execute()
  29. if (response.isSuccessful) {
  30. val body = response.body()
  31. val string = body.string()
  32. println(string)
  33. }
  34. response.close()
  35. }

当提交一个880K的图像文件到服务器时,结果显示如下:

开始上传
当前进度:11%
当前进度:23%
当前进度:34%
当前进度:46%
当前进度:57%
当前进度:69%
当前进度:80%
当前进度:92%
当前进度:100%
上传完成
{"success":true,"message":"文件上传成功","data":"cd734d6ef6637788070c6274cb8d2076"}

设置请求头

请求头在一次Http访问中的作用是相当重要的,一般使用请求头来向服务器说明客户端的信息,比如可接受的编码、可使用的压缩方式、客户端语言环境、本次访问的来源、客户端时间等等。那么在OkHttp中应该如何设置请求头呢?当我们创建一个Request.Builder对象的时候,我们可以看到其构造方法有这么一段代码:

  1. String method;
  2. Headers.Builder headers;
  3. public Builder() {
  4. this.method = "GET";
  5. this.headers = new Headers.Builder();
  6. }

一个请求建造者对象在创建时已经默认请求方式为“GET”了,并且初始化了一个Headers.Builder对象。而Headers.Builder内部主要就是封装了一个保存请求头Key-Value的链表。那么我们设置请求头的方式主要有这几种:

1,addHeader

这是最常用的一种添加请求头的方法

  1. val request = Request.Builder()
  2. .url(POST_IMAGE)
  3. .addHeader("Accept-Charset","utf-8")
  4. .addHeader("Accept-Language","en-us,zh-ch")
  5. .post(requestBody)
  6. .build()

来看看addHeader的源码:

  1. // Request.Builder.class
  2. public Builder addHeader(String name, String value) {
  3. headers.add(name, value);
  4. return this;
  5. }

这里调用了Headers.Builder类中的add方法:

  1. // Headers.Builder.class
  2. public Builder add(String name, String value) {
  3. checkNameAndValue(name, value); //检查参数是否正确
  4. return addLenient(name, value); //将name和value先后保存在Headers.Builder中的List中
  5. }

2,header

  1. val request = Request.Builder()
  2. .url(POST_IMAGE)
  3. .header("Accept-Charset","utf-8")
  4. .header("Accept-Language","en-us,zh-ch")
  5. .post(requestBody)
  6. .build()

乍一看,headeraddHeader方法好像作用是一样的,因为其参数看起来都是一样的。至于具体有什么不一样的地方,让我们来看一下源码:

  1. // Request.Builder.class
  2. public Builder header(String name, String value) {
  3. headers.set(name, value);
  4. return this;
  5. }

很明显,这里调用的headers的set方法,而不是add:

  1. // Headers.Builder.class
  2. public Builder set(String name, String value) {
  3. checkNameAndValue(name, value); //检查参数是否正确
  4. removeAll(name); //从List中移除其他名为name的请求头信息
  5. addLenient(name, value); //将name和value先后保存在Headers.Builder中的List中
  6. return this;
  7. }

看出来了吧! set与add相比,多了一步移除的过程。

3,headers

  1. val headers = Headers.Builder()
  2. .add("Accept-Charset","utf-8")
  3. .add("Accept-Language:en-us,zh-ch")//使用:作为分隔符
  4. .set("Accept-Encoding","gzip,compress")
  5. .build()
  6. val request = Request.Builder()
  7. .url(POST_IMAGE)
  8. .headers(headers)
  9. .post(requestBody)
  10. .build()

需要注意的是,这里的headers并不是将一个Headers对象添加到已有的请求头对象中:

  1. //Request.Builder
  2. public Builder headers(Headers headers) {
  3. this.headers = headers.newBuilder();
  4. return this;
  5. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注