分享

RxJava Retrofit框架Demo(一)

 昵称39514526 2017-01-22

从年前一两个月开始,就开始慢慢接触RxJava+Retrofit,针对以往开发中遇到的情况,慢慢写了一个框架Demo。文章不在进行入门介绍,需要了解的同学,可以查看笔者总结的文章RxJavaRetrofit

分割Response

一般来说,网络请求结果包括以下信息:
{ 'message': '操作成功', 'code': '1', 'object': {}}
我们可以定义一个对象Response,其中泛型T来表示object,可能是数组,也可能是对象。code为1(或者其他值,和后台商议)表示接口调用成功,如:登录成功,注册成功等;code为其他值,则表示失败,如登录失败等,此时message便返回对应的错误信息,如密码错误等。
如果返回结果为Response,则每次网络请求都要判断接口是否调用成功,比较麻烦,我们希望的是:如果接口调用成功,返回泛型T,即object;如果调用失败,则返回codemessage信息。因此,需要对返回结果进行分割处理。
分割操作代码如下:

/** * 对网络接口返回的Response进行分割操作 * * @param response * @param * @return */public Observable flatResponse(final Response response) { return Observable.create(new Observable.OnSubscribe() { @Override public void call(Subscriber subscriber) { if (response.isSuccess()) { if (!subscriber.isUnsubscribed()) { subscriber.onNext(response.object); } } else { if (!subscriber.isUnsubscribed()) { subscriber.onError(new APIException(response.code, response.message)); } return; } if (!subscriber.isUnsubscribed()) { subscriber.onCompleted(); } } });}

其中response.isSuccess()的代码如下:

public boolean isSuccess() { return code.equals(Constant.OK);}

通过以上代码,便可实现分割操作,这样每次返回结果都不用通过code来判断是否成功。

打印请求地址+参数

有些时候,为了方便调试,我们需要将网络请求的地址和参数log出来。由于Retrofit是基于OKHttp的,所以我们需要通过Interceptors来拦截OKHttp来log所需信息。
关于Interceptors,不再多说,直接附上代码。代码来自HttpLoggingInterceptor ,做了简化。

