一個小技巧提升 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的體現,具體的實施方案大家可以自行平衡選擇。

Tags: