美文网首页RX+Retrofit
Retrofit2 + RxJava2 封装

Retrofit2 + RxJava2 封装

作者: 小猪x | 来源:发表于2017-11-08 11:59 被阅读110次

    使用前需要学习retrofit2 和rxjava等相关知识,总结一下比较好的文章

    retrofit

    你真的会用Retrofit2吗?Retrofit2完全教程
    Retrofit解析2之使用简介
    Retrofit 2.0 自定义Converter
    作者介绍Retrofit 2.0
    Retrofit源码分析(超详细)

    rxjava

    RxJava + Retrofit 的实际应用场景
    RxJava 1.x详解
    这可能是最好的RxJava 2.x 教程(完结版)

    专栏

    专栏-Android RxJava之网络处理
    专栏-Retrofit+RxJava+Okhttp +Rx 实践及源码大全
    专栏-Retrofit 2.0 超能实践系列

    错误异常处理

    Rxjava、Retrofit返回json数据解析异常处理
    Retrofit+RxJava 优雅的处理服务器返回异常、错误

    Gson

    你真的会用Gson吗
    搞定Gson泛型封装

    HTTP基础

    你应该知道的HTTP基础知识
    ps:怪盗kidou的系列文章都值的一看

    其他

    Android Studio支持Java8方法支持lambda
    rxjava的ObserveOn和SubscribeOn的一些结论
    RxJava线程变换之observeOn与subscribeOn


    一、依赖引入

    依赖引入的过程中,会遇到一些冲突问题 具体参考 RxAndroid2+RxLifecycle2+Retrofit2 依赖引入和冲突分析

    最后依赖

    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
    
        //add on 2017.10.23
        //retrofit
    //    compile "com.squareup.retrofit2:retrofit:2.3.0" //adapter-rxjava2和converter-gson都引入了retrofit2,这个可以去掉
        compile"com.squareup.retrofit2:adapter-rxjava2:2.3.0" //连接器 自带retrofit:2.3.0
        compile"com.squareup.retrofit2:converter-gson:2.3.0" //解析器 主要处理具体对象、jsonObject、jsonArray 包含okhttp、gson等 自带retrofit:2.3.0
        compile"com.squareup.retrofit2:converter-scalars:2.3.0" //解析器 处理String boolean 等基本类型 包含okhttp、gson等 自带retrofit:2.3.0
    
        //rxjava rxandroid
        compile "io.reactivex.rxjava2:rxjava:2.1.5"
        compile "io.reactivex.rxjava2:rxandroid:2.0.1" //本身也自带rxjava
    
        //rxlifecycle
    //    compile "com.trello.rxlifecycle2:rxlifecycle:2.1.0" //rxlifecycle-components已经引入了,这个可以去掉
        compile "com.trello.rxlifecycle2:rxlifecycle-components:2.1.0" //自带rxlifecycle:2.1.0
    
        //dagger2
        compile 'com.google.dagger:dagger:2.12'
        annotationProcessor 'com.google.dagger:dagger-compiler:2.12'
    }
    

    其中dagger如果不使用可以去掉

    二、java8的引入

    java引入也会有些问题
    具体参考Android Studio支持Java8方法支持lambda

    java8缺点:

    java8的编译速度很慢,正常java7跑几秒 java8要跑1分钟。。无奈暂时弃用。如果你机器快建议使用java8 因为lambda会使你的代码更加简洁

    三、框架设计

    参考很多网上的文章,很多框架都觉得很蛋疼、Retofit每添加一个接口就得在service文件写一个接口定义类

    参考了浅谈Retrofit封装-让框架更加简洁易用
    文章只用了retrofit,我们把rxjava 也加上

    按常用做法每个接口请求需要在接口上添加对应的方法
    修改前:

    public interface RetrofitHttpService {
        @GET("/xxx/xxxx")
        Observable<BaseResult<BaseModel>> login(@Query("name") String name);
    }
    

    修改后:

    public interface RetrofitHttpService {
        @GET()
        Observable<Response<ResponseBody>> get(@Url String url, @QueryMap Map<String, String> params, @HeaderMap Map<String, String> headers);
    }
    

    这样就可以统一一个方法。只返回最基本的类型,然后在调用这个方法在统一进行解析

    注意:其中Response是retrofit2的, ResponseBody是okhttp3,这点容易搞错

    参考:Retrofit源码分析

    简单看一下调用方法

    HttpConfig.getService().get(checkUrl(mUrl), interceptParams(mParams), interceptHeaders(mHeaders))
        .compose(RxUtils.<T>translate(typeOfT))
        .subscribe(new SampleProgressObserver<T>() { //subscribe订阅里-添加观察者Observer
            @Override
            public void onSubscribe(Disposable d) {
                Log.i("lch1", "onSubscribe" + " => " + Thread.currentThread().getName());
            }
    
            @Override
            public void onNext(T response) {
                Log.i("lch", "onNext:" + " => " + Thread.currentThread().getName());
                mCallback.onSuccess(response);
            }
    
            @Override
            public void onError(ApiException e) {
                Log.i("lch1", "onError" + " => " + Thread.currentThread().getName());
                Log.i("lch1", "onError----  " + e.code + ":" + e.msg);
                e.printStackTrace();
            }
    
            @Override
            public void onComplete() {
                Log.i("lch1", "onComplete" + " => " + Thread.currentThread().getName());
            }
        });
    

    其中使用RxUtils进行数据解析、错误处理、和io main线程切换

    public class RxUtils {
    
        /**
         * ObservableTransformer 作用于整个流,Func是一个操作符,作用于数据项
         * @param typeOfT
         * @param <T>
         * @return
         */
        public static <T> ObservableTransformer<Response<ResponseBody>, T> translate(final Type typeOfT) {
            return new ObservableTransformer<Response<ResponseBody>, T>() {
                @Override
                public ObservableSource apply(Observable upstream) {
                    return upstream
                            .map(new ServerResponseTranslateFunc<T>(typeOfT)) //进行数据转换
                            .onErrorResumeNext(new HttpResponseFunc()) //拦截服务器返回的错误
                            .subscribeOn(Schedulers.io())   // subscribeOn() 指定的就是发射事件的线程
                            .unsubscribeOn(Schedulers.io()) //取消订阅制定线程 // TODO: 2017/11/7 测试
                            .observeOn(AndroidSchedulers.mainThread());  //observerOn 指定的就是订阅者接收事件的线程。
                }
            };
        }
    
    
        /**
         * 对服务器返回的数据进行解析,返回结构是BaseResult<T>
         *
         * @param <T>
         */
        private static class ServerResponseTranslateFunc<T> implements Function<Response<ResponseBody>, T> {
            private Type mTypeOfT;
    
            public ServerResponseTranslateFunc(Type typeOfT) {
                mTypeOfT = typeOfT;
            }
    
            @Override
            public T apply(Response<ResponseBody> response) throws Exception {
                Log.i("lch1", "ServerResponseTranslateFunc" + " => " + Thread.currentThread().getName());
                if (response != null && response.isSuccessful()) {
                    BaseResult<T> result = HttpConfig.getTransformer().transformer(response, mTypeOfT); //调用解析器,外部可以设置,可以使用gson或者其他
                    if (result != null) {
                        //判断跟服务器定义的code是否一样
                        if (result.isSuccess()) {
                            return result.data;
                        } else {
                            throw new ApiException(result.code, result.msg); //返回自定义ApiException-服务器提示
                        }
                    } else {
                        throw new ApiException(HttpCode.ServerError.ERROR_TRANSFORM);  //返回自定义ApiException
                    }
                } else {
                    throw new HttpException(response); //返回HttpException
                }
            }
        }
    
        /**
         * 错误处理fun,
         * 对所有错误进行统一解析
         */
        private static class HttpResponseFunc implements Function<Throwable, Observable> {
    
            @Override
            public Observable apply(Throwable throwable) throws Exception {
                Log.i("lch1", "HttpResponseFunc" + " => " + Thread.currentThread().getName());
                return Observable.error(ExceptionHandler.handleException(throwable));
            }
        }
    }
    

    1.异常处理可以参考参考:Retrofit+RxJava 优雅的处理服务器返回异常、错误

    2.ObservableTransformer 作用于整个流,它跟fun很像,但Fun是一个操作符,作用于数据项
    参考:Retrofit+RxJava错误预处理

    3.其中线程切换参考:
    rxjava的ObserveOn和SubscribeOn的一些结论
    RxJava线程变换之observeOn与subscribeOn

    其中目前传递进来的是Type类型 这样就可以传Type 或者Class类型了 因为class继承Type接口

    HttpUtils.request(url, params).get(new TypeToken<BaseResult<BaseInfo>>(){}.getType())
    HttpUtils.request(url, params).get(TestResult.class )
    

    以上两种方式都可以。只是TestResult就需要多一个类去继承BaseResult,这样接口多的时候就会比较蛋疼

    如果Type想使用泛型,不想每次都new TypeToken<BaseResult<BaseInfo>>(){}.getType() 可以参考
    搞定Gson泛型封装

    不过如果是List<T> 那就又要在包装。感觉还是直接new TypeToken算了。同时还可以支持直接CLASS


    最后使用方法是

    String url = "http://demo.phalapi.net/?service=User.getBaseInfo&user_id=1";
    Map<String, String> params = new HashMap<>();
    params.put("key", "value");
        HttpUtils.request(url, params)
            .callback(new APICallback<BaseInfo>() {
                @Override
                public void onSuccess(BaseInfo data) {
                    Log.e("lch1", "name---------:" + data.info.name);
                }
    
                @Override
                public void onFailed(APIStatus status) {
                    Log.e("lch1", "getMessage---------:" + status.getMessage());
                }
            }).get(new TypeToken<BaseResult<BaseInfo>>(){}.getType());
        }
    

    四、为Retrofit添加重试机制

    参考文章:
    All RxJava - 为Retrofit添加重试
    RxJava与Retrofit的封装

    public class RetryWhenNetworkException implements Function<Observable<? extends Throwable>, Observable<?>> {
        /**
         * retry次数
         */
        private int mMaxRetryCount = 2;
    
        /**
         * 延迟
         */
        private long mDelay = 1000;
    
        /**
         * 叠加延迟
         */
        private long mIncreaseDelay = 1000;
    
    
        public RetryWhenNetworkException() {
    
        }
    
        public RetryWhenNetworkException(int maxRetryCount) {
            mMaxRetryCount = maxRetryCount;
        }
    
        public RetryWhenNetworkException(int maxRetryCount, long delay) {
            mMaxRetryCount = maxRetryCount;
            mDelay = delay;
        }
    
        public RetryWhenNetworkException(int maxRetryCount, long delay, long increaseDelay) {
            mMaxRetryCount = maxRetryCount;
            mDelay = delay;
            mIncreaseDelay = increaseDelay;
        }
    
    
        @Override
        public Observable<?> apply(Observable<? extends Throwable> observable) throws Exception {
            return observable
                    .zipWith(Observable.range(1, mMaxRetryCount + 1), new BiFunction<Throwable, Integer, ThrowableWrapper>() {
                        @Override
                        public ThrowableWrapper apply(Throwable throwable, Integer curRetryCount) {
                            return new ThrowableWrapper(throwable, curRetryCount);
                        }
                    }).flatMap(new Function<ThrowableWrapper, Observable<?>>() {
                        @Override
                        public Observable<?> apply(ThrowableWrapper wrapper) {
                            //遭遇了IOException等时才重试网络请求,IllegalStateException,NullPointerException或者当你使用gson来解析json时还可能出现的JsonParseException等非I/O异常均不在重试的范围内。
                            if ((wrapper.throwable instanceof ConnectException
                                    || wrapper.throwable instanceof SocketTimeoutException
                                    || wrapper.throwable instanceof TimeoutException
                                    || wrapper.throwable instanceof HttpException
                            )) {
                                //如果超出重试次数也抛出错误,否则默认是会进入onCompleted
                                if (wrapper.curRetryCount <= mMaxRetryCount) {
                                    Log.i("lch1", "网络错误,重试次数:" + wrapper.curRetryCount);
                                    long delayTime = mDelay + (wrapper.curRetryCount - 1) * mIncreaseDelay;    //使用二进制指数退避算法,每次都比上次长时间
                                    return Observable.timer(delayTime, TimeUnit.MILLISECONDS, Schedulers.trampoline());
                                }
                            }
                            return Observable.error(wrapper.throwable);
                        }
                    });
        }
    
        private class ThrowableWrapper {
            /**
             * 当前重试次数
             */
            private int curRetryCount;
    
            /**
             * 抛出的异常
             */
            private Throwable throwable;
    
            public ThrowableWrapper(Throwable throwable, int curRetryCount) {
                this.curRetryCount = curRetryCount;
                this.throwable = throwable;
            }
        }
    }
    

    最后调用

    public static <T> ObservableTransformer<Response<ResponseBody>, T> translate(final Type typeOfT) {
        return new ObservableTransformer<Response<ResponseBody>, T>() {
            @Override
            public ObservableSource apply(Observable upstream) {
                return upstream
                        .map(new ServerResponseTranslateFunc<T>(typeOfT)) //进行数据转换
                        .retryWhen(new RetryWhenNetworkException())  //失败后的retry配置, 放到onErrorResumeNext前面,不然onErrorResumeNext会重新定义Exception
                        .onErrorResumeNext(new HttpResponseFunc()) //拦截服务器返回的错误
                        .subscribeOn(Schedulers.io())   // subscribeOn() 指定的就是发射事件的线程
                        .unsubscribeOn(Schedulers.io()) //取消订阅制定线程 // TODO: 2017/11/7 测试
                        .observeOn(AndroidSchedulers.mainThread());  //observerOn 指定的就是订阅者接收事件的线程。
            }
        };
    }
    

    注意:

    1、RetryWhenNetworkException配置, 放到onErrorResumeNext前面,不然onErrorResumeNext会重新定义Exception
    2、如果RetryWhenNetworkException放到onErrorResumeNext后面也可以, return new ThrowableWrapper(throwable, curRetryCount);的throwable修改成throwable.getCause() 里面这里重新定义Exception,变成ApiException 所以需要getCause()才能获取真正的错误

    五、retrofit接口封装

    参考文章:
    你真的会用Retrofit2吗?Retrofit2完全教程
    Retrofit解析2之使用简介
    你应该知道的HTTP基础知识

    以下内容待优化

    /**
     * 1、Map用来组合复杂的参数,并且对于FieldMap,HeaderMap,PartMap,QueryMap这四种作用方法的注解,其参数类型必须为Map实例,且key的类型必须为String类型,否则抛出异常。
     *    QueryMap作用于Get方法,形成url后面的参数键值对如:name=张三&name=李四&name=王五。同时进行url编码,把中文和特殊字符进行编码
     * 2、Query、QueryMap与Field、FieldMap功能一样,生成的数据形式一样;Query、QueryMap的数据体现在Url上;Field、FieldMap的数据是请求体
     * 3、@FormUrlEncoded 注解和@Multipart 注解不能同时使用,否则会抛出methodError(“Only one encoding annotation is allowed.”),可在ServiceMethod类中parseMethodAnnotation()方法中找到不能同时使用的具体原因。
     * 4、使用@Body 注解的参数不能使用form 或multi-part编码,即如果为方法使用了FormUrlEncoded或Multipart注解,则方法的参数中不能使用@Body 注解,否则会抛出异常parameterError(p, “@Body parameters cannot be used with form or multi-part encoding.”)
     *  参考:http://www.jianshu.com/p/345304325511
     *  http://www.jianshu.com/p/308f3c54abdd
     */
    public interface RetrofitHttpService {
    
        /**
         * @return Response是retrofit2的, ResponseBody是okhttp3 参考http://www.jianshu.com/p/097947afddaf 这个可以做学习点
         */
        @GET()
        Observable<Response<ResponseBody>> get(@Url String url, @QueryMap Map<String, Object> params, @HeaderMap Map<String, String> headers);
    
    
        /**
         * 标准普通的post请求
         *
         * FormUrlEncoded:用于修饰Fiedl注解 和FileldMap注解
         * 使用该注解,表示请求正文将使用表单网址编码
         * 使用@FormUrlEncoded 注解的请求将具有"application/x-www-form-urlencoded" MIME类型。字段名称和值将先进行UTF-8进行编码,再根据RFC-3986进行URI编码。
         * Content-Type: application/x-www-form-urlencoded;charset=utf-8
         * @return
         */
        @FormUrlEncoded
        @POST()
        Observable<Response<ResponseBody>> post(@Url String url, @FieldMap Map<String, Object> params, @HeaderMap Map<String, String> headers);
    
    
        /**
         * post-json格式请求
         *
         * @Body 注解定义的参数不能为null
         * 当你发送一个post或put请求,但是又不想作为请求参数或表单的方式发送请求时,
         * 使用该注解定义的参数可以直接传入一个实体类,retrofit会通过convert把该实体序列化并将序列化的结果直接作为请求体发送出去。
         * @param url
         * @param body body 可以传所有对象 map object 都可以 retrofit会通过convert把实体序列化
         *             并自动指定Content-Type: application/json;charset=UTF-8,或者自己添加头部也行@Headers({"Content-Type: application/json","Accept: application/json"})//需要添加头
         *             还能传RequestBody 作为body,这个就比较麻烦 可以参考:http://www.jianshu.com/p/32bfd5fd8b48
         * @param headers
         * @return
         */
        @POST
        Observable<Response<ResponseBody>> postJson(@Url String url, @Body Object body, @HeaderMap Map<String, String> headers);
    
    
        /**
         * 增加QueryMap 对path增加参数
         */
        @POST
        Observable<Response<ResponseBody>> postJson(@Url String url, @Body Object body, @HeaderMap Map<String, String> headers, @QueryMap Map<String, String> pathParams);
    
    
        /**
         * 使用 Multipart : 标记一个请求是Content-Type:multipart/form-data类型,需要和 @retrofit2.http.POST 一同使用,并且方法参数必须是 @retrofit2.http.Part 注解
         * @return
         */
    
        @Multipart
        @POST()
        Observable<Response<ResponseBody>> postMultipart(@Url String url, @PartMap Map<String, RequestBody> params, @HeaderMap Map<String, String> headers);
    
    
        //--------------上传文件方法---------------------------------------------
        /**
         *
         *  http://www.jianshu.com/p/3f1cc5a7bf8c
         *  为什么可以这样写:
         *  1、 Retrofit会判断@Body的参数类型,如果参数类型为okhttp3.RequestBody,则Retrofit不做包装处理,直接丢给okhttp3处理。而MultipartBody是继承RequestBody,因此Retrofit不会自动包装这个对象。
         *  2、同理,Retrofit会判断@Part的参数类型,如果参数类型为okhttp3.MultipartBody.Part,则Retrofit会把RequestBody封装成MultipartBody,再把Part添加到MultipartBody。
         */
    
    
    
        /**
         * 通过 MultipartBody和@body作为参数来上传
         * 注意1:必须使用@POST,使用@Body注解参数,则不能使用@Multipart注解方法了
         * 直接将所有的MultipartBody.Part合并到一个MultipartBody中
         */
        @POST()
        Observable<Response<ResponseBody>> postMultipart(@Url String url, @Body MultipartBody body, @HeaderMap Map<String, String> headers);
    
    
    
        /**
         *
         * 通过 List<MultipartBody.Part> 传入多个part实现多文件上传
         *
         *
         * 使用 Multipart : 标记一个请求是Content-Type:multipart/form-data类型,需要和 @retrofit2.http.POST 一同使用,并且方法参数必须是 @retrofit2.http.Part 注解
         *
         * 注意1:必须使用@POST注解为post请求
         * 注意:使用@Multipart注解方法,必须使用@Part
         * -@PartMap注解其参数
         * 本接口中将文本数据和文件数据分为了两个参数,是为了方便将封装
         * MultipartBody.Part的代码抽取到工具类中
         * 也可以合并成一个 @Part参数
         *
         * @param params 用于封装文本数据
         * @param parts 用于封装文件数据
         * @return BaseResp为服务器返回的基本Json数据的Model类
         */
        @Multipart
        @POST()
        Observable<Response<ResponseBody>> postMultipart(@Url String url, @PartMap Map<String, RequestBody> params, @Part List<MultipartBody.Part> parts, @HeaderMap Map<String, String> headers);
    
    }
    

    相关文章

      网友评论

        本文标题:Retrofit2 + RxJava2 封装

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