文末附代码。 文章包含:OkHttp同步或者异步、OkHttp获取响应的头部信息、OkHttp提交一个markdown文档到web服务,以HTML方式渲染markdown、OkHttp以流的方式POST提交请求体、OkHttp提交文件照片、OkHttp使用Gson队JSON和Java对象转化、OkHttp配置缓存、OkHttp配置超时(OkHttp支持连接,读取和写入超时)、OkHttp取消请求、OkHttp单独配置单个请求、OkHttp触发401后、OkHttp拦截器、OkHttp证书
OkHttp同步或者异步: 同步:client.newCall(request)之后用的是execute() 异步:client.newCall(request)之后用的是enqueue(回调)
OkHttp获取响应的头部信息: 通过response.header(key)获取即可,如果key不存在返回"" 如果用的是headers(key),key不存在返回[]
OkHttp提交一个markdown文档到web服务,以HTML方式渲染markdown: MediaType指定渲染markdown RequestBody.create(MediaType,body)是最终附加的核心api
OkHttp以流的方式POST提交请求体: 需要重写RequestBody,在writeTo,通过传递的BufferedSink(还存留),进行流的转化处理
OkHttp提交文件、照片 核心都是RequestBody.create(MediaType,body)是最终附加的核心api 照片需要用MultipartBody
OkHttp使用Gson队JSON和Java对象转化: 依赖: implementation ‘com.google.code.gson:gson:2.8.0’ ResponseBody.charStream()使用响应头Content-Type指定的字符集来解析响应体。默认是UTF-8 核心api为:gson.fromJson(…)
OkHttp配置缓存: val cacheSize = 10 * 1024 * 1024 // 10 MiB val cache = Cache(File(“缓存目录”), cacheSize.toLong()) client = OkHttpClient.Builder() // 为OkHttpClient附加缓存配置 .cache(cache) .build()
OkHttp配置超时(OkHttp支持连接,读取和写入超时): client = OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .build()
OkHttp取消请求: 拿到Call,通过call.cancel()取消 对于取消另外的请求,通过client.dispatcher().runningCalls()拿到正在请求中的call列表,然后比对tag,获取匹配的call然后取消 client.dispatcher().queuedCalls()能拿到队列中的call列表。 tag可以在Request.Builder()中通过tag(“tag”)加入
OkHttp单独配置单个请求: 核心api:OkHttpClient的newBuilder() 通过该api拿到全局的OkHttpClient,然后浅拷贝一个client,覆盖配置
OkHttp触发401后: 401 Not Authorized 就是请求没通过验证,OkHttp会自动重试未验证,所以一般通过response重新对其request附加证书即可 如果不打算自动验证,在新建Authenticator的时候返回null 通过Credentials.basic()创建证书实例,然后Builder附加进去即可 client = client.newBuilder() // 浅拷贝client .authenticator { route, response -> val credential = Credentials.basic(“证书账号”, “证书密码”) // 创建证书实例 response.request().newBuilder() // 注意:在response中拿到request,然后重新builder了headers .header(“Authorization”, credential) // 附加证书 .build() } .build()
OkHttp拦截器: 拦截器继承Interceptor,实现的intercept的参数Interceptor.Chain可以通过 chain.request()拿到请求, chain.proceed(request) 执行请求并拿到响应。 能拿到请求和响应,自然能够轻易的重写请求和响应,这就是拦截器的最大意义。 OkHttp里分为应用拦截器和网络拦截器,区别在于网络拦截器能够对重定向和重试等中间响应进行操作, 比如一个url会重定向,那么拦截器就会触发两次,但是如果是应用拦截器,就只有一次。 网络拦截器,在OkHttpClient的addNetworkInterceptor这个api里添加实例; 应用拦截器,在OkHttpClient的addInterceptor这个api里添加实例。
OkHttp证书: 官网连接:https://square.github.io/okhttp/features/https/
kt文件:
package com.example.wantv
import android.os.Bundle
import android.util.Log
import com.example.view.base.BaseActivity
import com.example.wantv.databinding.ActivityTestBinding
import com.google.gson.Gson
import okhttp3.*
import okhttp3.internal.Internal.logger
import okio.BufferedSink
import java.io.File
import java.io.IOException
import java.util.concurrent.TimeUnit
/**
* Create by smy on 2022/2/9
* Note:测试类
*/
class TestActivity : BaseActivity<ActivityTestBinding>() {
var client: OkHttpClient = OkHttpClient()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding.btGet.setOnClickListener { getRequest() }
mBinding.btPost.setOnClickListener { postRequest() }
mBinding.btSnycDownFile.setOnClickListener { syncDownFile() }
mBinding.btAsnycDownFile.setOnClickListener { asnycDownFile() }
mBinding.btGetResponseHeaders.setOnClickListener { getResponseHeaders() }
mBinding.btPostMoreLineText.setOnClickListener { postMoreLineText() }
mBinding.btPostTextByFlow.setOnClickListener { postTextByFlow() }
mBinding.btCommitFile.setOnClickListener { commitFile() }
mBinding.btCommitImg.setOnClickListener { commitImg() }
mBinding.btGson.setOnClickListener { gson() }
mBinding.btCache.setOnClickListener { cache() }
mBinding.btCancelRequest.setOnClickListener { cancelRequest() }
mBinding.btTimeoutSetting.setOnClickListener { timeoutSetting() }
mBinding.btCustomCall.setOnClickListener { customCall() }
mBinding.btAutoAuthentication.setOnClickListener { autoAuthentication() }
mBinding.btInterceptor.setOnClickListener { interceptor() }
mBinding.btInterceptorNetwork.setOnClickListener { interceptorNetwork() }
}
/**
* 网络拦截器
* 核心api:addNetworkInterceptor来添加
*
* 与应用拦截器区别:能够对重定向和重试等中间响应进行操作
*/
private fun interceptorNetwork() {
client = client.newBuilder().addNetworkInterceptor(LoggingInterceptor()).build() // 加入拦截器
// TODO: 下面正常请求即可
/**
* 当我们运行这段代码时,拦截器会运行两次。
* 一次用于初始请求http://www.publicobject.com/helloworld.txt,
* 另一个用于重定向到https://publicobject.com/helloworld.txt
*/
}
/**
* 应用拦截器
* 核心api:addInterceptor来添加
*/
private fun interceptor() {
client = client.newBuilder().addInterceptor(LoggingInterceptor()).build() // 加入拦截器
val request: Request = Request.Builder()
// http://www.publicobject.com/helloworld.txt 这个会重定向到https://publicobject.com/helloworld.txt
// 可以打断点查看到拦截器中request和response 的 url不一样
.url("http://www.publicobject.com/helloworld.txt")
.header("User-Agent", "OkHttp Example")
.build()
val response = client.newCall(request).execute()
response.body().close()
}
internal class LoggingInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request() // chain是关键
/**
* 这里可以看到拦截器能拿到request和response
* 所以可以轻易的重写请求和响应
*/
val t1 = System.nanoTime()
logger.info( //记录传出
java.lang.String.format(
"Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()
)
)
val response = chain.proceed(request) // 拦截传入的response
val t2 = System.nanoTime()
logger.info( // 传出传入做差 记录传出请求和传入响应
String.format(
"Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6, response.headers()
)
)
return response
}
}
/**
* 401 Not Authorized
* 就是请求没通过验证,OkHttp会自动重试未验证,所以一般通过response重新对其request附加证书即可
* 如果不打算自动验证,在新建Authenticator的时候返回null
* 通过Credentials.basic()创建证书实例,然后Builder
*/
private fun autoAuthentication() {
client = client.newBuilder() // 浅拷贝client
.authenticator { route, response ->
println("response: $response")
val credential = Credentials.basic("证书账号", "证书密码") // 创建证书实例
response.request().newBuilder() // 注意:在response中拿到request,然后重新builder了headers
.header("Authorization", credential) // 附加证书
.build()
}
.build()
// TODO: 写请求即可
// 不打算自动验证
client = OkHttpClient.Builder() // 直接重新实例化一个client
.authenticator(Authenticator { route, response ->
if (response.request().header("Authorization") != null) {
return@Authenticator null // 返回null直接不自动验证
}
val credential = Credentials.basic("证书账号", "证书密码") // 创建证书实例
response.request().newBuilder() // 注意:在response中拿到request,然后重新builder了headers
.header("Authorization", credential) // 附加证书
.build()
})
.build()
// TODO: 写请求即可
}
/**
* 核心api:OkHttpClient的newBuilder()
*/
private fun customCall() {
val request: Request = Request.Builder()
.url("xxxxurl")
.build()
try {
val copy = client.newBuilder() // 浅拷贝 通过newBuilder
.readTimeout(500, TimeUnit.MILLISECONDS)
.build()
val response = copy.newCall(request).execute()
println("Response 1 succeeded: $response")
} catch (e: IOException) {
println("Response 1 failed: $e")
}
}
/**
* 没有响应时使用超时结束call
* OkHttp支持连接,读取和写入超时
*/
private fun timeoutSetting() {
client = OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
// TODO: 后面就是正常请求了
}
/**
* 不管同步还是异步的call都可以取消,核心api:call.cancel()
* 取消另外的请求,一般采用client.dispatcher()拿到全部的请求然后去比对,最后取消
* tag可以在Request.Builder()中通过tag("tag")加入
*/
private fun cancelRequest() {
val request: Request = Request.Builder()
.url("xxxx") // This URL is served with a 2 second delay.
.build()
val call: Call = client.newCall(request)
call.cancel() // 取消
// 下面是按照tag取消请求
for (call in client.dispatcher().queuedCalls()) {
if ("你的tag".equals(call.request().tag())) {
call.cancel();
}
}
for (call in client.dispatcher().runningCalls()) {
if ("你的tag".equals(call.request().tag())) {
call.cancel();
}
}
// 下面是取消全部
for (call in client.dispatcher().queuedCalls()) {
call.cancel();
}
for (call in client.dispatcher().runningCalls()) {
call.cancel();
}
}
/**
* CacheControl.FORCE_NETWORK 可以通过这个让某个请求不使用缓存,不过会返回504
* 没试验,待定
*/
private fun cache() {
val cacheSize = 10 * 1024 * 1024 // 10 MiB
val cache = Cache(File("缓存目录"), cacheSize.toLong())
client = OkHttpClient.Builder() // 为OkHttpClient附加缓存配置
.cache(cache)
.build()
// TODO: 之后就是正常请求
}
private fun gson() {
val gson = Gson()
val request: Request = Request.Builder()
.url("https://api.github.com/gists/c2a7c39532239ff261be")
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call?, e: IOException?) {
e?.printStackTrace()
}
override fun onResponse(call: Call?, response: Response?) {
if (!response!!.isSuccessful) throw IOException("Unexpected code $response")
val gist: Gist = gson.fromJson(
response.body().charStream(),
Gist::class.java
) // 核心gson.fromJson
for ((key, value) in gist.files!!.entries) {
println("$key : ${value.content}")
}
}
})
}
class Gist {
var files: Map<String, GistFile>? = null
}
class GistFile {
var content: String? = null
}
private fun commitImg() {
val MEDIA_TYPE_PNG: MediaType = MediaType.parse("image/png")
val requestBody: RequestBody = MultipartBody.Builder() // 需要用到MultipartBody
.setType(MultipartBody.FORM)
.addFormDataPart("title", "Square Logo")
.addFormDataPart(
"image", "logo-square.png",
RequestBody.create(MEDIA_TYPE_PNG, File("Android设备中照片的路径"))
)
.build()
val request: Request = Request.Builder()
.header("Authorization", "Client-ID ...")
.url("https://api.imgur.com/3/image")
.post(requestBody)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call?, e: IOException?) {
e?.printStackTrace()
}
override fun onResponse(call: Call?, response: Response?) {
if (!response!!.isSuccessful) throw IOException("Unexpected code $response")
println(response.body().string())
}
})
}
private fun commitFile() {
val MEDIA_TYPE_MARKDOWN: MediaType = MediaType.parse("text/x-markdown; charset=utf-8")
val file = File("Android设备文件路径,需要权限")
val request: Request = Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file)) // 核心都是RequestBody.create
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call?, e: IOException?) {
e?.printStackTrace()
}
override fun onResponse(call: Call?, response: Response?) {
if (!response!!.isSuccessful) throw IOException("Unexpected code $response")
println(response.body().string())
}
})
}
/**
* 请求体的内容由流写入产生
* 流直接写入Okio的BufferedSink
* 如果是OutputStream,可以使用BufferedSink.outputStream()来获取
*/
private fun postTextByFlow() {
val MEDIA_TYPE_MARKDOWN: MediaType = MediaType.parse("text/x-markdown; charset=utf-8")
val requestBody: RequestBody = object : RequestBody() { // 核心 重写改请求体
override fun contentType(): MediaType {
return MEDIA_TYPE_MARKDOWN
}
override fun writeTo(sink: BufferedSink?) {
sink!!.writeUtf8("Numbers\n")
sink.writeUtf8("-------\n")
for (i in 2..100) {
sink.writeUtf8(String.format(i.toString() + "\n"))
}
}
}
val request: Request = Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(requestBody)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call?, e: IOException?) {
e?.printStackTrace()
}
override fun onResponse(call: Call?, response: Response?) {
if (!response!!.isSuccessful) throw IOException("Unexpected code $response")
println(response.body().string())
}
})
}
/**
* 多行文本的定义如同python的一般
* MediaType指定渲染markdown
* RequestBody.create是最终附加的核心api
* 因为整个请求体都在内存中,因此避免使用此api提交大文档(大于1MB)
*/
private fun postMoreLineText() {
val postBody =
"""
Releases
--------
* _1.0_ May 6, 2013
* _1.1_ June 15, 2013
* _1.2_ August 11, 2013
"""
val MEDIA_TYPE_MARKDOWN: MediaType = MediaType.parse("text/x-markdown; charset=utf-8")
val request: Request = Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call?, e: IOException?) {
e?.printStackTrace()
}
override fun onResponse(call: Call?, response: Response?) {
if (!response!!.isSuccessful) throw IOException("Unexpected code $response")
println(response.body().string())
}
})
}
/**
* 通过response.header(key)获取即可,如果key不存在返回""
* 如果用的是headers(key),key不存在返回[]
*/
private fun getResponseHeaders() {
val request: Request = Request.Builder()
.url("https://api.github.com/repos/square/okhttp/issues")
.header("User-Agent", "OkHttp Headers.java")
.addHeader("Accept", "application/json; q=0.5")
.addHeader("Accept", "application/vnd.github.v3+json")
.build()
val response = client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call?, e: IOException?) {
e?.printStackTrace()
}
override fun onResponse(call: Call?, response: Response?) {
if (!response!!.isSuccessful) throw IOException("Unexpected code $response")
println("Server: " + response.header("Server"))
println("Date: " + response.header("Date"))
println("Vary: " + response.headers("Vary"))
println("Hahsahdasdas: " + response.headers("Hahsahdasdas"))
}
})
}
/**
* 异步拉文件,通过响应的回调
*/
private fun asnycDownFile() {
val request: Request =
Request.Builder().url("http://publicobject.com/helloworld.txt").build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call?, e: IOException?) {
e?.printStackTrace()
}
override fun onResponse(call: Call?, response: Response?) {
if (!response!!.isSuccessful) {
throw IOException("$response")
}
val headers: Headers = response.headers()
for (i in 0 until headers.size()) {
Log.d("TestActivity", headers.name(i) + " : " + headers.value(i))
}
Log.e("TestActivity", response.body().toString())
}
})
Log.d("TestActivity", "main thread is run...")
}
/**
* 同步拉文件,必须开子线程
* 会将把整个文档加载到内存中。对于超过1MB的响应body,应使用流的方式来处理body
*/
private fun syncDownFile() {
Thread {
run {
val request: Request =
Request.Builder().url("http://publicobject.com/helloworld.txt").build()
val response: Response = client.newCall(request).execute()
if (!response.isSuccessful) {
throw IOException("$response")
}
val headers: Headers = response.headers()
for (i in 0 until headers.size()) {
Log.d("TestActivity", headers.name(i) + " : " + headers.value(i))
}
Log.e("TestActivity", response.body().toString())
}
}.start()
}
private fun postRequest() {
val body: RequestBody = FormBody.Builder().add("", "").build()
val request: Request = Request.Builder()
.url("http://www.baidu.com")
.post(body)
.build()
Thread {
run {
try {
val response: Response = client.newCall(request).execute()
if (response.isSuccessful) {
Log.d("TestActivity and post ", response.body().string())
} else {
throw IOException("" + response)
}
} catch (e: IOException) {
e.printStackTrace()
}
}
}.start()
}
private fun getRequest() {
val request: Request = Request.Builder()
.get()
.tag(this)
.url("http://www.baidu.com")
.build()
Thread {
run {
try {
val response: Response = client.newCall(request).execute()
if (response.isSuccessful) {
Log.d("TestActivity and get", response.body().string())
} else {
throw IOException("" + response)
}
} catch (e: IOException) {
e.printStackTrace()
}
}
}.start()
}
}
xml文件:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/bt_get"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Get请求"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/bt_post"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:text="Post请求"
app:layout_constraintLeft_toRightOf="@id/bt_get"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/bt_snyc_down_file"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:text="同步拉文件"
app:layout_constraintLeft_toRightOf="@id/bt_post"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/bt_asnyc_down_file"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:text="异步拉文件"
app:layout_constraintLeft_toRightOf="@id/bt_snyc_down_file"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/bt_get_response_headers"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:text="获取响应头信息"
app:layout_constraintLeft_toRightOf="@id/bt_asnyc_down_file"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/bt_post_more_line_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:text="POST多行文本给Server"
app:layout_constraintLeft_toRightOf="@id/bt_get_response_headers"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/bt_post_text_by_flow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="POST多行文本给Server通过流"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/bt_get" />
<Button
android:id="@+id/bt_commit_file"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:text="POST提交文件"
app:layout_constraintLeft_toRightOf="@id/bt_post_text_by_flow"
app:layout_constraintTop_toBottomOf="@id/bt_get" />
<Button
android:id="@+id/bt_commit_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:text="POST提交照片"
app:layout_constraintLeft_toRightOf="@id/bt_commit_file"
app:layout_constraintTop_toBottomOf="@id/bt_get" />
<Button
android:id="@+id/bt_gson"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:text="Gson解析"
app:layout_constraintLeft_toRightOf="@id/bt_commit_img"
app:layout_constraintTop_toBottomOf="@id/bt_get" />
<Button
android:id="@+id/bt_cache"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="缓存"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/bt_post_text_by_flow" />
<Button
android:id="@+id/bt_cancel_request"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:text="取消请求"
app:layout_constraintLeft_toRightOf="@id/bt_cache"
app:layout_constraintTop_toBottomOf="@id/bt_post_text_by_flow" />
<Button
android:id="@+id/bt_timeout_setting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:text="超时配置"
app:layout_constraintLeft_toRightOf="@id/bt_cancel_request"
app:layout_constraintTop_toBottomOf="@id/bt_post_text_by_flow" />
<Button
android:id="@+id/bt_custom_call"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:text="浅拷贝连接自定义单个请求"
app:layout_constraintLeft_toRightOf="@id/bt_timeout_setting"
app:layout_constraintTop_toBottomOf="@id/bt_post_text_by_flow" />
<Button
android:id="@+id/bt_auto_authentication"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:text="401自动验证"
app:layout_constraintLeft_toRightOf="@id/bt_custom_call"
app:layout_constraintTop_toBottomOf="@id/bt_post_text_by_flow" />
<Button
android:id="@+id/bt_interceptor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="拦截器"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/bt_cache" />
<Button
android:id="@+id/bt_interceptor_network"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:text="网络拦截器"
app:layout_constraintLeft_toRightOf="@id/bt_interceptor"
app:layout_constraintTop_toBottomOf="@id/bt_cache" />
</androidx.constraintlayout.widget.ConstraintLayout>
这里mBinding我提前和用viewBinding把V和M关联了,阅读代码只需要把mBinding.btGet看成xml里面的bt_get按钮就行。
|