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

指定访问地址:无论何种网络访问,我们都需要知道对方的URL地址。
指定请求方式:常见的请求方式有GET,POST,PUT,DELETE,PATCH等。
指定请求头:对于大部分场景,我们需要在请求头中声明例如 字符集(Accept-Charset)、接受的压缩格式(Accept-Encoding)、语言环境(Accept-Language)、是否保持连接(Connection) 等信息。
携带请求数据:在某些请求场景中,例如:POST,我们可能需要向服务器提交一些资源,这些资源就可以被存放在请求对象中。
Get请求
一般来说创建一个Get请求的过程如下所示,这里服务器是我自己搭建的返回,其会返回一段json字符串:

val HOST = "http://192.168.1.112:8080/smart"
val GET = "$HOST/okhttp/get?message=我是客户端"

fun main(args: Array<String>) {
    val client = OkHttpClient()
    val request = Request.Builder()
            .url(GET)
            .get()
            .build()
    val call = client.newCall(request)
    val response = call.execute()
    if (response.isSuccessful) {
        val body = response.body()
        val string = body.string()
        println(string)
    }
    response.close()
}

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

val request = Request.Builder()
            .url(NARUTO)
            .build()

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

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

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

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

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

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

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

val POST_FORM = "$HOST/okhttp/post/form"

fun main(args: Array<String>) {
    val client = OkHttpClient()

val requestBody = RequestBody.create(MediaType.parse("text"), "password=123456&username=Chen-XiaoMo")

val request = Request.Builder()
            .url(POST_FORM)
            .post(requestBody)
            .build()
    val call = client.newCall(request)
    val response = call.execute()
    if (response.isSuccessful) {
        val body = response.body()
        val string = body.string()
        println(string)
    }
    response.close()
}

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

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

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

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

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

val POST_IMAGE = "$HOST/okhttp/post/image"
val FILE_IMAGE = "$LOCAL_PATH/Naruto.jpg"

fun main(args: Array<String>) {
    val client = OkHttpClient()

val requestBody = MultipartBody.Builder()
            //携带一个表单参数
            .addFormDataPart("username", "Chen-XiaoMo")
            //设置参数名、文件名和文件
            .addFormDataPart("image", "Naruto.jpg", RequestBody.create(MediaType.parse("image"), File(FILE_IMAGE)))
            .build()

val request = Request.Builder()
            .url(POST_IMAGE)
            .post(requestBody)
            .build()
    val call = client.newCall(request)
    val response = call.execute()
    if (response.isSuccessful) {
        val body = response.body()
        val string = body.string()
        println(string)
    }
    response.close()
}

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

public Builder addFormDataPart(String name, String value);

public Builder addFormDataPart(String name, String filename, RequestBody body);

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

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

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

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

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

/**
 * OkHttp3文件上传时回调接口。
 * @author cxm
 */
interface ProgressListener {
    /**
     * 上传开始时回调
     */
    fun onStart()

/**
     * 上传过程中回调
     * @param current 当前上传的大小
     * @param max 文件的总大小
     */
    fun progress(current: Long, max: Long)

/**
     * 上传完成时回调
     */
    fun onComplete()

/**
     * 上传过程中出现错误时回调
     * @param e 异常对象
     */
    fun onError(e: Exception)
}

2,自定义RequestBody类

/**
 * 带有进度回调功能的RequestBody类
 * @param contentType 数据类型
 * @param file 上传的文件
 * @param listener 回调监听对象
 * @author cxm
 */
class ProgressRequestBody(private val contentType: MediaType?,
                          private val file: File,
                          private val listener: ProgressListener) : RequestBody() {
    override fun contentType(): MediaType? {
        return contentType
    }

override fun contentLength(): Long {
        return file.length()
    }

override fun writeTo(sink: BufferedSink) {
        try {
            val max = contentLength()
            var current = 0L
            listener.onStart()
            //每上传100KB数据就回调一次
            file.forEachBlock(100 * 1024, { bytes, count ->
                current += count
                listener.progress(current, max)
                sink.write(bytes, 0, count)
            })
            listener.onComplete()
        } catch (e: Exception) {
            listener.onError(e)
        }
    }
}

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

fun main(args: Array<String>) {
    val client = OkHttpClient()

val listener = object : ProgressListener {
        override fun onStart() {
            println("开始上传")
        }

override fun progress(current: Long, max: Long) {
            println("当前进度:${(current * 100 / max)}%")
        }

override fun onComplete() {
            println("上传完成")
        }

override fun onError(e: Exception) {
            e.printStackTrace()
        }
    }

val requestBody = MultipartBody.Builder()
            .addFormDataPart("username", "Chen-XiaoMo")
            .addFormDataPart("image", "Naruto.jpg",
                    ProgressRequestBody(MediaType.parse("image"),
                            File(FILE_IMAGE), listener))
            .build()

val request = Request.Builder()
            .url(POST_IMAGE)
            .post(requestBody)
            .build()
    val call = client.newCall(request)
    val response = call.execute()
    if (response.isSuccessful) {
        val body = response.body()
        val string = body.string()
        println(string)
    }
    response.close()
}

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

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

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

String method;
    Headers.Builder headers;

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

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

1,addHeader

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

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

来看看addHeader的源码:

// Request.Builder.class

public Builder addHeader(String name, String value) {
      headers.add(name, value);
      return this;
    }

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

// Headers.Builder.class

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

2,header

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

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

// Request.Builder.class

public Builder header(String name, String value) {
      headers.set(name, value);
      return this;
    }

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

