Okhttp中的response.body()只能访问一次,相信大部分人都已经踩过这个坑。在那个retrofit还没火起来的年代,大家都只是用okhttp然后自己封装,当时都是把response.body().string()用一个String保存起来随便传了。
但如今项目用的是retrofit + okhttp的网络层,在项目中打算加一个统一的异常处理,想了很久,感觉最简单的方式还是通过拦截器Interceptor的方式感觉最方便。想好了实现方式,开始好好屡一下实现的逻辑。
1、自定义拦截器,实现intercept方法
2、在intercept方法内读取json数据判断是否特定的异常统一处理
3、添加拦截器
逻辑比较简单,但是中途发现自定义处理后的后续处理不太正常
/**
* @Description 网络拦截器。
**/
private Interceptor getIns() {
Interceptor interceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
String res = response.body().string()
Constant.LogE(TAG, " response.url():"+ url);
Constant.LogE(TAG, " response.body():"+ res);
BaseBean baseBean = new Gson().fromJson( res,BaseBean.class);
if(baseBean.getState() == -401)
{
//异常处理
}
return response;
}
};
return interceptor;
}
经过一番调试,发现问题出在String res = response.body().string()上面。但这里又需要读取string的json数据。
看源码
/**
* Returns the response as a string decoded with the charset of the Content-Type header. If that
* header is either absent or lacks a charset, this will attempt to decode the response body as
* UTF-8.
*/
public final String string() throws IOException {
return new String(bytes(), charset().name());
}
继续磕
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;
}
问题来了,桥黑板!!!划重点了!!!!
Util.closeQuietly(source);
看看源码
/**
* Closes {@code closeable}, ignoring any checked exceptions. Does nothing if {@code closeable} is
* null.
*/
public static void closeQuietly(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (RuntimeException rethrown) {
throw rethrown;
} catch (Exception ignored) {
}
}
}
谷歌翻译下注释
关闭{@code closeable},忽略任何已检查的异常。如果{@code closeable}是空值。
问题找到了,细看源码会发现ResponseBody类以及其他大部分类都implements Closeable。问题找到了,如何解决呢?
okhttp有个log打印类HttpLoggingInterceptor,为什么他就可以读出来而不影响传递呢?继续磕源码
public final class HttpLoggingInterceptor implements Interceptor {
private static final Charset UTF8 = Charset.forName("UTF-8");
@Override public Response intercept(Chain chain) throws IOException {
Level level = this.level;
Request request = chain.request();
//如果Log Level 级别为NONOE,则不打印,直接返回
if (level == Level.NONE) {
return chain.proceed(request);
}
//是否打印body
boolean logBody = level == Level.BODY;
//是否打印header
boolean logHeaders = logBody || level == Level.HEADERS;
//获得请求body
RequestBody requestBody = request.body();
//请求body是否为空
boolean hasRequestBody = requestBody != null;
//获得Connection,内部有route、socket、handshake、protocol方法
Connection connection = chain.connection();
//如果Connection为null,返回HTTP_1_1,否则返回connection.protocol()
Protocol protocol = connection != null ? connection.protocol() : Protocol.HTTP_1_1;
//比如: --> POST http://121.40.227.8:8088/api http/1.1
String requestStartMessage = "--> " + request.method() + ' ' + request.url() + ' ' + protocol;
if (!logHeaders && hasRequestBody) {
requestStartMessage += " (" + requestBody.contentLength() + "-byte body)";
}
logger.log(requestStartMessage);
//打印 Request
if (logHeaders) {
if (hasRequestBody) {
// Request body headers are only present when installed as a network interceptor. Force
// them to be included (when available) so there values are known.
if (requestBody.contentType() != null) {
logger.log("Content-Type: " + requestBody.contentType());
}
if (requestBody.contentLength() != -1) {
logger.log("Content-Length: " + requestBody.contentLength());
}
}
Headers headers = request.headers();
for (int i = 0, count = headers.size(); i < count; i++) {
String name = headers.name(i);
// Skip headers from the request body as they are explicitly logged above.
if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) {
logger.log(name + ": " + headers.value(i));
}
}
if (!logBody || !hasRequestBody) {
logger.log("--> END " + request.method());
} else if (bodyEncoded(request.headers())) {
logger.log("--> END " + request.method() + " (encoded body omitted)");
} else {
Buffer buffer = new Buffer();
requestBody.writeTo(buffer);
//编码设为UTF-8
Charset charset = UTF8;
MediaType contentType = requestBody.contentType();
if (contentType != null) {
charset = contentType.charset(UTF8);
}
logger.log("");
if (isPlaintext(buffer)) {
logger.log(buffer.readString(charset));
logger.log("--> END " + request.method()
+ " (" + requestBody.contentLength() + "-byte body)");
} else {
logger.log("--> END " + request.method() + " (binary "
+ requestBody.contentLength() + "-byte body omitted)");
}
}
}
//打印 Response
long startNs = System.nanoTime();
Response response;
try {
response = chain.proceed(request);
} catch (Exception e) {
logger.log("<-- HTTP FAILED: " + e);
throw e;
}
long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
ResponseBody responseBody = response.body();
long contentLength = responseBody.contentLength();
String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length";
//比如 <-- 200 OK http://121.40.227.8:8088/api (36ms)
logger.log("<-- " + response.code() + ' ' + response.message() + ' '
+ response.request().url() + " (" + tookMs + "ms" + (!logHeaders ? ", "
+ bodySize + " body" : "") + ')');
if (logHeaders) {
Headers headers = response.headers();
for (int i = 0, count = headers.size(); i < count; i++) {
logger.log(headers.name(i) + ": " + headers.value(i));
}
if (!logBody || !HttpEngine.hasBody(response)) {
logger.log("<-- END HTTP");
} else if (bodyEncoded(response.headers())) {
logger.log("<-- END HTTP (encoded body omitted)");
} else {
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE); // Buffer the entire body.
Buffer buffer = source.buffer();
Charset charset = UTF8;
MediaType contentType = responseBody.contentType();
if (contentType != null) {
try {
charset = contentType.charset(UTF8);
} catch (UnsupportedCharsetException e) {
logger.log("");
logger.log("Couldn't decode the response body; charset is likely malformed.");
logger.log("<-- END HTTP");
return response;
}
}
if (!isPlaintext(buffer)) {
logger.log("");
logger.log("<-- END HTTP (binary " + buffer.size() + "-byte body omitted)");
return response;
}
if (contentLength != 0) {
logger.log("");
//获取Response的body的字符串 并打印
logger.log(buffer.clone().readString(charset));
}
logger.log("<-- END HTTP (" + buffer.size() + "-byte body)");
}
}
return response;
}
/**
* Returns true if the body in question probably contains human readable text. Uses a small sample
* of code points to detect unicode control characters commonly used in binary file signatures.
*/
static boolean isPlaintext(Buffer buffer) throws EOFException {
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.
}
}
private boolean bodyEncoded(Headers headers) {
String contentEncoding = headers.get("Content-Encoding");
return contentEncoding != null && !contentEncoding.equalsIgnoreCase("identity");
}
}
好了,开始了筛选有用的代码,最后实现的代码
Request request = chain.request();
Response response = chain.proceed(request);
ResponseBody responseBody = response.body();
long contentLength = responseBody.contentLength();
if (!bodyEncoded(response.headers())) {
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE); // Buffer the entire body.
Buffer buffer = source.buffer();
Charset charset = UTF8;
MediaType contentType = responseBody.contentType();
if (contentType != null) {
try {
charset = contentType.charset(UTF8);
} catch (UnsupportedCharsetException e) {
//Couldn't decode the response body; charset is likely malformed.
return response;
}
}
if (!isPlaintext(buffer)) {
// L.i("<-- END HTTP (binary " + buffer.size() + "-byte body omitted)");
return response;
}
if (contentLength != 0) {
String result = buffer.clone().readString(charset);
Constant.LogE(TAG, " response.url():"+ response.request().url());
Constant.LogE(TAG, " response.body():" + result);
}
}
完整的代码
/**
* @Description 网络拦截器
**/
private Interceptor getIns() {
Interceptor interceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
ResponseBody responseBody = response.body();
long contentLength = responseBody.contentLength();
if (!bodyEncoded(response.headers())) {
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE); // Buffer the entire body.
Buffer buffer = source.buffer();
Charset charset = UTF8;
MediaType contentType = responseBody.contentType();
if (contentType != null) {
try {
charset = contentType.charset(UTF8);
} catch (UnsupportedCharsetException e) {
return response;
}
}
if (!isPlaintext(buffer)) {
return response;
}
if (contentLength != 0) {
String result = buffer.clone().readString(charset);
Constant.LogE(TAG, " response.url():"+ response.request().url());
Constant.LogE(TAG, " response.body():" + result);
//得到所需的string,开始判断是否异常
//***********************do something*****************************
}
}
return response;
}
};
return interceptor;
}
private static final Charset UTF8 = Charset.forName("UTF-8");
private boolean bodyEncoded(Headers headers) {
String contentEncoding = headers.get("Content-Encoding");
return contentEncoding != null && !contentEncoding.equalsIgnoreCase("identity");
}
static boolean isPlaintext(Buffer buffer) throws EOFException {
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及返回的json
附上添加拦截器的方式
okHttpClient = new OkHttpClient
.Builder()
.connectTimeout(30, TimeUnit.SECONDS)//连接超时设置
.writeTimeout(30, TimeUnit.SECONDS)//写入超时设置,
.readTimeout(30, TimeUnit.SECONDS)//读取超时设置
.addInterceptor(getIns())//拦截器
.build();
感谢博主EthanCo的博客分享,此博客引用了很多其中的内容,大家可以参考参考
http://blog.csdn.net/ethanco/article/details/51908479