一個小技巧提升 OkHttp 請求穩定性
- 2020 年 1 月 19 日
- 筆記
OkHttp是可以說是Android開發中,每個項目都必需依賴的網路庫,我們可以很便捷高效的處理網路請求,極大的提升了編碼效率。但是有時候,我們使用OkHttp也會遇到這樣的問題
崩潰的stacktrace
1 2 3 4 5 6 7 8 9 10 11 | E AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher E AndroidRuntime: Process: com.example.okhttpexceptionsample, PID: 13564 E AndroidRuntime: java.lang.NullPointerException: blablabla E AndroidRuntime: at com.example.okhttpexceptionsample.MainActivity$createNPEInterceptor$1.intercept(MainActivity.kt:61) E AndroidRuntime: at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112) E AndroidRuntime: at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:87) E AndroidRuntime: at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.kt:184) E AndroidRuntime: at okhttp3.RealCall$AsyncCall.run(RealCall.kt:136) E AndroidRuntime: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) E AndroidRuntime: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) E AndroidRuntime: at java.lang.Thread.run(Thread.java:784) |
|---|
為什麼會崩潰
從上面的stacktrace,我們可以分析到,發生了NullPointerException。發生了崩潰。
等等,我記得OkHttp有處理異常的情況呢。
嗯,確實,OkHttp有處理異常的情況,比如發生異常會調用onFailure。比如下面的Callback的內容介紹。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | interface Callback { /** * Called when the request could not be executed due to cancellation, a connectivity problem or * timeout. Because networks can fail during an exchange, it is possible that the remote server * accepted the request before the failure. */ fun onFailure(call: Call, e: IOException) /** * Called when the HTTP response was successfully returned by the remote server. The callback may * proceed to read the response body with [Response.body]. The response is still live until its * response body is [closed][ResponseBody]. The recipient of the callback may consume the response * body on another thread. * * Note that transport-layer success (receiving a HTTP response code, headers and body) does not * necessarily indicate application-layer success: `response` may still indicate an unhappy HTTP * response code like 404 or 500. */ @Throws(IOException::class) fun onResponse(call: Call, response: Response) } |
|---|
是的,
- OkHttp只處理了IOException的情況,
- NullPointerException不是IOException的子類
所以沒有被處理,發生了崩潰。
那麼有沒有辦法解決,讓這種崩潰不發生,對用戶不進行干擾呢?其實是可以的。
使用Interceptor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | package com.example.okhttpexceptionsample import okhttp3.Interceptor import okhttp3.Response import java.io.IOException /** * 對於Interceptor的intercept中可能出現的Throwable包裹成IOExceptionWrapper,轉成網路請求失敗,而不是應用崩潰 */ class SafeGuardInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { try { return chain.proceed(chain.request()) } catch (t: Throwable) { throw IOExceptionWrapper("SafeGuarded when requesting ${chain.request().url}", t) } } } /** * 將chain.proceed處理中發生的Throwable包裝成IOExceptionWrapper */ class IOExceptionWrapper(message: String?, cause: Throwable?) : IOException(message, cause) |
|---|
上面的程式碼,我們將任何Throwable的轉成IOExceptionWrapper(偽裝成IOException),然後添加到OkHttpClient中
1 2 3 4 5 | fun createOKHttpClient(): OkHttpClient { return OkHttpClient.Builder() .addInterceptor(SafeGuardInterceptor()) .build() } |
|---|
當我們再次執行有NPE的程式碼,日誌就發生了改變(不再是崩潰的日誌,而是異常的日誌)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | W System.err: com.example.okhttpexceptionsample.IOExceptionWrapper: SafeGuarded=blablabla W System.err: at com.example.okhttpexceptionsample.SafeGuardInterceptor.intercept(SafeGuardInterceptor.kt:12) W System.err: at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112) W System.err: at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:87) W System.err: at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.kt:184) W System.err: at okhttp3.RealCall$AsyncCall.run(RealCall.kt:136) W System.err: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) W System.err: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) W System.err: at java.lang.Thread.run(Thread.java:784) W System.err: Caused by: java.lang.NullPointerException: blablabla W System.err: at com.example.okhttpexceptionsample.MainActivity$createNPEInterceptor$1.intercept(MainActivity.kt:61) W System.err: at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112) W System.err: at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:87) W System.err: at com.example.okhttpexceptionsample.SafeGuardInterceptor.intercept(SafeGuardInterceptor.kt:10) W System.err: ... 7 more |
|---|
上述需要注意兩點
- 添加的是Interceptor,而不是NetworkInterceptor
- 順序很重要,一定要放在第一個位置
這麼做有什麼問題
這麼做,當然可以明顯增強請求的穩定性和應用的崩潰率。但是是不是也有一些問題呢?比如
- 將問題情況吞掉,不利於發現問題呢
是的,確實可能存在上述的問題,但是我們可以利用下面的方式減輕或者解決問題
- 只針對release情況應用SafeGuardInterceptor,這樣便於debug情況下更容易發現
- 針對不同的build variants進行配置,便於儘可能的小範圍發現問題
- 實行更加智慧的動態開啟策略。
在軟體工程中,很多決定都是trade-off的體現,具體的實施方案大家可以自行平衡選擇。