RxJava + Retrofit2 + OkHttp3 封装及

作者: 小枫 | 来源:发表于2017-01-20 00:19 被阅读4341次

    网上对这三个开源库的组合框架代码已经一搜一大把了,但是都很零碎,需要搜索很多东拼西凑才能写一套完整的复合自己需求的框架。这篇文章就是我自己在封装过程遇到的各种问题的记录和总结,免得很多人重复踩坑。。

    一、封装后的效果

        private void requestTopMovies(int page) {
            showWaitingDialog();
    
            Subscriber subscriber = new RxCallback<List<Movie>>() {
                @Override
                public void onSuccess(List<Movie> movieList) {
                    if (adapter != null) {
                        adapter.updateData(movieList);
                    }
                }
    
                @Override
                public void onFinished() {
                    dismissWaitingDialog();
                }
            };
            
            RxRetrofitClient.getInstance().requestTop250Movies(subscriber, page, 10);
        }
    

    二、封装过程

    1. 全局Client管理类封装 RxRetrofitClient(单例)

    在这里初始化各种网络设置

        /// RxRetrofitClient.java
    
        private void initClient() {
            // 创建OkHttpClient
            OkHttpClient.Builder builder = new OkHttpClient.Builder()
                    // 超时设置
                    .connectTimeout(DEFAULT_CONNECT_TIMEOUT, TimeUnit.SECONDS)
                    .readTimeout(DEFAULT_READ_TIMEOUT, TimeUnit.SECONDS)
                    .writeTimeout(DEFAULT_WRITE_TIMEOUT, TimeUnit.SECONDS)
                    // 错误重连
                    .retryOnConnectionFailure(true)
                    // 支持HTTPS
                    .connectionSpecs(Arrays.asList(ConnectionSpec.CLEARTEXT, ConnectionSpec.MODERN_TLS)) //明文Http与比较新的Https
                    // cookie管理
                    .cookieJar(new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(App.getInstance())));
    
            // 添加各种插入器
            addInterceptor(builder);
    
            // 创建Retrofit实例
            Retrofit doubanRetrofit = new Retrofit.Builder()
                    .client(builder.build())
                    .addConverterFactory(GsonConverterFactory.create())
                    // .addConverterFactory(FastJsonConvertFactory.create())
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                    .baseUrl(BASE_URL_DOUBAN)
                    .build();
    
            // 创建API接口类
            doubanApi = doubanRetrofit.create(IDoubanApi.class);
        }
    
        private void addInterceptor(OkHttpClient.Builder builder) {
            // 添加Header
            builder.addInterceptor(new HttpHeaderInterceptor());
    
            // 添加缓存控制策略
            File cacheDir = App.getInstance().getExternalCacheDir();
            Cache cache = new Cache(cacheDir, DEFAULT_CACHE_SIZE);
            builder.cache(cache).addInterceptor(new HttpCacheInterceptor());
    
            // 添加http log
            HttpLoggingInterceptor logger = new HttpLoggingInterceptor();
            logger.setLevel(HttpLoggingInterceptor.Level.BODY);
            builder.addInterceptor(logger);
    
            // 添加调试工具
            builder.networkInterceptors().add(new StethoInterceptor());
        }
    
    2. Get和Post请求封装
    /**
     * 豆瓣 Retrofit API
     *
     * Created by XiaoFeng on 16/12/20.
     */
    
    public interface IDoubanApi {
    
        @GET("top250")
        Observable<DoubanResult<List<Movie>>> getTopMovies(@Query("start") int start, @Query("count") int count);
    
        /**
         * Json格式的Post请求(application/json)
         */
        @POST("account/update_user_info")
        Observable<RESTResult<String>> updateUserInfo(@Body RequestBody body);
    
        /**
         * Form格式的Post请求(application/x-www-form-urlencoded)
         */
        @FormUrlEncoded
        @POST("account/update_user_info")
        Observable<RESTResult<String>> updateUserInfo(@FieldMap Map<String, String> params);
    
    }
    
        /// RxRetrofitClient.java
        
        /**
         * 获取豆瓣电影Top250的列表数据
         *
         * @param subscriber 由调用者传过来的观察者对象
         * @param page       页码
         * @param count      每页个数
         */
        public void requestTop250Movies(Subscriber<List<Movie>> subscriber, int page, int count) {
            doubanApi.getTopMovies(page * count, count)
                    .map(RxUtil.<List<Movie>>handleDoubanResult())
                    .compose(RxUtil.<List<Movie>>normalSchedulers())
                    .subscribe(subscriber);
        }
    
        /**
         * 修改用户信息
         * Json格式
         *
         * @param subscriber
         * @param params
         */
        public void updateUserInfo(Subscriber<String> subscriber, Map<String, Object> params) {
            xzApi.updateUserInfo(toRequestBody(params))
                    .map(RxUtil.<String>handleRESTFulResult())
                    .compose(RxUtil.<String>normalSchedulers())
                    .subscribe(subscriber);
        }
    
        /**
         * 修改用户信息
         * Form格式
         *
         * @param subscriber
         * @param params
         */
        public void updateUserInfo(Subscriber<String> subscriber, Map<String, Object> params) {
            xzApi.updateUserInfo(params)
                    .map(RxUtil.<String>handleRESTFulResult())
                    .compose(RxUtil.<String>normalSchedulers())
                    .subscribe(subscriber);
        }
    

    这里有几个

    1. 写Form格式的Post请求时,需要添加 @FormUrlEncoded 注解,否则编译器会报错
    2. 写Json格式的Post请求时,不使用@FieldMap注解,而是使用 @Body 注解,并声明 RequestBody 类型变量
    3. RequestBody封装
        private RequestBody toRequestBody(Map params) {
            return RequestBody.create(JSON, toJsonStr(params));
        }
    
        private String toJsonStr(Map params) {
            return new JSONObject(params).toString();
        }
    
    4. 对请求结果统一封装 RESTResult
    /**
     * RESTFul 返回值封装类
     *
     * Created by XiaoFeng on 16/12/21.
     */
    
    public class RESTResult<T> {
    
        public static final int FAILURE = 0;
        public static final int SUCCESS = 1;
    
        @SerializedName("res")
        private int res;
    
        @SerializedName("msg")
        private String msg;
    
        @SerializedName("data")
        T data;
    
        public int getRes() {
            return res;
        }
    
        public void setRes(int res) {
            this.res = res;
        }
    
        public String getMsg() {
            return msg;
        }
    
        public void setMsg(String msg) {
            this.msg = msg;
        }
    
        public T getData() {
            return data;
        }
    
        public void setData(T data) {
            this.data = data;
        }
    
    }
    
    5. 对请求结果进行转换和预处理(map)
        /// RxUtil.java
    
        /**
         * 对RESTful返回结果做预处理,对逻辑错误抛出异常
         *
         * @param <T>
         * @return
         */
        public static <T> Func1<RESTResult<T>, T> handleRESTFulResult() {
            return new Func1<RESTResult<T>, T>() {
                @Override
                public T call(RESTResult<T> restResult) {
                    if (restResult.getRes() != RESTResult.SUCCESS) {
                        throw new ApiException(restResult.getRes(), restResult.getMsg());
                    }
                    return restResult.getData();
                }
            };
        }
    
    6. rxjava线程切换封装(compose)
        /// RxUtil.java
    
        /**
         * 普通线程切换: IO -> Main
         *
         * @param <T>
         * @return
         */
        public static <T> Observable.Transformer<T, T> normalSchedulers() {
            return new Observable.Transformer<T, T>() {
                @Override
                public Observable<T> call(Observable<T> source) {
                    return source.subscribeOn(Schedulers.io())
                            .observeOn(AndroidSchedulers.mainThread());
                }
            };
        }
    

    这里有一个
    我把handleRESTFulResult和normalSchedulers封装到了单独的帮助类RxUtil中,这样在使用map或者compose做转换时,需要显式写明返回类型,不然编译器会报错,这点在很多网上查找的资料中都没有提及

    .map(RxUtil.<String>handleRESTFulResult())
    .compose(RxUtil.<String>normalSchedulers())

    7. 对服务器返回的逻辑错误进行统一拦截和封装,抛出异常
    /**
     * 对服务器返回的逻辑错误值进行封装
     *
     * Created by XiaoFeng on 16/12/21.
     */
    
    public class ApiException extends RuntimeException {
    
        private static final int USER_NOT_EXIST = 100;
        private static final int WRONG_PASSWORD = 101;
    
        private int errorCode;
    
        public ApiException(String detailMessage) {
            super(detailMessage);
        }
    
        public ApiException(int resultCode) {
            this(resultCode, toApiExceptionMessage(resultCode));
        }
    
        public ApiException(int resultCode, String detailMessage) {
            super(detailMessage);
            this.errorCode = resultCode;
        }
    
        public int getErrorCode() {
            return errorCode;
        }
    
        /**
         * 映射服务器返回的自定义错误码,
         * (此时的http状态码在[200, 300) 之间)
         *
         * @param resultCode
         * @return
         */
        private static String toApiExceptionMessage(int resultCode) {
            String message;
            switch (resultCode) {
                case USER_NOT_EXIST:
                    message = "该用户不存在";
                    break;
                case WRONG_PASSWORD:
                    message = "密码错误";
                    break;
                default:
                    message = "未知错误";
            }
            return message;
        }
    
    }
    
    8. 对Subscriber的封装,同时对onError异常再次封装
    /**
     * 暴露给最上层的网络请求回调处理类
     *
     * Created by XiaoFeng on 16/12/28.
     */
    public abstract class RxCallback<T> extends Subscriber<T> {
    
        /**
         * 成功返回结果时被调用
         *
         * @param t
         */
        public abstract void onSuccess(T t);
    
        /**
         * 成功或失败到最后都会调用
         */
        public abstract void onFinished();
    
        @Override
        public void onCompleted() {
            onFinished();
        }
    
        @Override
        public void onError(Throwable e) {
            String errorMsg;
            if (e instanceof IOException) {
                /** 没有网络 */
                errorMsg = "Please check your network status";
            } else if (e instanceof HttpException) {
                /** 网络异常,http 请求失败,即 http 状态码不在 [200, 300) 之间, such as: "server internal error". */
                errorMsg = ((HttpException) e).response().message();
            } else if (e instanceof ApiException) {
                /** 网络正常,http 请求成功,服务器返回逻辑错误 */
                errorMsg = e.getMessage();
            } else {
                /** 其他未知错误 */
                errorMsg = !TextUtils.isEmpty(e.getMessage()) ? e.getMessage() : "unknown error";
            }
    
            Toast.makeText(App.getInstance(), errorMsg, Toast.LENGTH_SHORT).show();
    
            onFinished();
        }
    
        @Override
        public void onNext(T t) {
            onSuccess(t);
        }
    }
    

    参考:
    https://gank.io/post/56e80c2c677659311bed9841
    http://tech.glowing.com/cn/glow-android-performance-optimization/
    http://www.jianshu.com/p/f3f0eccbcd6f
    http://wuxiaolong.me/2016/06/18/retrofits/
    http://stackoverflow.com/questions/35243785/rxjava-static-generic-utility-method-with-transformer

    相关文章

      网友评论

      • _惊蛰:认真看完了,lz写的很好,学习了学习了
      • allenZP:题主写的很好,收藏啦
      • 0c37836b4524:写了这么多,不开源也不是很多人看的懂啊!你给个GitHub链接吧!谢谢,学习学习嘛
      • 戴定康:感觉,不是很好。但还是支持你
      • 戴定康:待我看完再评论,阅读ing
      • wo叫天然呆:// 支持HTTPS
        .connectionSpecs(Arrays.asList(ConnectionSpec.CLEARTEXT, ConnectionSpec.MODERN_TLS)) //明文Http与比较新的Https
        // cookie管理
        .cookieJar(new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(App.getInstance())));
        // 添加调试工具
        builder.networkInterceptors().add(new StethoInterceptor());

        貌似可以不用谢支持HTTPS,它本身就支持了
        cookie管理和添加调试工具能说得具体点么,不是很明白,最好是能附上代码:blush:
        小枫:@wo叫天然呆 两种拦截器简单来说就是调用时机的区别,应用拦截器调用时机较早,也就是进入chain.proceed的递归较早,相应的完成递归得到response会较晚;而网络拦截器则相反,request请求调用时机较晚,会较早完成chain.proceed递归调用,得到response的时机较早。
        简单来说就是应用拦截器较上层,而网络拦截器较底层,所有拦截器就是一个由浅入深的递归调用。具体还是看源码吧。
        wo叫天然呆:@小枫 好的,关于拦截器的问题,addNetworkInterceptor与addInterceptor我不知道要如何使用比较好,只知道
        addNetworkInterceptor添加的是网络拦截器Network Interfacetor它会在request和response时分别被调用一次
        addInterceptor添加的是应用拦截器Application Interceptor他只会在response被调用一次
        但是具体什么样的拦截器要使用addInterceptor,什么样的又要使用addNetworkInterceptor不是很清楚
        小枫: @wo叫天然呆 嗯,最近会写一篇补充文章,把这一篇没讲到的再具体写一下😜
      • Android之路:题主,还有生命周期喔😀
        小新哥的大梦想:@小枫 博主有Demo吗,看了你的分享非常不错。
        小枫: @Android之路 嗯嗯,之后补上

      本文标题:RxJava + Retrofit2 + OkHttp3 封装及

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