1. 程式人生 > >Android retrofit 日誌攔截器

Android retrofit 日誌攔截器

背景

在使用Android retrofit+rxjava時,想獲知網路請求的一些引數,方便除錯,比如:請求地址、請求響應時間、請求響應訊息體等內容,雖然部分可以通過每個介面進行獲知,但是這樣極其不方便,那麼有沒有可以統一設定的方法呢?請接下去看。

日誌攔截器

retrofit是使用okhttp3,做為網路請求,okhttp3有個Interceptor介面,可以對請求和響應進行攔截。通過這個機制,我們可以設計自己的一套日誌攔截器,列印一些必要的資訊。

  • 自定義攔截器

    首先要自己定義一個攔截器類,並實現Interceptor介面,在裡面列印一些資訊,如下LogInterceptor.java類。

public class LogInterceptor implements Interceptor{
    public static final String TAG = "LogInterceptor.java";
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        //the request url
        String url = request.url().toString();
        //the request method
String method = request.method(); long t1 = System.nanoTime(); HLog.d(TAG,String.format(Locale.getDefault(),"Sending %s request [url = %s]",method,url)); //the request body RequestBody requestBody = request.body(); if(requestBody!= null) { StringBuilder sb = new
StringBuilder("Request Body ["); okio.Buffer buffer = new okio.Buffer(); requestBody.writeTo(buffer); Charset charset = Charset.forName("UTF-8"); MediaType contentType = requestBody.contentType(); if (contentType != null) { charset = contentType.charset(charset); } if(isPlaintext(buffer)){ sb.append(buffer.readString(charset)); sb.append(" (Content-Type = ").append(contentType.toString()).append(",") .append(requestBody.contentLength()).append("-byte body)"); }else { sb.append(" (Content-Type = ").append(contentType.toString()) .append(",binary ").append(requestBody.contentLength()).append("-byte body omitted)"); } sb.append("]"); HLog.d(TAG, String.format(Locale.getDefault(), "%s %s", method, sb.toString())); } Response response = chain.proceed(request); long t2 = System.nanoTime(); //the response time HLog.d(TAG,String.format(Locale.getDefault(),"Received response for [url = %s] in %.1fms",url, (t2-t1)/1e6d)); //the response state HLog.d(TAG,String.format(Locale.CHINA,"Received response is %s ,message[%s],code[%d]",response.isSuccessful()?"success":"fail",response.message(),response.code())); //the response data ResponseBody body = response.body(); BufferedSource source = body.source(); source.request(Long.MAX_VALUE); // Buffer the entire body. Buffer buffer = source.buffer(); Charset charset = Charset.defaultCharset(); MediaType contentType = body.contentType(); if (contentType != null) { charset = contentType.charset(charset); } String bodyString = buffer.clone().readString(charset); HLog.d(TAG,String.format("Received response json string [%s]",bodyString)); return response; } static boolean isPlaintext(Buffer buffer){ try { Buffer prefix = new Buffer(); long byteCount = buffer.size() < 64 ? buffer.size() : 64; buffer.copyTo(prefix, 0, byteCount); for (int i = 0; i < 16; i++) { if (prefix.exhausted()) { break; } int codePoint = prefix.readUtf8CodePoint(); if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) { return false; } } return true; } catch (EOFException e) { return false; // Truncated UTF-8 sequence. } } }

在上面的實現中,列印了請求的url、請求方法(get\post\put\delete…)、響應時間、響應狀態、響應內容等。這裡需要主要的是,在列印響應內容體的時候,或許你會遇到下面這個異常:

 java.lang.IllegalStateException: closed

這是因為你列印的時候直接呼叫了ResponseBody.string(),我們看下原始碼的實現

public final String string() throws IOException {
    return new String(bytes(), charset().name());
}

它呼叫了bytes(),我在接著看下

public final byte[] bytes() throws IOException {
    long contentLength = contentLength();
    if (contentLength > Integer.MAX_VALUE) {
      throw new IOException("Cannot buffer entire body for content length: " + contentLength);
    }

    BufferedSource source = source();
    byte[] bytes;
    try {
      bytes = source.readByteArray();
    } finally {
      Util.closeQuietly(source);
    }
    if (contentLength != -1 && contentLength != bytes.length) {
      throw new IOException("Content-Length and stream length disagree");
    }
    return bytes;
  }

看到finally部分,它已經將 BufferedSource 關閉掉了,這樣到我們自己介面真正要處理的時候,它就會報錯了,已經關閉的緩衝區就不能操作了。
那麼上面LogInterceptor實際上是clone了一份BufferedSource 進行操作,這樣不影響原來的BufferedSource。
也可以使用下面第二種方法,原理相同:

ResponseBody originalBody = response.body();
ResponseBody body = response.peekBody(originalBody.contentLength());
bodyString = body.string();
  • 給okhttp設定攔截器
OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
builder.addNetworkInterceptor(new NetworkInterceptor());
if(BuildConfig.DEBUG){
    //日誌攔截器
    builder.addNetworkInterceptor(new LogInterceptor());
}
OkHttpClient client = builder.build();
retrofit = new Retrofit.Builder()
        .baseUrl(APIService.BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
        .client(client)
        .build();

相關Exception

 java.lang.IllegalStateException: closed