Get right to the point
The usual JSON data format received from the server looks something like this:
{
"code": 1,"message":"Query successful"."detail": {"aa":"123"."bb":"123"."cc":"123"}}Copy the code
So we usually define an entity class to parse the corresponding JSON:
public class Response {
@SerializedName("code")
private int code;
@SerializedName("message")
private String message;
@SerializedName("detail") private DetailBean detail; // omit getter and setter methods... public static class DetailBean { @SerializedName("aa")
private String aa;
@SerializedName("bb")
private String bb;
@SerializedName("cc") private String cc; // omit getter and setter methods... }}Copy the code
The code field represents the status, such as the following values, which may mean different things
- Code = 1 indicates success, not 1 indicates error
- Code = -101: Indicates that the token has expired
- Code = -102: Indicates that the mobile phone number has been registered
- Etc etc.
If we followed normal Retrofit+RxJava logic, the code would look like this:
//ApiService.java
public interface ApiService {
String ENDPOINT = Constants.END_POINT;
@POST("app/api")
Observable<Response1> request1(@Body Request1 request);
@POST("app/api")
Observable<Response2> request2(@Body Request2 request);
/**
* Create a new ApiService
*/
class Factory {
private Factory() { }
public static ApiService createService( ) {
OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
builder.readTimeout(10, TimeUnit.SECONDS);
builder.connectTimeout(9, TimeUnit.SECONDS);
if (BuildConfig.DEBUG) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
builder.addInterceptor(interceptor);
}
builder.addInterceptor(new HeaderInterceptor());
OkHttpClient client = builder.build();
Retrofit retrofit =
new Retrofit.Builder().baseUrl(ApiService.ENDPOINT)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
returnretrofit.create(ApiService.class); }}}Copy the code
When used:
ApiService mApiService = ApiService.Factory.createService();
mApiService.request1(request)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Response1>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Response1 response) {
int code = response.getCode();
switch (code) {
case1: / /do something
break;
case- 101: / /do something
break;
case- 102: / /do something
break;
default:
break; }}});Copy the code
If you do that for every request, you’re killing yourself, and if one day these values change, say, from -102 to -105, then you don’t have to change everything, which is scary to think about!
The solution
Retrofit retrofit =
new Retrofit.Builder().baseUrl(ApiService.ENDPOINT)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
Copy the code
AddConverterFactory (GsonConverterFactory. The create (), the code for the server returned with Gson parses the json data into entities, From here, you can define a GsonConverter to extend the original function
The default GsonConverter is composed of three classes:
- GsonConverterFactory // GsonConverterFactory class, used to create GsonConverter
- GsonResponseBodyConverter ResponseBody / / processing
- GsonRequestBodyConverter // Handles RequestBody
The name of each class is why it is easy to see that, GsonResponseBodyConverter this class must be key and have a look at this class:
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 {
returnadapter.read(jsonReader); } finally { value.close(); }}}Copy the code
You read that right, just a few lines of code… The convert() method is where you want to extend,
Just add processing code on top of the original logic! = 1 if code! = 1, throws an exception,
First go directly to the code:
//CustomGsonConverterFactory.java public class CustomGsonConverterFactory extends Converter.Factory { private final Gson gson; private CustomGsonConverterFactory(Gson gson) {if (gson == null) throw new NullPointerException("gson == null");
this.gson = gson;
}
public static CustomGsonConverterFactory create() {
return create(new Gson());
}
public static CustomGsonConverterFactory create(Gson gson) {
returnnew CustomGsonConverterFactory(gson); } @Override public Converter<ResponseBody, ? > responseBodyConverter(Typetype, Annotation[] annotations,Retrofit retrofit) { TypeAdapter<? > adapter = gson.getAdapter(TypeToken.get(type));
returnnew CustomGsonResponseBodyConverter<>(gson, adapter); } @Override public Converter<? , RequestBody> requestBodyConverter(Typetype,Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { TypeAdapter<? > adapter = gson.getAdapter(TypeToken.get(type));
returnnew CustomGsonRequestBodyConverter<>(gson, adapter); }}Copy the code
//CustomGsonRequestBodyConverter.java
final class CustomGsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
private static final Charset UTF_8 = Charset.forName("UTF-8");
private final Gson gson;
private final TypeAdapter<T> adapter;
CustomGsonRequestBodyConverter(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();
returnRequestBody.create(MEDIA_TYPE, buffer.readByteString()); }}Copy the code
//CustomGsonResponseBodyConverter.java final class CustomGsonResponseBodyConverter<T> implements Converter<ResponseBody, T> { private final Gson gson; private final TypeAdapter<T> adapter; CustomGsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) { this.gson = gson; this.adapter = adapter; } @Override public T convert(ResponseBody value) throws IOException { String response = value.string(); HttpStatus httpStatus = gson.fromJson(response, HttpStatus.class);if(httpStatus.isCodeInvalid()) { value.close(); throw new ApiException(httpStatus.getCode(), httpStatus.getMessage()); } MediaType contentType = value.contentType(); Charset charset = contentType ! = null ? contentType.charset(UTF_8) : UTF_8; InputStream inputStream = new ByteArrayInputStream(response.getBytes()); Reader reader = new InputStreamReader(inputStream, charset); JsonReader jsonReader = gson.newJsonReader(reader); try {returnadapter.read(jsonReader); } finally { value.close(); }}}Copy the code
The other two classes is the same as the default, only look at the third class CustomGsonResponseBodyConverter
There are two custom classes: HttpStatus and ApiException.
//HttpStatus.java
public class HttpStatus {
@SerializedName("code")
private int mCode;
@SerializedName("message")
private String mMessage;
public int getCode() {
return mCode;
}
public String getMessage() {
returnmMessage; } /** * WHETHER the API failed to request ** @returnFailure to returntrue, returns successfullyfalse
*/
public boolean isCodeInvalid() {
return mCode != Constants.WEB_RESP_CODE_SUCCESS;
}
}
Copy the code
//ApiException.java public class ApiException extends RuntimeException { private int mErrorCode; public ApiException(int errorCode, String errorMessage) { super(errorMessage); mErrorCode = errorCode; } /** * Check whether the token is invalid ** @returnFailure to returntrueOtherwise returnfalse;
*/
public boolean isTokenExpried() {
returnmErrorCode == Constants.TOKEN_EXPRIED; }}Copy the code
It’s easy to understand. Let’s explain a few key lines of code
String response = value.string(); // If code ==1 // if code ==1 // if code ==1 // if code ==1 // if code ==1 // if code ==1 // gson.fromJson(response, HttpStatus.class);if(httpStatus.isCodeInvalid()) { value.close(); // Throw a RuntimeException, which goes to the Subscriber onError() method and throws new ApiException(httpStatus.getCode(), httpStatus.getMessage()); }Copy the code
So there’s a little bit of a ResponseBody pit, if anyone’s ever encountered this exception
java.lang.IllegalStateException: closed
at com.squareup.okhttp.internal.http.HttpConnection$FixedLengthSource.read(HttpConnection.java:455)
at okio.Buffer.writeAll(Buffer.java:594)
at okio.RealBufferedSource.readByteArray(RealBufferedSource.java:87)
at com.squareup.okhttp.ResponseBody.bytes(ResponseBody.java:56)
at com.squareup.okhttp.ResponseBody.string(ResponseBody.java:82)
Copy the code
Because you can only read ResponseBody once, this exception is raised if you call Response.body ().string() twice or response.body().charstream () twice. Calling string() followed by charStream() also does not work.
So the usual thing to do is read it once and then save it, and then not read it from the ResponseBody.
Final usage:
Start by creating a BaseSubscriber
//BaseSubscriber.java
public class BaseSubscriber<T> extends Subscriber<T> {
protected Context mContext;
public BaseSubscriber(Context context) {
this.mContext = context;
}
@Override
public void onCompleted() {
}
@Override
public void onError(final Throwable e) {
Log.w("Subscriber onError", e);
if (e instanceof HttpException) {
// We had non-2XX http error
Toast.makeText(mContext, mContext.getString(R.string.server_internal_error), Toast.LENGTH_SHORT).show();
} else if (e instanceof IOException) {
// A network or conversion error happened
Toast.makeText(mContext, mContext.getString(R.string.cannot_connected_server), Toast.LENGTH_SHORT).show();
} else if (e instanceof ApiException) {
ApiException exception = (ApiException) e;
if(exception.istokenexpried ()) {// Handle logic corresponding to token invalidation}else {
Toast.makeText(mContext, e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
}
@Override
public void onNext(T t) {
}
}
Copy the code
Request way
ApiService mApiService = ApiService.Factory.createService();
mApiService.request1(request)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new BaseSubscriber<Response1>() {
@Override
public void onCompleted() { super.onCompleted(); } @Override public void onError(Throwable e) { super.onError(e); } @override public void onNext(Response1 response) {super.onnext (response); }});Copy the code
Well, that’s the end of this article