美文网首页Android干货Android学习Rxjava
RxJava+Retrofit框架Demo(一)

RxJava+Retrofit框架Demo(一)

作者: 小鹿儿 | 来源:发表于2016-02-20 23:12 被阅读29808次

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

    分割Response

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

    /**
     * 对网络接口返回的Response进行分割操作
     *
     * @param response
     * @param <T>
     * @return
     */
    public <T> Observable<T> flatResponse(final Response<T> response) {
        return Observable.create(new Observable.OnSubscribe<T>() {
    
            @Override
            public void call(Subscriber<? super T> 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.
             * <p/>
             * Example:
             * <pre>{@code
             * --> POST /greeting HTTP/1.1 (3-byte body)
             * <p/>
             * <-- HTTP/1.1 200 OK (22ms, 6-byte body)
             * }</pre>
             */
            BASIC,
            /**
             * Logs request and response lines and their respective headers.
             * <p/>
             * Example:
             * <pre>{@code
             * --> POST /greeting HTTP/1.1
             * Host: example.com
             * Content-Type: plain/text
             * Content-Length: 3
             * --> END POST
             * <p/>
             * <-- HTTP/1.1 200 OK (22ms)
             * Content-Type: plain/text
             * Content-Length: 6
             * <-- END HTTP
             * }</pre>
             */
            HEADERS,
            /**
             * Logs request and response lines and their respective headers and bodies (if present).
             * <p/>
             * Example:
             * <pre>{@code
             * --> POST /greeting HTTP/1.1
             * Host: example.com
             * Content-Type: plain/text
             * Content-Length: 3
             * <p/>
             * Hi?
             * --> END GET
             * <p/>
             * <-- HTTP/1.1 200 OK (22ms)
             * Content-Type: plain/text
             * Content-Length: 6
             * <p/>
             * Hello!
             * <-- END HTTP
             * }</pre>
             */
            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网络请求地址+参数log网络请求地址+参数

    ApiWrapper封装类

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

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

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

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

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

    ApiWrapper wrapper = new ApiWrapper();
    wrapper.getSmsCode(mobile)
            .subscribe(new Subscriber<String>() {
                @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<String> getSmsCode(String mobile) {
        return getService().getSmsCode(mobile, "GRAVIDA")
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .flatMap(new Func1<Response<String>, Observable<String>>() {
                    @Override
                    public Observable<String> call(Response<String> stringResponse) {
                        return flatResponse(stringResponse);
                    }
                });
    }
    

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

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

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

    public Observable<String> getSmsCode(String mobile) {
        return getService().getSmsCode(mobile, "GRAVIDA")
                .compose(this.<String>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<Object>)response);
                        }
                    })
                    ;
        }
    };
    protected <T> Observable.Transformer<Response<T>, T> applySchedulers() {
        return (Observable.Transformer<Response<T>, T>) transformer;
    }
    

    Note

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

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

    封装Subscriber

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

    ApiWrapper wrapper = new ApiWrapper();
    showLoadingDialog();
    wrapper.getSmsCode(mobile)
            .subscribe(new Subscriber<String>() {
                @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 <T>
     * @return
     */
    protected <T> Subscriber newSubscriber(final Action1<? super T> onNext) {
        return new Subscriber<T>() {
            @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()接触引用关系:

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

    在Activity中调用网络请求时:

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

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

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

    相关文章

      网友评论

      • 大叔不秃:感谢楼主的分享 入门学习挺不错
      • 2d51cabea192:如何在组件间通讯
      • 曾经的你呀:在开始学习retrofit+rxjava2.
      • MonkiRayman:问问LZ,我在FlatResponse里面,Response不为空,但是在call里面的Observable愣是不传递,怎么搞?
      • MonkiRayman:Mark,今晚回去看看
      • 默默_大魔王:demo跑不起来啊,
      • summer_lz:这篇博客借鉴了很多代码,而且技术点也不详细,基本上就是自己开发多方借鉴下,再贴出来
      • 谷歌清洁工:upupupupupupup
      • aositeluoke:打印请求参数的时候,参数值为中文时,有乱码
      • jzhu085:rxjava+retrofit2 怎么取消请求呢? 之前单独用retrofit 可以通过call.cancel()
        devlzh:调用Subscription的unsubscribe方法
      • 风中旅途:楼主你好,subscriber.onNext(response.object);这个传入的response.object是null。其他返回的都正常,求解?
        风中旅途:@leon0411 :smile: 是的,是的,谢谢这位仁兄!
        c2b55743ab45:@yangfang 可能的情况之一是object和指定的Javabean没有对应……
      • 飞飞舞舞:有一个问题,这么封装,如果请求成功是没有问题的,如果遇到问题,在哪里处理呢?实验一下根本不走newSubscriber这个方法里面去. 一直想做底层的通用处理,一直想不到好办法,刚学这个不久..
        小鹿儿:@飞飞舞舞 subscribe()方法有好几种重载,感觉参数的不同,分别可以处理onNext() onError() onComplete()。而newSubscriber()方法只是个封装,处理了onNext()。我在文中有写“其实,对于大部分请求来说,我们只需处理onNext() 方法”
      • supersugar:楼主写的真好啊,学习了很多东西!
        已经过去了几个月了,不知道楼主现在有没有做进一步的优化啊
      • Leo_Dongang:好!!!
      • Leo_Dongang:力挺这个,如果有问题不会还请指教
      • 6e898915b098:这样写的话也是说Response解析了最外层json 但是 我有些值是在最外层的 这样返的object 就没有意义了 这个和接口返回的json格式有关系的
      • vudiy:按照你的写出来的,回调成功以后,object那个泛型是空对象,坑死了,求解决呀
        616的小鼠:@小鹿儿 楼主,我也是遇到这个问题,就是Response中不是有个泛型T么,请求某个具体接口解析的时候这个泛型T指代的类型是一个list集合,但是打印出来的这个T对象一直是null,实际后台返回的是有数据的,一直不是很懂这个解析转换过程,泛型用的也不熟练,不知道是不是哪儿漏掉了,请楼主指点一下啊:sweat_smile:
        汤圆的馒头:@vudiy 2种方法- -1.叫你们后台不要返回空的 2.自定义gson解析的工厂类
        小鹿儿:@vudiy hi,哥们,你这问的没头没尾的....目测是你的JSON数据和JavaBean不对应
      • Jocker_h:这应该是我这几天搜到的最好的文章之一了!感谢
      • 09a927aca3d8:我想请问一下,okhttp client的拦截器的Log能实现打印出我请求接口返回的json么?经常在项目中遇到这样的问题,就是不知道服务器传回来的json数据是怎么样的,或者说服务器返回的json数据出现了异常,但是使用Retrofit返回拿到的都是对象,无法观察服务器返回json哪里出现了异常,不知楼主能否解答一下。。。。谢谢
        小鹿儿:@暖气片儿 可以啊 你可以单独把项目中HttpLoggingInterceptor 拷贝出来,添加到自己的代码中看下!
      • KanaliNa:不错,
      • 7bad9929ae77:封装的真好,有完整demo吗请问?。
        小鹿儿:@yupeng 已添加github项目地址 请多多指教
        小鹿儿:@yupeng 在Github上有demo,不过待完善,之后会提供github项目连接的 thx

      本文标题:RxJava+Retrofit框架Demo(一)

      本文链接:https://www.haomeiwen.com/subject/fhkvkttx.html