在接入 Retrofit + RxAndroid 之前,项目代码中主要存在如下问题:
- 服务器 API 的定义方式不一致,有的集中定义,有的定义在业务代码中,没有分类不便于维护。
- Request / Response / API 三者没有对应关系(Request 参数使用 Map 传递,Response 返回 JSON 数据)。
- 每次都需要传递
access_token
给需要验证登录的 API。 - Response 中错误信息的数据结构不一致,错误处理不统一。
引入 Retrofit + RxAndroid 后,以上问题都会迎刃而解。
定义基类
首先定义一个 BaseResponse
,所有的 Response 都要继承自它。
Response
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@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
:
1 2 3 4 5 6 7 8 9 10 11 12 |
public class ErrorResponseException extends IOException { public ErrorResponseException() { super(); } public ErrorResponseException(String detailMessage) { super(detailMessage); } } |
定义 Service Method
1 2 3 4 5 6 7 8 9 10 11 |
public interface TradesService { @GET("api.tradecategories/1.0.0/get") Observable<Response<CategoryResponse>> tradeCategories(); @FormUrlEncoded @GET("api.trade/1.0.0/get") Observable<Response<TradeItemResponse>> tradeDetail(@Field("tid") String tid); } |
其中 CategoryResponse
、TradeItemResponse
全部继承自 BaseResponse
。
泛型 Response
由 Retrofit 提供,定义了三个成员变量:
1 2 3 4 5 6 |
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
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
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。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
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/"; private static final OkHttpClient sClient = 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(); 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) { Retrofit retrofit = new Retrofit.Builder() .client(sClient) .baseUrl(baseUrl) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .build(); return retrofit.create(serviceClazz); } } |
因为这两个网关都要求登录后才能访问,因此我们通过 OkHttpClient#addInterceptor
拦截 Request
之后加上了参数 access_token
。
线程模型
大多数情况下,我们都会在 io 线程发起 request,在主线程处理 response,所以我们定义了一个默认的线程模型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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
结合起来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public abstract class BaseSubscriber<T> extends Subscriber<T> { private Context mContext; public BaseSubscriber(Context context) { mContext = context; } public Context getContext() { return mContext; } } |
ToastSubscriber
以 Toast 形式展示错误消息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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 形式展示错误消息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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
,里面包含了错误信息的数据结构。
1 2 3 4 5 6 7 8 9 10 11 12 |
@Keep public class CategoryResponse extends BaseResponse { public Response response; @Keep public static final class Response { public List<Category> categories; } } |
其中 Category
是具体的实体类型。
2. 定义 Service Method
1 2 3 4 5 6 |
public interface TradesService { @GET("api.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
实例。
1 2 3 4 |
mTradesService = ServiceFactory.createNewService(TradesService.class) |
4. TradeService 获取数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
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>(getActivity) { @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 – 网络环境模拟
1 2 3 4 5 6 7 8 |
private void givenNetworkFailurePercentIs(int failurePercent) { mNetworkBehavior.setDelay(0, TimeUnit.MILLISECONDS); mNetworkBehavior.setVariancePercent(0); mNetworkBehavior.setFailurePercent(failurePercent); } |
TestSubscriber – 带断言的 Subscriber
1 2 3 4 5 6 |
private TestSubscriber<Response<CategoryResponse>> mTestSubscriberCategory = TestSubscriber.create() subscriber.assertError(RuntimeException.class); subscriber.assertNotCompleted(); |
MockRetrofit – 为 Retrofit 添加 Mock 数据(NetworkBehavior 等)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@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 数据
1 2 3 4 5 |
BehaviorDelegate<TradesService> delegate = mockRetrofit.create(TradesService.class); mTradesServiceMock = new TradesServiceMock(delegate); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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,四两拨千斤,尽情发挥你的想象力。