Retrofit + RxAndroid的使用

在接入 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>> 类型。
  1. 利用 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 修改为单例。

转载:http://blog.csdn.net/feelang/article/details/51840022

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Retrofit 可以与响应式编程框架 RxJava 结合使用,这样可以更加方便地进行异步操作和数据流处理。使用 RetrofitRxJava 结合的步骤如下: 1. 添加依赖 在 build.gradle 文件中添加以下依赖: ```groovy implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0' implementation 'io.reactivex.rxjava2:rxjava:2.2.21' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' ``` 2. 创建 Retrofit 实例 同样需要创建 Retrofit 实例,示例代码如下: ```java Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.example.com/") .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create()) .build(); ``` 需要注意的是,这里需要添加 RxJava2CallAdapterFactory 适配器。 3. 创建 API 接口 同样需要创建 API 接口,示例代码如下: ```java public interface ApiService { @GET("users") Observable<List<User>> getUsers(); } ``` 需要注意的是,这里返回的是一个 Observable 对象,而不是 Call 对象。 4. 调用 API 接口 使用 API 接口的实现调用 API 接口方法,示例代码如下: ```java ApiService service = retrofit.create(ApiService.class); service.getUsers() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<List<User>>() { @Override public void onSubscribe(Disposable d) { // TODO: 订阅开始 } @Override public void onNext(List<User> users) { // TODO: 处理返回结果 } @Override public void onError(Throwable e) { // TODO: 处理请求失败 } @Override public void onComplete() { // TODO: 订阅结束 } }); ``` 这里使用了 subscribeOn 和 observeOn 操作符,使得网络请求在后台线程中执行,而结果回调在主线程中执行。 通过这种方式,我们可以使用 RetrofitRxJava 结合进行网络请求,将返回结果转换成数据流进行处理,实现更加方便和灵活的异步编程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值