Retrofit + RxAndroid 实践总结

在接入 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);
}

其中 CategoryResponseTradeItemResponse 全部继承自 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,实际上业务方想要的是 Observable,那么我们就定义一个 Transformer 来转换这两个 Observable

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

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;}@Overridepublic Observable<R> call(Observable<T> observable) {return observable.map(new Func1<T, R>() {@Overridepublic 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() {@Overridepublic 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> {@Overridepublic Observable<T> call(Observable<T> observable) {return observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());}public static <T> SchedulerTransformer<T> create() {return new SchedulerTransformer<>();}
}

为了方便使用,我们又定义了一个 DefaultTransformerSchedulerTransformerErrorCheckerTransformer 结合起来。

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;}@Overridepublic 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@Overridepublic 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@Overridepublic 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;@Keeppublic 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> 类型。

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

在适当的时机(Activity#onCreateFragment#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>>() {@Overridepublic List<Category> call(CategoryResponse response) {return response.response.categories;}}).flatMap(new Func1<List<Category>, Observable<Category>>() {@Overridepublic Observable<Category> call(List<Category> categories) {return Observable.from(categories);}}).subscribe(new ToastSubscriber<Category>() {@Overridepublic void onCompleted() {hideProgressBar();// business related code}@Overridepublic void onError(Throwable e) {super.onError(e);hideProgressBar();// business related code}@Overridepublic 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;}@Overridepublic Observable<Response<CategoryResponse>> tradeCategories() {return mDelegate.returningResponse("{\"error_response\": \"my god\"}").tradeCategories();}
}

总结

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

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


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部