// Headers.Builder.class

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

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

3,headers

val headers = Headers.Builder()
            .add("Accept-Charset","utf-8")
            .add("Accept-Language:en-us,zh-ch")//使用:作为分隔符
            .set("Accept-Encoding","gzip,compress")
            .build()

val request = Request.Builder()
            .url(POST_IMAGE)
            .headers(headers)
            .post(requestBody)
            .build()

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

//Request.Builder

public Builder headers(Headers headers) {
      this.headers = headers.newBuilder();
      return this;
    }

OkHttp3 (二)——请求相关推荐

  1. Android Okhttp3 (二) 二次封装请求管理类

    okHttp介绍 通过上面的对比说明,让你不得不做出明智的选择,OkHttp是一个相对成 官网地址:http://square.github.io/okhttp/ 官方API地址:http://m.b ...

  2. Android --- Retrofit 之 Okhttp3 网络请求总是调用 onFailure 方法,而不调用 onResponse,报错 timeout。

    今天在做 Android 项目的时候使用到了 okhttp3 的网络请求,由于我没有设置 ReadTimeout,指的是建立连接后从服务器读取到可用资源所用的时间.所以就会抛出异常(timeout), ...

  3. OkHttp3 HTTP请求执行流程分析

    OkHttp3的基本用法 使用OkHttp3发送Http请求并获得响应的过程大体为: 创建OkHttpClient对象.OkHttpClient为网络请求执行的一个中心,它会管理连接池,缓存,Sock ...

  4. okhttp3发送请求

    引入okhttp3的依赖 <dependencies><!--ok3http--><dependency><groupId>com.squareup.o ...

  5. Android 9.0/P(android p指安卓9.0版本) okhttp3网络请求出错

    已经在AndroidManifest.xml申请网络权限,在8.0以下的系统中网络访问正常,但是9.0中出现网络请求失败 Android 9.0的网络请求失败如下图: 出现这个错误的原因是:从Andr ...

  6. 初探ZeroMQ(二) 请求-应答模式中套结字总结

    参考资料:ØMQ - The Guide(英文) 参考资料:ØMQ - The Guide(中文) 本文主要介绍和总结在请求-应答模式中各种套结字的行为. 套结字简介 来点通俗易懂的,先认识下请求-应 ...

  7. RxJava2+Retrofit2+RxLifecycle3+OkHttp3网络请求封装(动态演示)

    入职公司后,公司要求组件化开发,经过讨论后我将网络请求框架单独进行了封装,不过当时框架里将常用的 util 和 ui 均放入到了共同的 Common 包下,导致里面部分代码耦合,后来为了降低耦合性又将 ...

  8. Servlet--基本二(请求对象,文件下载)

    请求对象 1. 获取请求行数据 getMethod()获取请求方式 getContextPath()获取虚拟目录 2. 获取请求头数据 getHeader(请求头) 获取指定的请求头中的数据 3. 获 ...

  9. Ocelot(二)- 请求聚合与负载均衡

    Ocelot(二)- 请求聚合与负载均衡 作者:markjiang7m2 原文地址:http://letyouknow.net/ocelot/ocelot-tutorial-2.html 源码地址:h ...

  10. kafka请求全流程(二)—— 请求的接收以及分发

    承接上一篇(https://blog.csdn.net/fenglei0415/article/details/106162288) 二. 请求的接收以及分发 主要分析两个类,实现网络通信的关键部件. ...

最新文章

  1. 点分治问题 ----------- 2017杭州CCPC E.Master of Subgraph[bitset+点分治]
  2. offset/client/scroll一些总结
  3. Java 原生日志 java.util.logging
  4. 公司的一些SEO面试题
  5. JavaScript中的arguments,callee,caller
  6. 全球最囧的爱情测试.....
  7. 给年薪不到48w的程序员提个醒!!
  8. python RSA加密、解密、签名
  9. HBase regions分布不均匀的解决
  10. 【Unity新闻】Unity发布白皮书《11种有效的玩家参与策略》
  11. oracle19c配置scott创建,oracle 19c创建sample schema-HR,OE,SH等等
  12. 如何给数组用fill函数和memset函数给数组赋初值
  13. 好久没更新了,更新一篇,关于ZEC的吧
  14. 学习一下什么是SRE和DevOps
  15. 关于如何职业规划和选择换工作
  16. 追捕文件WRY.DLL的浅显分析及程序示例
  17. 军哥独家QCIE(囊括CCIE和HCIEv3.0)的全新课程。请大家参阅
  18. linux压缩后删原文件夹,Linux tar 如何在压缩之后删除原文件及相关拓展用法
  19. #### mysql联合索引 注意事项 ####
  20. python 工资管理软件_使用Python计算公司所有员工的薪资报表,核算

热门文章

  1. u盘克隆服务器系统,只需4步!简单又快速的克隆U盘
  2. Html 实现amr文件播放
  3. 有些事现在不做,一辈子都不会做了
  4. Aqara绿米董事长游延筠专访:以用户体验为出发点,打造更懂你的家
  5. 项目中使用docker部署xxl-job
  6. BUUCTF Reverse reverse3 WriteUp
  7. “校长”潘淳:侠之大者,一蓑烟雨任平生
  8. MarkDown表格合并
  9. 现代计算机专业版官网,现代计算机期刊_投稿邮箱_杂志_编辑部_审稿_投稿须知...
  10. win7系统备份还原软件_傲梅轻松备份bug导致系统还原0x81000203错误,