在日常开发中经常会涉及到网络请求,随着业务的复杂多变,对于请求库的功能及职责也要求越来越高,一个不错的请求库能使日常开发事半功倍。使用Retrofit,OkHttp,RxJava,Autodispose封装一个带有生命周期的请求库。
流程图 添加依赖dependencies {
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
// Gson
implementation 'com.google.code.gson:gson:2.8.9'
// RxJava
implementation 'io.reactivex.rxjava3:rxjava:3.1.2'
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
// AutoDispose
implementation 'com.uber.autodispose2:autodispose-android:2.1.1'
implementation 'com.uber.autodispose2:autodispose-androidx-lifecycle:2.1.1'
}
请求结果包装
一个完整的请求结果包含请求状态,远程结果,异常等。
public class RequestBuilder{ private final ResultType result; private final RequestStatus status; private final Throwable throwable; @NonNull public static RequestBuilder loading() { return new RequestBuilder<>(RequestStatus.LOADING, null); } @NonNull public static RequestBuilder success(ResultType result) { return new RequestBuilder<>(RequestStatus.SUCCEED, result); } @NonNull public static RequestBuilder failed(Throwable throwable) { return new RequestBuilder<>(RequestStatus.FAILED, throwable); } @NonNull public static RequestBuilder completed() { return new RequestBuilder<>(RequestStatus.COMPLETED); } public RequestBuilder(@NonNull RequestStatus status) { this(status, null, null); } public RequestBuilder(@NonNull RequestStatus status, ResultType result) { this(status, result, null); } public RequestBuilder(@NonNull RequestStatus status, @Nullable Throwable throwable) { this(status, null, throwable); } public RequestBuilder(@NonNull RequestStatus status, @Nullable ResultType data, @Nullable Throwable throwable) { this.result = data; this.status = status; this.throwable = throwable; } public ResultType getResult() { return result; } public RequestStatus getStatus() { return status; } public ResponseException getThrowable() { return (ResponseException) throwable; } }
请求状态
public enum RequestStatus {
LOADING,
SUCCEED,
FAILED,
COMPLETED
}
请求结果被观察者
在RequestObservable中进行请求,数据解析与转换,状态分发,订阅观察者。
public abstract class RequestObservable请求结果观察者extends Observable > { private Observer super RequestBuilder > observer; private final AndroidLifecycleScopeProvider lifecycleScopeProvider; public RequestObservable(@NonNull Lifecycle lifecycle) { this(lifecycle, Lifecycle.Event.ON_DESTROY); } public RequestObservable(@NonNull LifecycleOwner lifecycleOwner) { this(lifecycleOwner, Lifecycle.Event.ON_DESTROY); } public RequestObservable(Lifecycle lifecycle, Lifecycle.Event event) { this(AndroidLifecycleScopeProvider.from(lifecycle, event)); } public RequestObservable(LifecycleOwner lifecycleOwner, Lifecycle.Event event) { this(AndroidLifecycleScopeProvider.from(lifecycleOwner, event)); } public RequestObservable(AndroidLifecycleScopeProvider lifecycleScopeProvider) { this.lifecycleScopeProvider = lifecycleScopeProvider; } @Override protected void subscribeActual(@NonNull Observer super RequestBuilder > observer) { this.observer = observer; this.observer.onNext(RequestBuilder.loading()); dispatcherStrategy(); } private void dispatcherStrategy() { request().distinct() .subscribeOn(Schedulers.io()) .observeOn(Schedulers.io()) .map(this::transform) // 在io线程中对数据进行解析转换 .observeOn(AndroidSchedulers.mainThread()) // 切换到UI线程,通过回调更新UI层业务 .to(AutoDispose.autoDisposable(lifecycleScopeProvider)) .subscribe(this::emitValue, this::emitError, this::emitCompleted); } @WorkerThread protected abstract Observable request(); @WorkerThread protected abstract ResultType transform(ResponseType result) throws ResponseException; @MainThread private void emitValue(ResultType value) { if (this.observer != null) { this.observer.onNext(RequestBuilder.success(value)); } } @MainThread private void emitError(Throwable throwable) { if (this.observer != null) { this.observer.onNext(RequestBuilder.failed(throwable)); } } @MainThread private void emitCompleted() { if (this.observer != null) { this.observer.onNext(RequestBuilder.completed()); } } public void execute(RequestSubscriber request) { this.subscribe(new RequestObserver<>(request)); } }
在RequestObserver中对请求过程事件分发处理。
public class RequestObserver回调结果implements Observer > { private final RequestSubscriber subscriber; public RequestObserver(@NonNull RequestSubscriber subscriber) { this.subscriber = subscriber; } @Override public void onSubscribe(@NonNull Disposable disposable) { } @Override public void onNext(@NonNull RequestBuilder result) { try { switch (result.getStatus()) { case LOADING: this.subscriber.onLoading(); break; case SUCCEED: this.subscriber.onSuccess(result.getResult()); break; case FAILED: this.subscriber.onFailed(result.getThrowable()); break; case COMPLETED: this.subscriber.onCompleted(); break; } } catch (Throwable throwable) { onError(throwable); } } @Override public void onError(@NonNull Throwable e) { this.subscriber.onFailed(NetworkException.UNKNOWN_NETWORK_EXCEPTION); } @Override public void onComplete() { } }
定义一个结果回调接口,将请求状态及请求结果回调给UI层,进行对应的业务处理。
public interface RequestSubscriber解析工厂{ default void onLoading() { } default void onSuccess(ResultType result) { } default void onFailed(ResponseException throwable) { } default void onCompleted() { } }
在日常开发中,由于后台语言特性的问题,返回的结果多变,直接使用Retrofit提供的解析工厂时遇到类型不匹配的结果就会出错,就需要单独对每个API返回的数据进行处理,如下:
示例1:
{
"code": 200,
"msg": "请求成功"
"data": {
"token":"592faa1b-0e1f-4b84-9ab4-786efcb774bb"
}
}
示例2,data返回null:
{
"code": 1,
"msg": "请求失败"
"data": null
}
示例3,约定好的data值为对象,结果返回[]:
{
"code": 1,
"msg": "请求失败"
"data": [],
}
定义解析工厂,直接返回字符串,同时也向外提供了对应的callback接口,可针对一些公共返回值做处理。
public class JsonConverterFactoryextends Converter.Factory { private final Gson gson; private final OnCallback callback; public JsonConverterFactory(@NonNull Gson gson) { this(gson, null); } public JsonConverterFactory(OnCallback callback) { this(JSONHelper.jsonConverter(), callback); } public JsonConverterFactory(@NonNull Gson gson, @Nullable OnCallback callback) { this.gson = gson; this.callback = callback; } @Override public Converter, RequestBody> requestBodyConverter(@NonNull Type type, @NonNull Annotation[] parameterAnnotations, @NonNull Annotation[] methodAnnotations, @NonNull Retrofit retrofit) { return new JsonRequestBodyConverter<>(gson, gson.getAdapter(TypeToken.get(type))); } @Override public Converter responseBodyConverter(@NonNull Type type, @NonNull Annotation[] annotations, @NonNull Retrofit retrofit) { return value -> { if (callback != null) { return callback.converter(value.string()); } else { return value; } }; } public interface OnCallback { T converter(String result) throws ResponseException; } }
转换响应体
public class JsonRequestBodyConverter请求拦截implements Converter { private static final Charset UTF_8 = StandardCharsets.UTF_8; private static final MediaType MEDIA_TYPE = MediaType.get("application/json; charset=UTF-8"); private final Gson gson; private final TypeAdapter adapter; public JsonRequestBodyConverter(Gson gson, TypeAdapter adapter) { this.gson = gson; this.adapter = adapter; } @Override public RequestBody convert(@NonNull 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()); } }
对于大多数API,都需要传递一些公共参数,如果每次调用一个API都配置一些参数,那样就显得不太友好,代码也相对冗余,针对公共参数建议使用拦截器,在拦截器中添加请求头信息,如:User-Agent, Token, version,sign等。
public class RequestInterceptor implements Interceptor {
private final onCallback callback;
public RequestInterceptor(onCallback callback) {
this.callback = callback;
}
@NonNull
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
return chain.proceed(callback.onCallback(chain.request()).build());
}
public interface onCallback {
Request.Builder onCallback(Request request);
}
}
对于一些服务器异常,网络异常,解析异常(API返回非JSON格式结果)等,统一在拦截其中处理。
public class RequestErrorInterceptor implements Interceptor {
@NonNull
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
try {
if (NetworkHelper.isConnected(ApplicationProvider.appContext)) {
return chain.proceed(chain.request());
} else {
throw NetworkException.NETWORK_CONNECTED_EXCEPTION;
}
} catch (Exception e) {
throw NetworkException.handleException(e);
}
}
}
异常处理
定义请求响应异常包装类,方便传入code与msg。
public class ResponseException extends IOException {
private final int code;
private final String msg;
public ResponseException(int code, @Nullable String msg) {
super(msg);
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
@NonNull
public static ResponseException builder(int code, @Nullable String msg) {
return new ResponseException(code, msg);
}
}
处理常见异常
public class NetworkException {
// 无有效网络链接
public static ResponseException NETWORK_CONNECTED_EXCEPTION;
// 请求超时
public static ResponseException REQUEST_TIMEOUT_EXCEPTION;
// 404,500
public static ResponseException HTTP_REQUEST_EXCEPTION;
// 数据解析错误
public static ResponseException DATA_PARSE_EXCEPTION;
// 未知网络错误定义为网络异常
public static ResponseException UNKNOWN_NETWORK_EXCEPTION;
static {
NETWORK_CONNECTED_EXCEPTION = new ResponseException(-101, "网络链接异常");
REQUEST_TIMEOUT_EXCEPTION = new ResponseException(-102, "请求超时");
HTTP_REQUEST_EXCEPTION = new ResponseException(-103, "网络异常");
DATA_PARSE_EXCEPTION = new ResponseException(-104, "解析异常");
UNKNOWN_NETWORK_EXCEPTION = new ResponseException(-105, "网络异常");
}
public static ResponseException handleException(Exception throwable) {
if (throwable instanceof HttpException) {
HttpException exception = (HttpException) throwable;
return ResponseException.builder(exception.code(), exception.message());
} else if (throwable instanceof JsonParseException
|| throwable instanceof JSonException
|| throwable instanceof ParseException
|| throwable instanceof MalformedJsonException) {
return DATA_PARSE_EXCEPTION;
} else if (throwable instanceof InterruptedIOException) {
return REQUEST_TIMEOUT_EXCEPTION;
} else {
return UNKNOWN_NETWORK_EXCEPTION;
}
}
}
定义请求结果数据模型
根据API返回结果json, 定义数据模型。
public class ResultBody客户端请求类{ @SerializedName("code") private final int code; @SerializedName("msg") private final String msg; @SerializedName("time") private final String time; @SerializedName("data") private final String data; @Expose(serialize = false, deserialize = false) private T result; @NonNull public static ResultBody builder(int code, String msg, String time, @Nullable String data) { return new ResultBody<>(code, msg, time, data); } public ResultBody(int code, String msg) { this(code, msg, "", ""); } public ResultBody(int code, String msg, String time, String data) { this.code = code; this.msg = msg; this.time = time; this.data = data; } public int getCode() { return code; } public String getMsg() { return msg; } public String getTime() { return time; } public String getData() { return data; } public T getResult() { return result; } public ResultBody setResult(T result) { this.result = result; return this; } @NonNull @Override public String toString() { return JSONHelper.toJson(this); } }
在这里为每次请求添加请求头,对请求参数包装再处理,拦截异常状态,具体设置请参考对应逻辑。
public class ClientRequest {
private static final long REQUEST_TIMEOUT = 30L;
private static final String REQUEST_GET = "GET";
private static final String REQUEST_POST = "POST";
private static final String REQUEST_PARAMS_SIGN = "sign";
private static final String REQUEST_PARAMS_TOKEN = "token";
private static final String REQUEST_PARAMS_APP_KEY = "appkey";
private static final String REQUEST_PARAMS_TIMESTAMP = "timestamp";
private volatile static ClientRequest client = null;
private final Retrofit.Builder retrofit;
private final OkHttpClient.Builder httpClient;
private ClientRequest() {
retrofit = retrofitCreator();
httpClient = httpClientCreator();
}
public static ClientRequest getClient() {
if (client == null) {
synchronized (ClientRequest.class) {
if (client == null) {
client = new ClientRequest();
}
}
}
return client;
}
@NonNull
private Retrofit.Builder retrofitCreator() {
return new Retrofit.Builder()
.baseUrl(Constants.base_URI)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(new JsonConverterFactory<>(this::resultConverter))
.addCallAdapterFactory(RxJava3CallAdapterFactory.createWithScheduler(Schedulers.io()));
}
@NonNull
private OkHttpClient.Builder httpClientCreator() {
HttpLoggingInterceptor.Level loggingLevel = HttpLoggingInterceptor.Level.BODY;
return new OkHttpClient().newBuilder()
.readTimeout(REQUEST_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(REQUEST_TIMEOUT, TimeUnit.SECONDS)
.connectTimeout(REQUEST_TIMEOUT, TimeUnit.SECONDS)
.addInterceptor(new RequestErrorInterceptor()) // 请求错误拦截
.addInterceptor(new HttpLoggingInterceptor().setLevel(loggingLevel)); // 打印请求日志
}
public T create(Class service) {
return create(true, service);
}
public T create(boolean isNeedToken, Class service) {
RequestInterceptor requestInterceptor = new RequestInterceptor(request -> getRequestBuilder(request, isNeedToken));
return retrofit.client(httpClient.addInterceptor(requestInterceptor).build()).build().create(service);
}
@NonNull
private ResultBody resultConverter(String result) throws ResponseException {
try {
JSonObject jsonObject = new JSONObject(result);
int code = jsonObject.optInt("code");
String msg = jsonObject.optString("msg");
String time = jsonObject.optString("time");
if (code == ResultCode.SUCCESS) { // code值对应的状态非SUCCESS状态,则抛出异常,走失败回调。
String data = jsonObject.optString("data");
return ResultBody.builder(code, msg, time, data);
} else {
if (code == ResultCode.TOKEN_EXCEED) { // Token过期,清除本地存储的Token
AccountHelper.getInstance().clear();
}
throw ResponseException.builder(code, msg);
}
} catch (JSonException e) {
throw NetworkException.DATA_PARSE_EXCEPTION;
}
}
@NonNull
private Request.Builder getRequestBuilder(@NonNull Request request, boolean isNeedToken) {
Request.Builder builder = request.newBuilder();
if (request.method().equals(REQUEST_POST)) { // Post
builder.post(getRequestBody(request.body(), isNeedToken));
} else if (request.method().equals(REQUEST_GET)) { // Get
builder.url(getHttpUrl(request.url().newBuilder().build(), isNeedToken));
}
return builder;
}
@NonNull
private RequestBody getRequestBody(@Nullable RequestBody requestBody, boolean isNeedToken) {
FormBody.Builder builder = new FormBody.Builder(StandardCharsets.UTF_8);
if (requestBody == null) return builder.build(); // 请求参数为空时直接返回。
Map params = new HashMap<>(); // 收集参数生成sign值
// 设置API私有参数
if (requestBody instanceof FormBody) {
FormBody formBody = (FormBody) requestBody;
for (int i = 0; i < formBody.size(); i++) {
String key = formBody.name(i);
String value = formBody.value(i);
builder.add(key, value);
params.put(key, value);
}
}
// 设置API公共参数
params.put(REQUEST_PARAMS_APP_KEY, Constants.APP_KEY);
builder.add(REQUEST_PARAMS_APP_KEY, Constants.APP_KEY);
String timestamp = String.valueOf(System.currentTimeMillis());
params.put(REQUEST_PARAMS_TIMESTAMP, timestamp);
builder.add(REQUEST_PARAMS_TIMESTAMP, timestamp); // 时间戳
// 设置Token
String token = AccountHelper.getInstance().getToken();
if (isNeedToken && !token.isEmpty()) {
params.put(REQUEST_PARAMS_TOKEN, token);
builder.add(REQUEST_PARAMS_TOKEN, token); // token
}
builder.add(REQUEST_PARAMS_SIGN, getSign(params)); // Sign
return builder.build();
}
@NonNull
private HttpUrl getHttpUrl(@Nullable HttpUrl url, boolean isNeedToken) {
HttpUrl.Builder builder = new HttpUrl.Builder();
if (url == null) return builder.build(); // 请求参数为空时直接返回。
builder.host(url.host()); // 设置Host
builder.port(url.port()); // 设置API端口号
builder.scheme(url.scheme()); // 设置scheme(Http/Https)
// 设置Api接口名
for (String pathSegment : url.pathSegments()) {
builder.addPathSegment(pathSegment);
}
// 收集参数生成sign值
Map params = new HashMap<>();
// 设置API私有参数
for (int i = 0; i < url.querySize(); i++) {
String key = url.queryParameterName(i);
String value = url.queryParameterValue(i);
builder.addQueryParameter(key, value);
params.put(key, value);
}
// 设置API公共参数
params.put(REQUEST_PARAMS_APP_KEY, Constants.APP_KEY);
builder.addQueryParameter(REQUEST_PARAMS_APP_KEY, Constants.APP_KEY); // AppKey
String timestamp = String.valueOf(System.currentTimeMillis());
params.put(REQUEST_PARAMS_TIMESTAMP, timestamp);
builder.addQueryParameter(REQUEST_PARAMS_TIMESTAMP, timestamp); // 时间戳
String token = AccountHelper.getInstance().getToken();
if (isNeedToken && !token.isEmpty()) {
params.put(REQUEST_PARAMS_TOKEN, token);
builder.addQueryParameter(REQUEST_PARAMS_TOKEN, token); // token
}
builder.addQueryParameter(REQUEST_PARAMS_SIGN, getSign(params)); // Sign
return builder.build();
}
@NonNull
private String getSign(@NonNull Map params) {
// 根据参数名称的ASCII码表的顺序排序
List> mappingList = new ArrayList<>(params.entrySet());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Collections.sort(mappingList, Map.Entry.comparingByKey());
} else {
Collections.sort(mappingList, (value1, value2) -> value2.getKey().compareTo(value1.getKey()));
}
StringBuilder builder = new StringBuilder();
builder.append(Constants.APP_SECRET);
for (Map.Entry param : mappingList) {
builder.append(param.getValue());
}
builder.append(Constants.APP_SECRET);
// 生成最终的签名字符串
return toMD5(builder.toString());
}
@NonNull
private String toMD5(@NonNull String value) {
byte[] hash;
try {
hash = MessageDigest.getInstance("MD5").digest(value.getBytes(StandardCharsets.UTF_8));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return "";
}
StringBuilder hex = new StringBuilder(hash.length * 2);
for (byte b : hash) {
if ((b & 0xFF) < 0x10) hex.append("0");
hex.append(Integer.toHexString(b & 0xFF));
}
return hex.toString();
}
}
使用
单个API请求
创建登录API。
public interface AuthService {
@FormUrlEncoded
@POST("user/login")
Observable> login(@Field("username") String phone, @Field("password") String password);
}
在数据仓库中创建API,处理数据解析与转换。
public class AuthRepository extends baseRepository {
public AuthRepository(@NonNull LifecycleOwner lifecycleOwner) {
super(lifecycleOwner);
}
public RequestObservable, Account> login(String phone, String password) {
return new RequestObservable, Account>(getLifecycleOwner()) {
@Override
public Observable> request() { // 登录API接口
return ClientRequest.getClient().create(false, AuthService.class).login(phone, password);
}
@Override
protected Account transform(ResultBody result) throws ResponseException { // 对API返回结果进行转换
if (JSONHelper.isJsonObject(result.getData())) {
String json = JSONHelper.getValue(result.getData(), "userInfo");
Account account = JSONHelper.jsonConverter().fromJson(json, Account.class);
AccountHelper.getInstance().save(account); // 存储用户信息
return account;
} else {
throw NetworkException.DATA_PARSE_EXCEPTION;
}
}
};
}
}
在ViewModel中调用登录API。
public class RequestViewModel extends baseViewModel {
private final AuthRepository repository;
public RequestViewModel(@NonNull LifecycleOwner lifecycleOwner) {
super(lifecycleOwner);
this.repository = new AuthRepository(lifecycleOwner);
}
public void login(String phone, String password) {
repository.login(phone, password).execute(new RequestSubscriber() {
@Override
public void onLoading() {
Log.e("请求状态", "loading...");
}
@Override
public void onSuccess(Account result) {
Log.e("请求状态", "success");
Log.e("结果", "token:" + result.getToken());
}
@Override
public void onFailed(ResponseException throwable) {
Log.e("请求状态", "failed");
Log.e("失败", "code:" + throwable.getCode() + "tmsg:" + throwable.getMsg());
}
@Override
public void onCompleted() {
Log.e("请求状态", "completed");
}
});
}
}
多个API异步请求同步返回结果
创建账户中心API。
public interface AccountService {
@GET("API接口")
Observable> getAccountInfo();
@GET("API接口")
Observable> getAccountAmount();
@GET("API接口")
Observable> checkHasUnreadMsg();
}
在数据仓库中创建API,处理数据解析与转换。
public class AccountRepository extends baseRepository {
public AccountRepository(@NonNull LifecycleOwner lifecycleOwner) {
super(lifecycleOwner);
}
public RequestObservable getAccount() {
return new RequestObservable(getLifecycleOwner()) {
@Override
protected Observable request() {
Observable> account = ClientRequest.getClient().create(AccountService.class).getAccountInfo();
Observable> accountAmount = ClientRequest.getClient().create(AccountService.class).getAccountAmount();
return Observable.zip(account, accountAmount, AccountBody::new);
}
@Override
protected AccountBody transform(AccountBody result) {
ResultBody account = analysisAccount(result.getAccount());
ResultBody accountAmount = analysisAccountAmount(result.getAccountAmount());
return new AccountBody(account, accountAmount);
}
};
}
@NonNull
public ResultBody analysisAccount(@NonNull ResultBody resultBody) {
if (JSONHelper.isJsonObject(resultBody.getData())) {
String json = JSONHelper.getValue(resultBody.getData(), "userInfo");
ResultBody result = new ResultBody<>(resultBody.getCode(), resultBody.getMsg(), resultBody.getTime(), resultBody.getData());
result.setResult(JSONHelper.jsonConverter().fromJson(json, Account.class));
return result;
} else {
return resultBody;
}
}
public ResultBody analysisAccountAmount(@NonNull ResultBody resultBody) {
if (JSONHelper.isJsonObject(resultBody.getData())) {
ResultBody result = new ResultBody<>(resultBody.getCode(), resultBody.getMsg(), resultBody.getTime(), resultBody.getData());
result.setResult(JSONHelper.jsonConverter().fromJson(resultBody.getData(), AccountAmount.class));
return result;
} else {
return resultBody;
}
}
}
在ViewModel中调用账户中心API。
public class RequestViewModel extends baseViewModel {
private final AccountRepository repository;
public RequestViewModel(@NonNull LifecycleOwner lifecycleOwner) {
super(lifecycleOwner);
this.repository = new AccountRepository(lifecycleOwner);
}
public void getAccount() {
repository.getAccount().execute(new RequestSubscriber() {
@Override
public void onLoading() {
Log.e("请求状态", "loading...");
}
@Override
public void onSuccess(AccountBody result) {
Log.e("请求状态", "success");
Log.e("结果", result.toString());
}
@Override
public void onFailed(ResponseException throwable) {
Log.e("请求状态", "completed");
Log.e("失败", "code:" + throwable.getCode() + "tmsg:" + throwable.getMsg());
}
@Override
public void onCompleted() {
Log.e("请求状态", "completed");
}
});
}
}
对于同步与合并请求,请参考RxJava中的函数 .switchMap
请求日志请求成功log
E/开始状态: loading...
D/OkHttp: --> POST https://API地址
D/OkHttp: Content-Type: application/x-www-form-urlencoded
D/OkHttp: Content-Length: 37
D/OkHttp: username=17691129200&password=M000000
D/OkHttp: --> END POST (37-byte body)
D/OkHttp: <-- 200 OK https://API地址 (744ms)
D/OkHttp: Server: nginx
D/OkHttp: Date: Tue, 09 Nov 2021 05:55:40 GMT
D/OkHttp: Content-Type: application/json; charset=utf-8
D/OkHttp: Transfer-Encoding: chunked
D/OkHttp: Connection: keep-alive
D/OkHttp: X-Powered-By: PHP/7.2.34
D/OkHttp: Set-cookie: ""
D/OkHttp: Expires: Thu, 19 Nov 1981 08:52:00 GMT
D/OkHttp: Cache-Control: no-store, no-cache, must-revalidate
D/OkHttp: Pragma: no-cache
D/OkHttp: X-frame-Options: SAMEORIGIN
D/OkHttp: X-XSS-Protection: 1; mode=block
D/OkHttp: X-Content-Type-Options: nosniff
D/OkHttp: {"code":200,"msg":"登陆成功","time":1636437339,"data":{"userInfo":{"id":473435,"username":"36WD2qYpET","email":"","mobile":"17691129200","score":0,"token":"7be17f28-8f31-411e-8e43-dc3e8ee7b76f","nickname":"语蓉如南","idcard":"","registertime":1631771027,"realname":"","portrait":"","promote_id":0,"expires_in":2592000,"has_password":0,"is_app_card":1}}}
D/OkHttp: <-- END HTTP (361-byte body)
E/开始状态: success
E/结果: token:7be17f28-8f31-411e-8e43-dc3e8ee7b76f
E/开始状态: completed
请求失败log
E/开始状态: loading...
D/OkHttp: --> POST https://API地址
D/OkHttp: Content-Type: application/x-www-form-urlencoded
D/OkHttp: Content-Length: 37
D/OkHttp: username=17691129200&password=M000000
D/OkHttp: --> END POST (37-byte body)
D/OkHttp: <-- 200 OK https://API地址 (797ms)
D/OkHttp: Server: nginx
D/OkHttp: Date: Tue, 09 Nov 2021 05:59:17 GMT
D/OkHttp: Content-Type: application/json; charset=utf-8
D/OkHttp: Transfer-Encoding: chunked
D/OkHttp: Connection: keep-alive
D/OkHttp: X-Powered-By: PHP/7.2.34
D/OkHttp: X-frame-Options: SAMEORIGIN
D/OkHttp: X-XSS-Protection: 1; mode=block
D/OkHttp: X-Content-Type-Options: nosniff
D/OkHttp: {"code":0,"msg":"验签失败","time":1636437556,"data":null}
D/OkHttp: <-- END HTTP (61-byte body)
E/开始状态: failed
E/失败: code:0 msg:验签失败



