Retrofit + RxAndroid 实践总结_List

在接入 Retrofit + RxAndroid 之前,项目代码中主要存在如下问题:

  1. 服务器 API 的定义方式不一致,有的集中定义,有的定义在业务代码中,没有分类不便于维护。
  2. Request / Response / API 三者没有对应关系(Request 参数使用 Map 传递,Response 返回 JSON 数据)
  3. 每次都需要传递 ​​access_token​​ 给需要验证登录的 API
  4. Response 中错误信息的数据结构不一致,错误处理不统一

引入 Retrofit + RxAndroid 后,以上问题都会迎刃而解。

定义基类

首先定义一个 ​​BaseResponse​​,所有的 Response 都要继承自它。

Response

@Keep
public class BaseResponse {
public static final int CODE_SUCCESS = 0;

public String msg;
public int code;
@SerializedName("error_response")
public ErrorResponse errorResponse;

public static final class ErrorResponse {
public String msg;
public int code;
}
}

​BaseResponse​​ 的主要作用是统一了错误信息的格式,同时为后面统一错误处理打好基础。

ErrorResponseException

为了统一请求错误返回错误,我们定义了一个继承自 ​​IOException​​​ 的子类 ​​ErrorResponseException​​:

public class ErrorResponseException extends IOException {
public ErrorResponseException() {
super();
}

public ErrorResponseException(String detailMessage) {
super(detailMessage);
}
}

定义 Service Method

public interface TradesService {
@GET("kdt.tradecategories/1.0.0/get")
Observable<Response<CategoryResponse>> tradeCategories();

@FormUrlEncoded
@GET("kdt.trade/1.0.0/get")
Observable<Response<TradeItemResponse>> tradeDetail(@Field("tid") String tid);
}

其中 ​​CategoryResponse​​​、​​TradeItemResponse​​​ 全部继承自 ​​BaseResponse​​。

泛型 ​​Response​​ 由 Retrofit 提供,定义了三个成员变量:

private final okhttp3.Response rawResponse;
private final T body;
private final ResponseBody errorBody;

可以看出,​​Response​​​ 是对 ​​okhttp3.Response​​​ 的封装,​​body​​​ 是一个 ​​BaseResponse​​ 实例。

因为 ​​Response​​​ 只会根据 ​​code​​​ 值判断请求是否成功,而不会判断 ​​body​​​ 的内容是否出错,所以我们把 ​​Response​​ 中的错误信息称作请求错误,把 ​​body​​ 中的错误信息称作返回错误

既然 ​​Response​​​ 包含了 ​​BaseResponse​​​(即 ​​body​​),那么我们就可以对两种错误(请求错误、返回错误)进行统一处理。

统一错误处理

Service Method 的返回值类型是 ​​Observable<Response<? extends BaseResponse>​​​,实际上业务方想要的是 ​​Observable<? extends BaseResponse>​​​,那么我们就定义一个 ​​Transformer​​​ 来转换这两个 ​​Observable​​。

转换过程其实就是一个错误处理的过程,因为我们要从 ​​Response​​​ 中把 ​​BaseResponse​​​ 剥离出来,如果 ​​Response​​​ 或者 ​​BaseResponse​​ 中含有错误信息则意味着转换失败,直接抛出我们已经定义好的 ​​BaseErrorResponse​​​,回调 ​​Subscriber​​​ 的 ​​onError​​ 方法。

public class ErrorCheckerTransformer<T extends Response<R>, R extends BaseResponse>
implements Observable.Transformer<T, R> {

public static final String DEFAULT_ERROR_MESSAGE = "Oh, no";

private Context mContext;

public ErrorCheckerTransformer(final Context context) {
mContext = context;
}

@Override
public Observable<R> call(Observable<T> observable) {
return observable.map(new Func1<T, R>() {
@Override
public R call(T t) {
String msg = null;
if (!t.isSuccessful() || t.body() == null) {
msg = DEFAULT_ERROR_MESSAGE;
} else if (t.body().errorResponse != null) {
msg = t.body().errorResponse.msg;
if (msg == null) {
msg = DEFAULT_ERROR_MESSAGE;
}
} else if (t.body().code != BaseResponse.CODE_SUCCESS) {
msg = t.body().msg;
if (msg == null) {
msg = DEFAULT_ERROR_MESSAGE;
}
}

if (msg != null) {
try {
throw new ErrorResponseException(msg);
} catch (ErrorResponseException e) {
throw Exceptions.propagate(e);
}
}

return t.body();
}
});
}
}

