OkHttp的基本用法
看完Google官方的网络通信库Volley,再继续学习Android开发中大名鼎鼎的网络请求框架:OkHttp。深入学习一个优秀的框架之前,先了解它的基本用法,由浅入深,逐步探索。本篇简要介绍一下OkHttp的使用,关于拦截器详见OkHttp拦截器Interceptor。
1、OkHttp
OkHttp是Square公司开源的网络请求框架,Android开发中另一个网络请求框架Retrofit就是基于OkHttp二次封装。
1.1、介绍
OkHttp是一个支持HTTP 和 HTTP/2 的客户端,可以在Android和Java应用程序中使用,其具有以下特点:
1. API设计轻巧,基本上通过几行代码的链式调用即可获取结果。
2. 既支持同步请求,也支持异步请求。同步请求会阻塞当前线程,异步请求不会阻塞当前线程,异步执行完成后执行相应的回调方法。
3. 其支持HTTP/2协议,通过HTTP/2,可以让客户端中到同一服务器的所有请求共用同一个Socket连接。
4. 如果请求不支持HTTP/2协议,那么Okhttp会在内部维护一个连接池, 通过该连接池,可以对HTTP/1.x的连接进行重用,减少了延迟。
5. 透明的GZIP处理降低了下载数据的大小。
6. 请求的数据会进行相应的缓存处理,下次再进行请求时,如果服务器告知304(表明数据没有发生变化),那么就直接从缓存中读取数据,降低了重复请求的数量。
其它废话就不多粘贴了,详见官方文档:
官方指南:OkHttp Overview
GitHub:square/okhttp
1.2、准备
在模块的build.gradle中添加OkHttp依赖,用最新的稳定版本,开源库的查找参考添加三方开源库的正确方式一文。
//OkHttp
implementation 'com.squareup.okhttp3:okhttp:4.9.1'
使用网络别忘了在AndroidManifest.xml中添加应用网络访问权限,这是新手容易忽略的小坑。
<uses-permission android:name="android.permission.INTERNET" />
接下来开始了解OkHttp最常用的两种请求:GET请求和POST请求。
2、GET请求
网络请求通过OkHttp提供的OkHttpClient发起,先创建一个OkHttpClient实例。这里展示用法,实际项目中不要创建多个OkHttpClient实例(应使用单例)。
//直接创建默认OkHttpClient
val okHttpClient: OkHttpClient = OkHttpClient()
//使用OkHttpClient.Builder构造
val okHttpClient: OkHttpClient = OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.build()
2.1、构造Request
使用Request的内部类Builder构造网络请求,Request默认请求方法是GET,可以调用get()或者method("GET" , requestBody)方法设置请求为GET。
//网络请求Request通过内部类Request.Builder建造者模式创建
val requestBuilder: Request.Builder = Request.Builder()
requestBuilder.url(url)
//请求方法设置为GET,RequestBody请求体空。也可用get()进行设置,内部实现是一样的
requestBuilder.method("GET", null)
//从Request.Builder创建Request实例
val request: Request = requestBuilder.build()
OkHttp中很多类的实例(比如:FormBody、MultipartBody、Headers等)都是通过内部Builder类构造。本篇只简单了解一下,后面会专门深入源码研究。
2.2、准备请求Call
借助本节开头创建的OkHttpClient实例,调用newCall(Request )方法获取网络请求Call接口类型的实例,每个Call实例代表单个响应流,只能执行一次请求,实现类是RealCall。
//通过第一步创建的OkHttppClient创建网络Call对象
val call: Call = okHttpClient.newCall(request)
通过Call发起最终网络请求,有两种方式:异步enqueue(Callback)和同步的execute()。
2.3、异步GET:enqueue()
拿到已准备好执行的请求Call,调用enqueue(Callback)方法提交异步网络请求。请求会被放到OkHttpClient的Dispatcher中调度执行,网络请求完之后后执行Callback回调。
//调用Call的enqueue(Callback )方法异步请求,在回调中处理网络请求结果
call.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
//网络请求失败回调
}
override fun onResponse(call: Call, response: Response) {
//处理网络请求响应
}
})
2.4、同步GET:execute()
同步的网络请求必须在子线程中进行,因为是耗时的网络操作。执行请求Call的execute()方法获取网络请求返回的结果Response。
//调用Call的execute()方法同步请求,获取返回的Response
Thread {
try {
call.execute().use {
//处理网络结果Response
val data = it.body
}
} catch (e: Exception) {
Log.e(TAG, "${e.message}")
}
}.start()
使用完要关闭Response避免内存泄漏,这里使用Closeable的扩展函数use{},方法执行完后自动关闭资源。
3、POST请求
POST提交请求和GET差不多,POST请求需要了解RequestBody的构造,POST请求携带不同的RequestBody以上传不同的数据内容给服务端。在这节简单了解一下RequestBody请求体,关于OkHttp的RequestBody详见OkHttp之RequestBody请求体。
3.1、RequestBody
POST向服务端提交数据,离不开网络请求的数据载体:RequestBody。在该抽象类中使用Kotlin实现了很多扩展方法方便开发者从String、File、ByteArray中创建RequestBody实例。
抽象类RequestBody有两个实现类:FormBody和MultipartBody,方便开发者提交表单数据和分块数据。
3.2、POST文本
从String文本直接通过扩展函数toRequestBody(MediaType )构造RequestBody,传入MediaType指明数据内容的MIME类型。关于MIME详见了解媒体类型MIME。
//创建数据的MIME类型,关于MIME参考另一博客
val stringMediaType: MediaType = "text/plain".toMediaType()
//直接通过String扩展函数构造对应的RequestBody
val stringRequestBody: RequestBody = "string from client".toRequestBody(stringMediaType)
//简单快速的构造一个文本POST请求
val request: Request = Request.Builder()
.url(url)
.post(stringRequestBody)
.build()
val call: Call = client.newCall(request)
//处理POST
try {
call.execute().use {
}
} catch (e: Exception) {
}
3.3、POST表单
通过FormBody内部Builder类构造表单,携带key-value表单数据。
val requestBodyBuilder: FormBody.Builder = FormBody.Builder()
requestBodyBuilder.add("keywords", "search")
val requestBody: RequestBody = requestBodyBuilder.build()
Request.Builder构造POST请求,调用post(Request )或method("POST", Request)将请求方法设置为POST。
val requestBuilder: Request.Builder = Request.Builder()
requestBuilder.url(url)
.method("POST", requestBody) //和下面方法等价
.post(requestBody) //和上面方法等价
发起POST请求和GET方式一样,至于异步或同步看具体业务场景。
try {
call.execute().use {
//表单提交完成
}
} catch (e: Exception) {
//表单提交异常
}
3.4、POST上传文件
上传文件也很简单,借助RequestBody内部实现的File扩展函数asRequestBody(),从文件对象直接创建对应的RequestBody。发起上传文件的请求和上一节的POST流程一样。
//指明文件MIME为图片image/png
val fileContentType: MediaType? = "image/png".toMediaTypeOrNull()
//从File创建RequestBody
val fileBody = File(path).asRequestBody(fileContentType)
//构造文件上传请求
val request: Request = Request.Builder()
.url(url)
.post(fileBody)
.build()
//同步执行文件上传请求
val call: Call = OkHttpClient().newCall(request)
try {
call.execute().use {
//表单提交异常
}
} catch (e: Exception) {
//表单提交异常
}
3.5、Multipart分块POST
借助OkHttp内部的MultipartBody类可以构造复杂的请求体。分块请求体中的每块都是独立的一个RequestBody,可以设置自己的Header。
//通过MultipartBody.Builder构造
val multipartBodyBuilder: MultipartBody.Builder = MultipartBody.Builder()
multipartBodyBuilder.setType("multipart/form-data".toMediaType())
//添加分块
multipartBodyBuilder.addPart(requestOne)
multipartBodyBuilder.addPart(MultipartBody.Part.create(requestTwo))
multipartBodyBuilder.addPart(MultipartBody.Part.create(null, requestTwo))
multipartBodyBuilder.addPart(Headers.headersOf("key", "value"), requestThree)
//build()分块请求体对象
val multipartBody: MultipartBody = multipartBodyBuilder.build()