在接入 Retrofit + RxAndroid 之前,项目代码中主要存在如下问题:
服务器 API 的定义方式不一致,有的集中定义,有的定义在业务代码中,没有分类不便于维护。
Request / Response / API 三者没有对应关系(Request 参数使用 Map 传递,Response 返回 JSON 数据)
每次都需要传递 access_token 给需要验证登录的 API
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
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>> 类型。
- 利用 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 大大改善了代码的可维护性。
以 API 为中心,Request、Response、Method 一一对应,开发效率飙升
告别 Callback Hell,以同步方式写异步代码,让代码结构更清晰,更易于维护
基于事件,各种 Operator,四两拨千斤,尽情发挥你的想象力。
修订
OkHttpClient 修改为单例。