当然,你也可以在这里判断是否需要唤起登录请求。

创建 Service Method

不同的 Service Method 可能对应着不同的网关,因此我们需要定义一个工厂为不同的网关生产 Service Method。

public class ServiceFactory {
public static final String OLD_BASE_URL = "https://liangfeizc.com/gw/oauthentry/";
public static final String NEW_BASE_URL = "https://liangfei.me/api/oauthentry/";

public static <T> T createOldService(Class<T> serviceClazz) {
return createOauthService(OLD_BASE_URL, serviceClazz);
}

public static <T> T createNewService(Class<T> serviceClazz) {
return createOauthService(NEW_BASE_URL, serviceClazz);
}

public static <T> T createOauthService(String baseUrl, Class<T> serviceClazz) {
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
HttpUrl url = request.url().newBuilder()
.addQueryParameter("access_token", UserInfo.getAccessToken())
.build();
request = request.newBuilder().url(url).build();
return chain.proceed(request);
}
})
.build();

Retrofit retrofit = new Retrofit.Builder()
.client(client)
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();

return retrofit.create(serviceClazz);
}
}

因为这两个网关都要求登录后才能访问,因此我们通过 ​​OkHttpClient#addInterceptor​​​ 拦截 ​​Request​​​ 之后加上了参数 ​​access_token​​。

线程模型

大多数情况下,我们都会在 io 线程发起 request,在主线程处理 response,所以我们定义了一个默认的线程模型:

public class SchedulerTransformer<T> implements Observable.Transformer<T, T> {
@Override
public Observable<T> call(Observable<T> observable) {
return observable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}

public static <T> SchedulerTransformer<T> create() {
return new SchedulerTransformer<>();
}
}

为了方便使用,我们又定义了一个 ​​DefaultTransformer​​​ 把 ​​SchedulerTransformer​​​ 和 ​​ErrorCheckerTransformer​​ 结合起来。

public class DefaultTransformer<T extends Response<R>, R extends BaseResponse>
implements Observable.Transformer<T, R> {

private Context mContext;

public DefaultTransformer(final Context context) {
mContext = context;
}

@Override
public Observable<R> call(Observable<T> observable) {
return observable
.compose(new SchedulerTransformer<T>())
.compose(new ErrorCheckerTransformer<T, R>(mContext));
}
}

Subscriber

为了进一步统一错误消息的展示方式,我们又对 ​​Subscriber​​ 进行了一层封装。

BaseSubscriber

public abstract class BaseSubscriber<T> extends Subscriber<T> {
private Context mContext;

public BaseSubscriber(Context context) {
mContext = context;
}

public Context getContext() {
return mContext;
}
}

ToastSubscriber

以 Toast 形式展示错误消息。

public abstract class ToastSubscriber<T> extends BaseSubscriber<T> {

public ToastSubscriber(Context context) {
super(context);
}

@CallSuper
@Override
public void onError(Throwable e) {
ToastUtil.show(getContext(), e.getMessage());
}
}

DialogSubscriber

以 Dialog 形式展示错误消息。

public abstract class DialogSubscriber<T> extends BaseSubscriber<T> {

public DialogSubscriber(Context context) {
super(context);
}

@CallSuper
@Override
public void onError(Throwable e) {
DialogUtil.showDialog(getContext(), e.getMessage(), "OK", true);
}
}

如何使用

我们以获取 Category 为例来说明如何利用 Retrofit 和 RxAndroid 来改写现有模块。

