Retrofit2 + RxJava2 封装

使用前需要学习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);

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容