Android retrofit 日誌攔截器
阿新 • • 發佈:2019-01-29
背景
在使用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