关于 OkHttp 和 Retrofit 的使用,你需要注意的点

前言

现在做 Android 开发大家基本上都是使用 OkHttp 或者 Retrofit 进行网络请求的。


添加依赖

1
2
3
4

// OkHttp 框架:https://github.com/square/okhttp
implementation 'com.squareup.okhttp3:okhttp:4.9.3'


简单使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private val mClient = OkHttpClient.Builder()
.addInterceptor { chain ->
val request = chain.request()
chain.proceed(request)
}.build()

fun okHttpExceptionTest() {
val request = Request.Builder()
.url("https://www.baidu.com/")
.build()
mClient.newCall(request)
.enqueue(object : Callback {

override fun onFailure(call: Call, e: IOException) {
e.printStackTrace()
}

@Throws(IOException::class)
override fun onResponse(call: Call, response: Response) {
println("okHttpExceptionTest:response is ${response.body?.string()}")
}
})
}

正常情况下以上代码没什么问题,对吧?


如果在网络请求时发生了异常,那么 OkHttp 会回调 Callback 的 onFailure 方法。


但是,这个时候我们假如想要在请求前先做一点处理(比如给请求添加一个统一的请求头或者别的参数),然后再去进行网络请求,这个时候如果我们在拦截器里不小心抛出了一个非 IOException ,这个时候程序会直接 crash!


伪代码复现


1
2
3
4
5
6
7
8
9
10
11
12
13
private val mClient = OkHttpClient.Builder()
.addInterceptor { chain ->
val request = chain.request()
val method = request.method
// do something...
if ("GET" == method) {
// 模拟抛出未受检的异常(会导致程序 crash)
throw RuntimeException("are you ok?")
// 下面抛出的这个异常不会导致程序 crash
// throw IOException("are you ok?")
}
chain.proceed(request)
}.build()

由于 Retrofit 是基于 OkHttp 的封装,所以使用 Retrofit 的时候也会有这个问题。这里就不再贴出代码了(笔者已经试过了)。


拦截器接口源码阅读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

/**
* Observes, modifies, and potentially short-circuits requests going out and the corresponding
* responses coming back in. Typically interceptors add, remove, or transform headers on the request
* or response.
*
* Implementations of this interface throw [IOException] to signal connectivity failures. This
* includes both natural exceptions such as unreachable servers, as well as synthetic exceptions
* when responses are of an unexpected type or cannot be decoded.
*
* Other exception types cancel the current call:
*
* * For synchronous calls made with [Call.execute], the exception is propagated to the caller.
*
* * For asynchronous calls made with [Call.enqueue], an [IOException] is propagated to the caller
* indicating that the call was canceled. The interceptor's exception is delivered to the current
* thread's [uncaught exception handler][Thread.UncaughtExceptionHandler]. By default this
* crashes the application on Android and prints a stacktrace on the JVM. (Crash reporting
* libraries may customize this behavior.)
*
* A good way to signal a failure is with a synthetic HTTP response:
*
* ```
* @Throws(IOException::class)
* override fun intercept(chain: Interceptor.Chain): Response {
* if (myConfig.isInvalid()) {
* return Response.Builder()
* .request(chain.request())
* .protocol(Protocol.HTTP_1_1)
* .code(400)
* .message("client config invalid")
* .body("client config invalid".toResponseBody(null))
* .build()
* }
*
* return chain.proceed(chain.request())
* }
* ```
*/
fun interface Interceptor {
@Throws(IOException::class)
fun intercept(chain: Chain): Response

companion object {
/**
* Constructs an interceptor for a lambda. This compact syntax is most useful for inline
* interceptors.
*
* ```
* val interceptor = Interceptor { chain: Interceptor.Chain ->
* chain.proceed(chain.request())
* }
* ```
*/
inline operator fun invoke(crossinline block: (chain: Chain) -> Response): Interceptor =
Interceptor { block(it) }
}

interface Chain {
fun request(): Request

@Throws(IOException::class)
fun proceed(request: Request): Response

/**
* Returns the connection the request will be executed on. This is only available in the chains
* of network interceptors; for application interceptors this is always null.
*/
fun connection(): Connection?

fun call(): Call

fun connectTimeoutMillis(): Int

fun withConnectTimeout(timeout: Int, unit: TimeUnit): Chain

fun readTimeoutMillis(): Int

fun withReadTimeout(timeout: Int, unit: TimeUnit): Chain

fun writeTimeoutMillis(): Int

fun withWriteTimeout(timeout: Int, unit: TimeUnit): Chain
}
}


关键源码

1
2
3
4

@Throws(IOException::class)
fun intercept(chain: Chain): Response


从上方部分代码可以看出 intercept 只会向上抛出 IOException 异常。英语好一点的同学可能会去读一下上面的文档注释,这里我给大家机翻一下吧~

译:

观察、修改并可能使发出的请求和返回的相应响应短路。通常,拦截器会在请求或响应上添加、删除或转换标头。

此接口的实现抛出IOException以表示连接失败。这包括自然异常(例如无法访问的服务器)以及响应为意外类型或无法解码时的合成异常。

其他异常类型取消当前调用:

  • 对于使用Call.execute进行的同步调用,异常会传播给调用者。

  • 对于使用Call.enqueue进行的异步调用, IOException会传播到调用者,指示调用已被取消。拦截器的异常被传递给当前线程的未捕获异常处理程序。默认情况下,这会使 Android 上的应用程序崩溃并在 JVM 上打印堆栈跟踪。 (崩溃报告库可以自定义此行为。)

从上面的注释可以看出最关键点的一句话:默认情况下,这会使 Android 上的应用程序崩溃 并在 JVM 上打印堆栈跟踪。


总结

所以我们要想避免这个问题的话,我们可以将拦截器中的异常捕获到并包装成 IOException 抛出即可解决该问题。


如果对你有帮助的话,欢迎一键三连+关注哦~


关于 OkHttp 和 Retrofit 的使用,你需要注意的点
https://blog.funkt.cn/2022/08/25/1562803985595764737/
作者
anjiemo
发布于
2022年8月25日
许可协议
未经作者授权,禁止转载