package com.sunflower.rxandroiddemo.utils;import java.io.IOException;import java.nio.charset.Charset;import java.util.concurrent.TimeUnit;import okhttp3.Headers;import okhttp3.Interceptor;import okhttp3.MediaType;import okhttp3.MultipartBody;import okhttp3.Protocol;import okhttp3.Request;import okhttp3.RequestBody;import okhttp3.Response;import okhttp3.internal.Platform;import okio.Buffer;/** * Created by Sunflower on 2016/1/12. */public class HttpLoggingInterceptor implements Interceptor { private static final Charset UTF8 = Charset.forName('UTF-8'); public enum Level { /** * No logs. */ NONE, /** * Logs request and response lines. *

* Example: *

{@code         * --> POST /greeting HTTP/1.1 (3-byte body)         * 

* <-- http/1.1="" 200="" ok="" (22ms,="" 6-byte="" body)="" *="">

*/ BASIC, /** * Logs request and response lines and their respective headers. *

* Example: *

{@code         * --> POST /greeting HTTP/1.1         * Host: example.com         * Content-Type: plain/text         * Content-Length: 3         * --> END POST         * 

* <-- http/1.1="" 200="" ok="" (22ms)="" *="" content-type:="" plain/text="" *="" content-length:="" 6="" *=""><-- end="" http="" *="">

*/ HEADERS, /** * Logs request and response lines and their respective headers and bodies (if present). *

* Example: *

{@code         * --> POST /greeting HTTP/1.1         * Host: example.com         * Content-Type: plain/text         * Content-Length: 3         * 

* Hi? * --> END GET *

* <-- http/1.1="" 200="" ok="" (22ms)="" *="" content-type:="" plain/text="" *="" content-length:="" 6="" *="">

* Hello! * <-- end="" http="" *="">

*/ BODY } public interface Logger { void log(String message); /** * A {@link Logger} defaults output appropriate for the current platform. */ Logger DEFAULT = new Logger() { @Override public void log(String message) { Platform.get().log(message); } }; } public HttpLoggingInterceptor() { this(Logger.DEFAULT); } public HttpLoggingInterceptor(Logger logger) { this.logger = logger; } private final Logger logger; private volatile Level level = Level.BODY; /** * Change the level at which this interceptor logs. */ public HttpLoggingInterceptor setLevel(Level level) { if (level == null) throw new NullPointerException('level == null. Use Level.NONE instead.'); this.level = level; return this; } @Override public Response intercept(Interceptor.Chain chain) throws IOException { Level level = this.level; Request request = chain.request(); if (level == Level.NONE) { return chain.proceed(request); } boolean logBody = level == Level.BODY; boolean logHeaders = logBody || level == Level.HEADERS; RequestBody requestBody = request.body(); boolean hasRequestBody = requestBody != null; String requestStartMessage = request.method() + ' ' + request.url(); if (!logHeaders && hasRequestBody) { requestStartMessage += ' (' + requestBody.contentLength() + '-byte body)'; } logger.log(requestStartMessage); if (logHeaders) { if (!logBody || !hasRequestBody) { logger.log('--> END ' + request.method()); } else if (bodyEncoded(request.headers())) { logger.log('--> END ' + request.method() + ' (encoded body omitted)'); } else if (request.body() instanceof MultipartBody) { //如果是MultipartBody,会log出一大推乱码的东东 } else { Buffer buffer = new Buffer(); requestBody.writeTo(buffer); Charset charset = UTF8; MediaType contentType = requestBody.contentType(); if (contentType != null) { contentType.charset(UTF8); } logger.log(buffer.readString(charset));// logger.log(request.method() + ' (' + requestBody.contentLength() + '-byte body)'); } } long startNs = System.nanoTime(); Response response = chain.proceed(request); long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs); logger.log(response.code() + ' ' + response.message() + ' (' + tookMs + 'ms' + ')'); return response; } private boolean bodyEncoded(Headers headers) { String contentEncoding = headers.get('Content-Encoding'); return contentEncoding != null && !contentEncoding.equalsIgnoreCase('identity'); } private static String protocol(Protocol protocol) { return protocol == Protocol.HTTP_1_0 ? 'HTTP/1.0' : 'HTTP/1.1'; }}

这样在初始化Retrofit时,我们可以通过以下代码来log请求地址+参数

HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() { @Override public void log(String message) { Log.i('RxJava', message); }});OkHttpClient client = new OkHttpClient.Builder() //log请求参数 .addInterceptor(interceptor) .build();

结果如下:


log网络请求地址+参数

ApiWrapper封装类

对于一个APP来说,我们需要建立一个或者多个接口(我们先分析一个接口的情况,下文用APIService来替代),里面是相应的网络请求,然而不可能每次请求都初始化一个Retrofit对象,进而获得APIService对象,传入对应参数,进行网络请求,处理返回结果。
所以,首先可以新建RetrofitUtil类,用于初始化操作,网络结果分割操作等等;然后新建ApiWrapper封装类(继承自RetrofitUtil)。新建ApiWrapper封装类有什么好处呢?用代码来说明吧!
比说在APIService中有这样一个网络请求方法:

@FormUrlEncoded@POST('api/common/msg.json')Observable<>> getSmsCode(@Field('mobile') String mobile, @Field('appType') String appType);

该方法是用来获取短信验证码的,需要传入两个参数:手机号、app类型(医生端or孕妇端)
由于返回结果为验证码,即object字段为String类型,所以返回结果是Response
通过ApiWrapper封装后,代码如下:

public Observable getSmsCode(String mobile) { return getService().getSmsCode(mobile, 'GRAVIDA') .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .flatMap(new Func1<>, Observable>() { @Override public Observable call(Response stringResponse) { return flatResponse(stringResponse); } });}

其中getService()为父类RetrofitUtil中获取APIService对象的方法。
这样的话,在对应Activity中调用起来就很方便了

ApiWrapper wrapper = new ApiWrapper();wrapper.getSmsCode(mobile) .subscribe(new Subscriber() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(String s) { Log.i(TAG, 'call ' + s); } });

通过以上代码,我们可以发现封装类有以下好处:

  • 传递某些固定参数,如上述代码中的String appType,或者userId等
  • 对网络请求返回结果进行分割操作
  • 可以进行线程控制

进一步封装

就只能这样了么?

public Observable getSmsCode(String mobile) { return getService().getSmsCode(mobile, 'GRAVIDA') .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .flatMap(new Func1<>, Observable>() { @Override public Observable call(Response stringResponse) { return flatResponse(stringResponse); } });}

从这个方法中,我们可以清楚地看到数据是如何在一系列操作符之间进行转换的。但是以后每个网络请求都将进行这样的重复操作。
如何将一组操作符重用于多个数据流中呢?例如,因为希望在工作线程中处理数据,在主线程中处理结果,然后分割网络请求结果。所以我会频繁使用subscribeOn()observeOn()flatMap()。如果我能够通过重用的方式,将这种逻辑运用到我所有的数据流中,将是一件多么棒的事。
RxJava提供了一种解决方案:Transformer(有转换器意思),一般情况下可以通过使用操作符Observable.compose()来实现。
Transformer实际上就是一个Func1<>, Observable>,换言之就是:可以通过它将一种类型的Observable转换成另一种类型的Observable,和调用一系列的内联操作符是一模一样的。

/** * http://www.jianshu.com/p/e9e03194199e *

* * @param * @return */protected Observable.Transformer<>, T> applySchedulers() { return new Observable.Transformer<>, T>() { @Override public Observable call(Observable<>> responseObservable) { return responseObservable.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .flatMap(new Func1<>, Observable>() { @Override public Observable call(Response tResponse) { return flatResponse(tResponse); } }) ; } };}

恩,没错,这一部分内容参考了注释内的链接,大家可以去看下这篇帖子。
通过上面的方法,我们将Observable<>>转化成了Observable,并把处理了线程调度、分割返回结果等操作组合了起来,达到了复用的目的。
现在APIServicegetSmsCode()可简化为:

public Observable getSmsCode(String mobile) { return getService().getSmsCode(mobile, 'GRAVIDA') .compose(this.applySchedulers());}

由于要经常调用applySchedulers()方法,可以考虑创造一个实例化Transformer,节省不必要的实例化对象。代码如下:

final Observable.Transformer transformer = new Observable.Transformer() { @Override public Object call(Object observable) { return ((Observable) observable).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .flatMap(new Func1() { @Override public Object call(Object response) { return flatResponse((Response)response); } }) ; }};protected Observable.Transformer<>, T> applySchedulers() { return (Observable.Transformer<>, T>) transformer;}

Note

.flatMap(new Func1() { @Override public Object call(Object response) { return flatResponse((Response)response); }})

flatResponse()进行类型强转的话,应该没问题吧?笔者暂时不确定,但目前也没发现什么问题,,,

封装Subscriber

在Activity中我们调用getSmsCode()代码如下:

ApiWrapper wrapper = new ApiWrapper();showLoadingDialog();wrapper.getSmsCode(mobile) .subscribe(new Subscriber() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(String s) { Log.i(TAG, 'call ' + s); } });

其实,对于大部分请求来说,我们只需处理onNext() 方法,默认在onCompleted()方法中hideLoadingDialog();在onError()方法中Toast对应的错误信息。
所以我们可以进一步封装Subscriber,代码如下:

/** * 创建观察者 * * @param onNext * @param * @return */protected Subscriber newSubscriber(final Action1 onNext) { return new Subscriber() { @Override public void onCompleted() { hideLoadingDialog(); } @Override public void onError(Throwable e) { if (e instanceof RetrofitUtil.APIException) { RetrofitUtil.APIException exception = (RetrofitUtil.APIException) e; showToast(exception.message); } else if (e instanceof SocketTimeoutException) { showToast(e.getMessage()); } else if (e instanceof ConnectException) { showToast(e.getMessage()); } Log.e(TAG, String.valueOf(e.getMessage())); //e.printStackTrace(); hideLoadingDialog(); } @Override public void onNext(T t) { if (!mCompositeSubscription.isUnsubscribed()) { onNext.call(t); } } };}

onError()方法中,可以根据Throwable e的类型进行对应处理,其中APIException是我们自定义的异常,SocketTimeoutExceptionConnectException则是OKHttp返回的异常。
onCompleted()onError()中,我们都需要hideLoadingDialog()

subscribe()之后, Observable会持有 Subscriber的引用,这个引用如果不能及时被释放,将有内存泄露的风险。所以最好保持一个原则:要在不再使用的时候尽快在合适的地方(例如 onPause()onStop()等方法中)调用 unsubscribe()来解除引用关系,以避免内存泄露的发生。

我们可以在BaseActivity中声明一个对象

/** * 使用CompositeSubscription来持有所有的Subscriptions */protected CompositeSubscription mCompositeSubscription;

onCreate()方法中初始化:

mCompositeSubscription = new CompositeSubscription();

onDestroy()unsubscribe()接触引用关系:

@Overrideprotected void onDestroy() { super.onDestroy(); //一旦调用了 CompositeSubscription.unsubscribe(),这个CompositeSubscription对象就不可用了, // 如果还想使用CompositeSubscription,就必须在创建一个新的对象了。 mCompositeSubscription.unsubscribe();}

在Activity中调用网络请求时:

Subscription subscription = wrapper.getSmsCode2('15813351726') .subscribe(newSubscriber(new Action1() { @Override public void call(String s) { Log.i(TAG, 'call ' + s); } }));mCompositeSubscription.add(subscription);

所以在newSubscriber()中的onNext()方法中,我们需要事先判断mCompositeSubscription是否已经解除了引用。

---20160229更新---
代码地址在RxAndroidDemo
请看下篇RxJava+Retrofit框架Demo(二)

著作权归作者所有

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多