关于 OkHttp 和 Retrofit 的使用,你需要注意的点
前言
现在做 Android 开发大家基本上都是使用 OkHttp 或者 Retrofit 进行网络请求的。
添加依赖
// OkHttp 框架:https://github.com/square/okhttp
implementation 'com.squareup.okhttp3:okhttp:4.9.3'
简单使用
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!
伪代码复现
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 的时候也会有这个问题。这里就不再贴出代码了(笔者已经试过了)。
拦截器接口源码阅读
/**
* 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
}
}
关键源码
@Throws(IOException::class)
fun intercept(chain: Chain): Response
从上方部分代码可以看出 intercept 只会向上抛出 IOException
异常。英语好一点的同学可能会去读一下上面的文档注释,这里我给大家机翻一下吧~
译:
观察、修改并可能使发出的请求和返回的相应响应短路。通常,拦截器会在请求或响应上添加、删除或转换标头。
此接口的实现抛出IOException以表示连接失败。这包括自然异常(例如无法访问的服务器)以及响应为意外类型或无法解码时的合成异常。
其他异常类型取消当前调用:
-
对于使用Call.execute进行的同步调用,异常会传播给调用者。
-
对于使用Call.enqueue进行的异步调用, IOException会传播到调用者,指示调用已被取消。拦截器的异常被传递给当前线程的未捕获异常处理程序。默认情况下,这会使 Android 上的应用程序崩溃并在 JVM 上打印堆栈跟踪。 (崩溃报告库可以自定义此行为。)
从上面的注释可以看出最关键点的一句话:默认情况下,这会使 Android 上的应用程序崩溃 并在 JVM 上打印堆栈跟踪。
总结
所以我们要想避免这个问题的话,我们可以将拦截器中的异常捕获到并包装成 IOException
抛出即可解决该问题。
如果对你有帮助的话,欢迎一键三连+关注哦~