使用rxjava2+retrofit处理网络请求,线程的切换变得十分简单,代码也简洁了很多。但是简介的代码就是对可扩展性有着负面的影响,所以要对rxjava2+retrofit进行一定封装,使结构更清晰,可扩展性更强。这里给出一种可行的封装。
以下均以登陆请求为例子。
http://xxx/user/login
account
和password
均为String首先定一个retrofit接口:UserService
public interface UserService {
@FormUrlEncoded
@POST("/user/login")
Observable> login(@Field("account") String account,
@Field("password") String password);
}
这里ResponseBean
是一个返回值json对应的实体类,服务器返回的json数据格式为{code: xxx, message: xxx, data:xxx}
。而UidBean
是一个data的具体内容,也是个json实体类,具体是什么无关紧要。
/**
* 服务器返回值的实体类
* Created by stephen on 2017/9/8.
*/
public class ResponseBean<T> {
@SerializedName("code")
private int code;
@SerializedName("message")
private String message;
@SerializedName("data")
private T data;
public ResponseBean(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
public T getData() {
return data;
}
/**
* 根据返回码确定API是否请求返回失败
*
* @return 失败返回true, 成功返回false
*/
public boolean isCodeValid() {
return code == 0;
}
@Override
public String toString() {
return "code:" + code + ", message:" + message + ",data:" + data.toString();
}
}
Oberser类的封装
这里BaseObserver
里直接对Disposable
进行处理,内存泄漏什么的省心多了。同时还保存了一个子类可见的errMsg,可以获取自定义的错误类型。
(关于Disposable可以参考我的这篇文章,如果有哪里理解错了记得帮忙指出哦)。
public abstract class BaseObserver<T> implements Observer<T> {
protected String errMsg = "";
private Disposable disposable;
@Override
public void onSubscribe(Disposable d) {
disposable = d;
}
@Override
public void onNext(T t) {}
@Override
public void onError(Throwable e) {
LogUtils.e("Observer.java", e.getMessage() + " " + toastErrHint);
if (!NetworkUtils.isConnected()) {
errMsg = "网络连接出错,";
} else if (e instanceof HttpException) {
errMsg = "网络请求出错,";
} else if (e instanceof IOException) {
errMsg = "网络出错,";
}
disposeIt();
}
@Override
public void onComplete() {
disposeIt();
}
/**
* 销毁disposable
*/
private void disposeIt() {
if (disposable != null && !disposable.isDisposed()) {
disposable.dispose();
disposable = null;
}
}
}
接口类整合retrofit
HttpFactory
类里是一些网络交互的基本设置,单独放一个类是为了方便扩展。之后就会发现这样做的好处。public class HttpFactory {
private static HttpFactory instance;
public synchronized static HttpFactory getInstance() {
return instance == null ? instance = new HttpFactory() : instance;
}
private Retrofit mRetrofit;
private HttpFactory() {
//创建一个OkHttpClient并设置超时时间
OkHttpClient httpClient = new OkHttpClient.Builder()
.connectTimeout(AppConfig.DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.cookieJar(cookieJar)
.build();
mRetrofit = new Retrofit.Builder()
.client(httpClient)
.baseUrl(AppConfig.URL_BASE)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
Retrofit getmRetrofit() {
return mRetrofit;
}
}
UserApi
类将retrofit接口封装成rxjava调用的方法。public class APIUser extends BaseAPI {
private static final APIUser INSTANCE = new APIUser();
public static APIUser getInstance() {
return INSTANCE;
}
private UserService userService;
private Retrofit mRetrofit;
private APIUser() {
mRetrofit = HttpFactory.getInstance().getmRetrofit();
userService = mRetrofit.create(UserService.class);
}
/**
* 登录 这里为了方便逻辑展示,password直接明文传输
*
* @param account 账号
* @param password 密码
* @param observer 观察者
*/
public void login(String account, String password, BaseObserver observer) {
userService.login(account, password)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.map(new Function, UidBean>() {
@Override
public UidBean apply(ResponseBean responseBean) throws Exception {
return responseBean.getData();
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
}
}
愉快的调用
APIUser.getInstance().login(account, password, new BaseObserver() {
@Override
public void onNext(UidBean uidBean) {
// your logic code here
ToastUtils.showShort("登录成功");
}
});
以上实现了一个简单的封装,现在我们开始加入cookie支持:第一次拿到set-cookie的时候保存cookie,然后再每次request的时候加入cookie。
这里借用了一下GitHub上别人造的轮子,使用了PersistentCookieJar。PersistentCookieJar
是ClearableCookieJar
的一个具体实现,利用SharedPreferences保存cookie。具体使用方法如下:
HttpFactory
添加cookieJarpublic class HttpFactory {
private static HttpFactory instance;
public synchronized static HttpFactory getInstance() {
return instance == null ? instance = new HttpFactory() : instance;
}
private Retrofit mRetrofit;
private ClearableCookieJar cookieJar;
private HttpFactory() {
//设置cookie
cookieJar = new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor());
//创建一个OkHttpClient并设置超时时间
OkHttpClient httpClient = new OkHttpClient.Builder()
.connectTimeout(AppConfig.DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.cookieJar(cookieJar)
.build();
mRetrofit = new Retrofit.Builder()
.client(httpClient)
.baseUrl(AppConfig.URL_BASE)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
Retrofit getmRetrofit() {
return mRetrofit;
}
/**
* 退出登录后清除cookie
*/
public void clearCookie() {
cookieJar.clear();
}
}
这里HttpFactory
还提供了一个对外接口,可以在退出登录后调用清除clearCookie()
cookie。
现在每次处理请求的时候,ClearableCookieJar
就会帮你打点好所有的cookie验证相关信息。
很多时候服务器返回的json数据格式会有一个统一的格式,比如以上说的形式:{code: xxx, message: xxx, data:xxx}
。带有返回状态码和错误信息,如果对于每个code我们都在Observer
中的onNext
处理,显然很麻烦很冗余一点也不优雅,可扩展性也很差。比如服务器新添加一个状态码,所有的代码都要改,不切实际嘛。所以我们下面要做的就是对错误码添加统一处理。
实现思路:对于json数据response的时候,我们都会用GsonConverterFactory
来将json转换为java对象,那么我们就可以利用GsonConverterFactory
来统一处理。我们来重新定义一个类实现GsonConverterFactory
的关键方法。因为GsonConverterFactory
是final的,不可继承,所以这里会重新定义三个类:CustomGsonConverterFactory
,CustomGsonResponseBodyConverter
和CustomGsonRequestBodyConverter
分别对应默认的GsonConverterFactory
,GsonResponseBodyConverter
和GsonRequestBodyConverter
。
CustomGsonConverterFactory
/**
* 自定义的GsonConverterFactory
* 主要是重写了responseBodyConverter类
* Created by stephen on 2017/10/2.
*/
public final class CustomGsonConverterFactory 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 CustomGsonConverterFactory 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.
*/
public static CustomGsonConverterFactory create(Gson gson) {
return new CustomGsonConverterFactory(gson);
}
private final Gson gson;
private CustomGsonConverterFactory(Gson gson) {
if (gson == null) throw new NullPointerException("gson == null");
this.gson = gson;
}
@Override
public Converter responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
TypeAdapter adapter = gson.getAdapter(TypeToken.get(type));
return new CustomGsonResponseBodyConverter<>(gson, adapter);
}
@Override
public Converter requestBodyConverter(Type type, Annotation[] parameterAnnotations,
Annotation[] methodAnnotations, Retrofit retrofit) {
TypeAdapter adapter = gson.getAdapter(TypeToken.get(type));
return new CustomGsonRequestBodyConverter<>(gson, adapter);
}
}
CustomGsonRequestBodyConverter。这个类没有改动,直接copy了GsonRequestBodyConverter的源码。
final class CustomGsonRequestBodyConverter implements Converter {
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 adapter;
CustomGsonRequestBodyConverter(Gson gson, TypeAdapter 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());
}
}
CustomGsonResponseBodyConverter。重信写convert
方法。这里直接将返回值转换为responseBean,并根据返回值判断是否返回成功(这里根据responseBean.isCodeValid()
判断)。如果返回不成功,可以自定义一些exception,并抛出异常。比如我这里的throw new APIException
。
final class CustomGsonResponseBodyConverter implements Converter {
private final Gson gson;
private final TypeAdapter adapter;
private static final Charset UTF_8 = Charset.forName("UTF-8");
CustomGsonResponseBodyConverter(Gson gson, TypeAdapter adapter) {
this.gson = gson;
this.adapter = adapter;
}
@Override
public T convert(ResponseBody value) throws IOException {
String response = value.string();
//关键代码
ResponseBean responseBean = gson.fromJson(response, ResponseBean.class);
if (!responseBean.isCodeValid()) {
value.close();
throw new APIException(responseBean.getCode(), responseBean.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 {
return adapter.read(jsonReader);
} finally {
value.close();
}
}
}
同样的自定义的exception可以在BaseObserver
的onError
中捕获然后获取不同的errMsg
。
@Override
public void onError(Throwable e) {
LogUtils.e("Observer.java", e.getMessage() + " " + toastErrHint);
if (!NetworkUtils.isConnected()) {
errMsg = "网络连接出错,";
} else if (e instanceof APIException) {
APIException exception = (APIException) e;
errMsg = exception.getMessage();
} else if (e instanceof HttpException) {
errMsg = "网络请求出错,";
} else if (e instanceof IOException) {
errMsg = "网络出错,";
}
}
好了至此,封装已经可以达到了一定的可扩展性,使用也很方便。事实上你还可以在BaseObserver
中添加一些其他的东西,比如加载动画等等。
大家有意见提出来一起探讨,共同进步。:)