@cxm-2016
2016-12-25T12:49:06.000000Z
字数 7279
阅读 5429
OkHttp3
版本:3
作者:陈小默
声明:禁止商业,禁止转载
在OkHttp3中,所有的网络请求都由一个Request对象指定,而客户端的任务就是执行这个请求。Request对象不能被直接创建,必须通过Builder模式构建。这个请求对象在网络访问过程中的作用有四:
GET,POST,PUT,DELETE,PATCH等。字符集(Accept-Charset)、接受的压缩格式(Accept-Encoding)、语言环境(Accept-Language)、是否保持连接(Connection) 等信息。一般来说创建一个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请求时,OkHttp要求我们提交一个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
首先我们先看一下直接使用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"}
现在有这么一个需求,本地有一张图片,需要上传给服务器,那么如何使用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))
通常情况下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 = 0Llistener.onStart()//每上传100KB数据就回调一次file.forEachBlock(100 * 1024, { bytes, count ->current += countlistener.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.classpublic Builder addHeader(String name, String value) {headers.add(name, value);return this;}
这里调用了Headers.Builder类中的add方法:
// Headers.Builder.classpublic 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.classpublic Builder header(String name, String value) {headers.set(name, value);return this;}
很明显,这里调用的headers的set方法,而不是add:
// Headers.Builder.classpublic 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.Builderpublic Builder headers(Headers headers) {this.headers = headers.newBuilder();return this;}