一般情况下app里面接口请求异常了,但是我们没法搜集到是哪个接口,异常的问题,哪个数据异常等。
所以我改写了下拦截器和解析器。
代码如下,供参考:
OkHttpClient.Builder builder = new OkHttpClient.Builder();
if (HcbAppConfig.HCB_ENVIRONMENT_TYPE == 0) {
builder.dns(OkHttpDns.getInstance(HcbApp.getContext()));
}
// 连接超时时间
builder.connectTimeout(DEFAULT_TIME_OUT, TimeUnit.SECONDS);
// 写操作 超时时间
builder.writeTimeout(DEFAULT_READ_TIME_OUT,TimeUnit.SECONDS);
// 读操作超时时间
builder.readTimeout(DEFAULT_READ_TIME_OUT,TimeUnit.SECONDS);
builder.addInterceptor(new HcbHttpPublicParamsInterceptor());
builder.addInterceptor(new MyHttpLoggingInterceptor());
mRetrofit = new Retrofit.Builder()
.client(builder.build())
.baseUrl(HcbAppConfig.HCB_APP_CURRENT_ENVIRONMENT)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(MyGsonConverterFactory.create())
.build();
package com.hcb.base.app.http;
import android.text.TextUtils;
import com.tencent.bugly.crashreport.CrashReport;
import java.io.EOFException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import okhttp3.Connection;
import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.internal.http.HttpHeaders;
import okhttp3.internal.platform.Platform;
import okio.Buffer;
import okio.BufferedSource;
import okio.GzipSource;
import static okhttp3.internal.platform.Platform.INFO;
/**
* 请描述使用该类使用方法!!!
*
* @author 陈聪 2020-07-17 16:48
*/
public class MyHttpLoggingInterceptor implements Interceptor {
private static final Charset UTF8 = Charset.forName("UTF-8");
public enum Level {
/** No logs. */
NONE,
/**
* Logs request and response lines.
*
* <p>Example:
* <pre>{@code
* --> POST /greeting http/1.1 (3-byte body)
*
* <-- 200 OK (22ms, 6-byte body)
* }</pre>
*/
BASIC,
/**
* Logs request and response lines and their respective headers.
*
* <p>Example:
* <pre>{@code
* --> POST /greeting http/1.1
* Host: example.com
* Content-Type: plain/text
* Content-Length: 3
* --> END POST
*
* <-- 200 OK (22ms)
* Content-Type: plain/text
* Content-Length: 6
* <-- END HTTP
* }</pre>
*/
HEADERS,
/**
* Logs request and response lines and their respective headers and bodies (if present).
*
* <p>Example:
* <pre>{@code
* --> POST /greeting http/1.1
* Host: example.com
* Content-Type: plain/text
* Content-Length: 3
*
* Hi?
* --> END POST
*
* <-- 200 OK (22ms)
* Content-Type: plain/text
* Content-Length: 6
*
* Hello!
* <-- END HTTP
* }</pre>
*/
BODY
}
public interface Logger {
void log(String message);
/** A {@link MyHttpLoggingInterceptor.Logger} defaults output appropriate for the current platform. */
MyHttpLoggingInterceptor.Logger DEFAULT = new MyHttpLoggingInterceptor.Logger() {
@Override
public void log(String message) {
Platform.get().log(INFO, message, null);
}
};
}
public MyHttpLoggingInterceptor() {
this(MyHttpLoggingInterceptor.Logger.DEFAULT);
}
public MyHttpLoggingInterceptor(MyHttpLoggingInterceptor.Logger logger) {
this.logger = logger;
}
private final MyHttpLoggingInterceptor.Logger logger;
private volatile Set<String> headersToRedact = Collections.emptySet();
public void redactHeader(String name) {
Set<String> newHeadersToRedact = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
newHeadersToRedact.addAll(headersToRedact);
newHeadersToRedact.add(name);
headersToRedact = newHeadersToRedact;
}
private volatile MyHttpLoggingInterceptor.Level level = MyHttpLoggingInterceptor.Level.NONE;
/** Change the level at which this interceptor logs. */
public MyHttpLoggingInterceptor setLevel(MyHttpLoggingInterceptor.Level level) {
if (level == null) throw new NullPointerException("level == null. Use Level.NONE instead.");
this.level = level;
return this;
}
public MyHttpLoggingInterceptor.Level getLevel() {
return level;
}
@Override
public Response intercept(Chain chain) throws IOException {
MyHttpLoggingInterceptor.Level level = this.level;
Request request = chain.request();
if (level == MyHttpLoggingInterceptor.Level.NONE) {
return chain.proceed(request);
}
boolean logBody = level == MyHttpLoggingInterceptor.Level.BODY;
boolean logHeaders = logBody || level == MyHttpLoggingInterceptor.Level.HEADERS;
RequestBody requestBody = request.body();
boolean hasRequestBody = requestBody != null;
Connection connection = chain.connection();
String requestStartMessage = "--> "
+ request.method()
+ ' ' + request.url()
+ (connection != null ? " " + connection.protocol() : "");
if (!logHeaders && hasRequestBody) {
requestStartMessage += " (" + requestBody.contentLength() + "-byte body)";
}
logger.log(requestStartMessage);
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)) {
logHeader(headers, i);
}
}
if (!logBody || !hasRequestBody) {
logger.log("--> END " + request.method());
} else if (bodyHasUnknownEncoding(request.headers())) {
logger.log("--> END " + request.method() + " (encoded body omitted)");
} else {
Buffer buffer = new Buffer();
requestBody.writeTo(buffer);
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)");
}
}
}
//获取header,判断是否标识了需要配置用户账号,此逻辑用于登录注册等接口中无法获取手机号上报情况
Headers mHeaders = request.headers();
String httpExPhone = "";
for (int i = 0, count = mHeaders.size(); i < count; i++) {
String name = mHeaders.name(i);
if ("Http-ex-phone".equalsIgnoreCase(name)) {
httpExPhone = mHeaders.value(i);
if (!TextUtils.isEmpty(httpExPhone)) {
if (TextUtils.isEmpty(CrashReport.getUserId()) ||
!TextUtils.equals(CrashReport.getUserId(), httpExPhone)) {
CrashReport.setUserId(httpExPhone);
com.orhanobut.logger.Logger.e("登录注册等接口配置上报手机号:" + httpExPhone);
}
}
break;
}
}
long startNs = System.nanoTime();
Response response;
try {
response = chain.proceed(request);
} catch (Exception e) {
logger.log("<-- HTTP FAILED: " + e);
CrashReport.postCatchedException(new Exception("请求失败:" + request.url() + "\n" + e.getMessage()));
logger.log("请求失败:" + request.url() + "\n" + e.getMessage());
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";
logger.log("<-- "
+ response.code()
+ (response.message().isEmpty() ? "" : ' ' + 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++) {
logHeader(headers, i);
}
if (!logBody || !HttpHeaders.hasBody(response)) {
logger.log("<-- END HTTP");
} else if (bodyHasUnknownEncoding(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();
Long gzippedLength = null;
if ("gzip".equalsIgnoreCase(headers.get("Content-Encoding"))) {
gzippedLength = buffer.size();
GzipSource gzippedResponseBody = null;
try {
gzippedResponseBody = new GzipSource(buffer.clone());
buffer = new Buffer();
buffer.writeAll(gzippedResponseBody);
} finally {
if (gzippedResponseBody != null) {
gzippedResponseBody.close();
}
}
}
Charset charset = UTF8;
MediaType contentType = responseBody.contentType();
if (contentType != null) {
charset = contentType.charset(UTF8);
}
if (!isPlaintext(buffer)) {
logger.log("");
logger.log("<-- END HTTP (binary " + buffer.size() + "-byte body omitted)");
return response;
}
if (contentLength != 0) {
logger.log("");
logger.log(buffer.clone().readString(charset));
}
if (gzippedLength != null) {
logger.log("<-- END HTTP (" + buffer.size() + "-byte, "
+ gzippedLength + "-gzipped-byte body)");
} else {
logger.log("<-- END HTTP (" + buffer.size() + "-byte body)");
}
}
}
if (!response.isSuccessful()) {
CrashReport.postCatchedException(new Exception("请求失败:" + request.url() + "\n状态码:" + response.code()));
}
Response.Builder builder = response.newBuilder();
MyResponseBody myResponseBody = MyResponseBody.create(responseBody.contentType(), responseBody.string());
myResponseBody.url = request.url().url().toString();
builder.body(myResponseBody);
return builder.build();
}
private void logHeader(Headers headers, int i) {
String value = headersToRedact.contains(headers.name(i)) ? "██" : headers.value(i);
logger.log(headers.name(i) + ": " + value);
}
/**
* 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) {
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 static boolean bodyHasUnknownEncoding(Headers headers) {
String contentEncoding = headers.get("Content-Encoding");
return contentEncoding != null
&& !contentEncoding.equalsIgnoreCase("identity")
&& !contentEncoding.equalsIgnoreCase("gzip");
}
}
package com.hcb.base.app.http;
import com.google.gson.Gson;
import com.google.gson.JsonIOException;
import com.google.gson.TypeAdapter;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import com.tencent.bugly.crashreport.CrashReport;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import okio.Buffer;
import retrofit2.Converter;
import retrofit2.Retrofit;
/**
* gson解析类,自定义用于收集异常信息
*
* @author 陈聪 2020-07-20 09:20
*/
class MyGsonConverterFactory extends Converter.Factory {
/**
* Create an instance using a default {@link Gson} instance for conversion. Encoding to JSON and
* decoding from JSON (when no charset is specified by a header) will use UTF-8.
*/
public static MyGsonConverterFactory create() {
return create(new Gson());
}
/**
* Create an instance using {@code gson} for conversion. Encoding to JSON and
* decoding from JSON (when no charset is specified by a header) will use UTF-8.
*/
@SuppressWarnings("ConstantConditions") // Guarding public API nullability.
public static MyGsonConverterFactory create(Gson gson) {
if (gson == null) {
throw new NullPointerException("gson == null");
}
return new MyGsonConverterFactory(gson);
}
private final Gson gson;
private MyGsonConverterFactory(Gson gson) {
this.gson = gson;
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new GsonResponseBodyConverter<>(gson, adapter);
}
@Override
public Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new GsonRequestBodyConverter<>(gson, adapter);
}
final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private final Gson gson;
private final TypeAdapter<T> adapter;
GsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}
@Override
public T convert(ResponseBody value) throws IOException {
JsonReader jsonReader = gson.newJsonReader(value.charStream());
try {
T result = adapter.read(jsonReader);
if (jsonReader.peek() != JsonToken.END_DOCUMENT) {
throw new JsonIOException("JSON document was not fully consumed.");
}
return result;
} catch (Exception e) {
//判断是否被代理了
if (value.getClass().getDeclaredFields().length > 0
&& value.getClass().getDeclaredFields()[0].getName().equals("delegate")) {
try {
//获取被代理对象
Class eRspon = value.getClass();
eRspon.getDeclaredField("delegate").setAccessible(true);
Field field = eRspon.getDeclaredField("delegate");
field.setAccessible(true);
Object obj = field.get(value);
//判断代理对象是否为自己定义的类
if (obj instanceof MyResponseBody) {
//解析失败情况下会将异常上报服务器
String url = ((MyResponseBody) obj).url;
CrashReport.postCatchedException(new Exception("解析失败:" + url +
"\n异常如下:" +
"\n" + e.getMessage()));
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
throw e;
} finally {
value.close();
}
}
}
final class GsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
private final MediaType MEDIA_TYPE = MediaType.get("application/json; charset=UTF-8");
private final Charset UTF_8 = Charset.forName("UTF-8");
private final Gson gson;
private final TypeAdapter<T> adapter;
GsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}
@Override
public RequestBody convert(T value) throws IOException {
Buffer buffer = new Buffer();
Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
JsonWriter jsonWriter = gson.newJsonWriter(writer);
adapter.write(jsonWriter, value);
jsonWriter.close();
return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
}
}
}
package com.hcb.base.app.http;
import java.nio.charset.Charset;
import androidx.annotation.Nullable;
import okhttp3.MediaType;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSource;
import okio.ByteString;
import static okhttp3.internal.Util.UTF_8;
/**
* 继承接口返回对象,添加关联接口
*
* @author 陈聪 2020-07-20 09:25
*/
public abstract class MyResponseBody extends ResponseBody {
/** 接口请求链接 */
String url = "";
/**
* Returns a new response body that transmits {@code content}. If {@code contentType} is non-null
* and lacks a charset, this will use UTF-8.
*/
public static MyResponseBody create(@Nullable MediaType contentType, String content) {
Charset charset = UTF_8;
if (contentType != null) {
charset = contentType.charset();
if (charset == null) {
charset = UTF_8;
contentType = MediaType.parse(contentType + "; charset=utf-8");
}
}
Buffer buffer = new Buffer().writeString(content, charset);
return create(contentType, buffer.size(), buffer);
}
/** Returns a new response body that transmits {@code content}. */
public static MyResponseBody create(final @Nullable MediaType contentType, byte[] content) {
Buffer buffer = new Buffer().write(content);
return create(contentType, content.length, buffer);
}
/** Returns a new response body that transmits {@code content}. */
public static MyResponseBody create(@Nullable MediaType contentType, ByteString content) {
Buffer buffer = new Buffer().write(content);
return create(contentType, content.size(), buffer);
}
/** Returns a new response body that transmits {@code content}. */
public static MyResponseBody create(final @Nullable MediaType contentType,
final long contentLength, final BufferedSource content) {
if (content == null) throw new NullPointerException("source == null");
return new MyResponseBody() {
@Override
public @Nullable
MediaType contentType() {
return contentType;
}
@Override
public long contentLength() {
return contentLength;
}
@Override
public BufferedSource source() {
return content;
}
};
}
}