1. 定义 CategoryResponse

​CategoryResponse​​​ 必须继承自 ​​BaseResponse​​,里面包含了错误信息的数据结构。

@Keep
public class CategoryResponse extends BaseResponse {
public Response response;

@Keep
public static final class Response {
public List<Category> categories;
}
}

其中 ​​Category​​ 是具体的实体类型。

2. 定义 Service Method

public interface TradesService {
@GET("kdt.tradecategories/1.0.0/get")
Observable<Response<CategoryResponse>> tradeCategories();

注意点

  • ​TradesService​​​ 必须是一个 ​​interface​​​,而且不能继承其他 ​​interface​​。
  • ​tradeCategories​​​ 的返回值必须是 ​​Observable<Response<? extends BaseResponse>>​​ 类型。

3. 利用 ServiceFactory 创建一个 TradeService 实例

在适当的时机(​​Activity#onCreate​​​、​​Fragment#onViewCreated​​​ 等)根据网关类型通过 ​​ServiceFactory​​​ 创建一个 ​​TradeService​​ 实例。

mTradesService = ServiceFactory.createNewService(TradesService.class)

4. TradeService 获取数据

mTradesService.tradeCategories()
.compose(new DefaultTransformer<Response<CategoryResponse>, CategoryResponse>(getActivity()))
.map(new Func1<CategoryResponse, List<Category>>() {
@Override
public List<Category> call(CategoryResponse response) {
return response.response.categories;
}
})
.flatMap(new Func1<List<Category>, Observable<Category>>() {
@Override
public Observable<Category> call(List<Category> categories) {
return Observable.from(categories);
}
})
.subscribe(new ToastSubscriber<Category>() {
@Override
public void onCompleted() {
hideProgressBar();
// business related code
}

@Override
public void onError(Throwable e) {
super.onError(e);
hideProgressBar();
// business related code
}

@Override
public void onNext(Category category) {
// business related code
}
});

注意:​​DefaultTransformer​​ 包含了线程分配错误处理两部分功能,所以调用方只需要关心正确的数据就可以了。

测试

NetworkBehavior - 网络环境模拟

private void givenNetworkFailurePercentIs(int failurePercent) {
mNetworkBehavior.setDelay(0, TimeUnit.MILLISECONDS);
mNetworkBehavior.setVariancePercent(0);
mNetworkBehavior.setFailurePercent(failurePercent);
}

TestSubscriber - 带断言的 Subscriber

private TestSubscriber<Response<CategoryResponse>> mTestSubscriberCategory = TestSubscriber.create()
subscriber.assertError(RuntimeException.class);
subscriber.assertNotCompleted();

MockRetrofit - 为 Retrofit 添加 Mock 数据(NetworkBehavior 等)

@Before
public void setUp() {
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl(ServiceFactory.CARMEN_BASE_URL)
.build();

MockRetrofit mockRetrofit = new MockRetrofit.Builder(retrofit)
.networkBehavior(mNetworkBehavior)
.build();
}

BehaviorDelegate - Retrofit Service 的代理,用于产生 Mock 数据

BehaviorDelegate<TradesService> delegate = mockRetrofit.create(TradesService.class);
mTradesServiceMock = new TradesServiceMock(delegate);
public class TradesServiceMock implements TradesService {
private final BehaviorDelegate<TradesService> mDelegate;

public TradesServiceMock(BehaviorDelegate<TradesService> delegate) {
mDelegate = delegate;
}

@Override
public Observable<Response<CategoryResponse>> tradeCategories() {
return mDelegate.returningResponse("{\"error_response\": \"my god\"}").tradeCategories();
}
}

总结

通过以上实践可以看出,Retrofit + RxAndroid 大大改善了代码的可维护性。

  1. 以 API 为中心,Request、Response、Method 一一对应,开发效率飙升
  2. 告别 Callback Hell,以同步方式写异步代码,让代码结构更清晰,更易于维护
  3. 基于事件,各种 Operator,四两拨千斤,尽情发挥你的想象力。