一步步封装Retrofit + RxJava2

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuehuayous/article/details/78058170
前言:现在网络访问已经基本都是Retrofit + RxJava了,只不过有一些还是使用的RxJava1,比如我们目前的项目。为毛不升级为RxJava2,项目还是比较庞大的,改起来还是要费时费力,还要QA全覆盖区测试等等,所以暂时没有升级的想法。扯远了,我们这里主要来封装Retrofit + RxJava2,至于JSON解析是用Gson、FastJson是Jeckson还是LoganSquare等,这个就仁者见仁智者见智了。其实也很简单,只是一行代码的配置。

一、基本使用


1. 添加依赖

添加如下依赖,建议使用前去github看下,使用最新版本。
dependencies {
    // ... ...
    compile 'io.reactivex.rxjava2:rxjava:2.1.2'
    compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
    compile 'com.squareup.retrofit2:retrofit:2.3.0'
    compile 'com.squareup.okhttp3:logging-interceptor:3.8.1'
    compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
    compile 'com.google.code.gson:gson:2.8.2'
    compile 'com.squareup.retrofit2:converter-gson:2.3.0'
}

2. 编写实体类


这里以 http://123.57.31.11/androidnet/getArticleList接口为例,数据格式为:
 
  
 
  
{
    "status": "OK",
    "msg": "获取成功!",
    "data": {
        "list": [
            {
                "id": 0,
                "name": "十月秋花,人生几度",
                "author": "心怡",
                "category": "经典美文",
                "time": "2016-12-04 14:47:05",
                "point": 4953,
                "summary": "一梦红尘烟雨,窗外流云几许。",
                "content": "一梦红尘烟雨,窗外流云几许。云烟深处,水雾茫茫。阴无情,再好的花开也敌不过季节的流转。"
            }
        ]
    }
}
将其抽象为ArticleResult:
public class ArticleResult {

    public String status;
    public String msg;
    public List<Article> list;

    public static class Article {

        public int id;
        public String name;    // 文章名称
        public String author;  // 作者
        public String category;// 分类
        public String time;    // 时间
        public int point;      // 点击量
        public String summary; // 摘要
        public String content; // 内容
    }
}

3. 编写接口


这里采用GET和POST两种方式。
public interface APIService {

    @GET("getArticleList")
    Observable<ArticleResult> getArticleList1(
            @Query("pageSize") int pageSize,
            @Query("page") int page);


    @FormUrlEncoded
    @POST("getArticleList")
    Observable<ArticleResult> getArticleList2(
            @Field("pageSize") int pageSize,
            @Field("page") int page);
}

4. 初始化Retrofit

Retrofit retrofit = new Retrofit.Builder()
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .baseUrl("http://123.57.31.11/androidnet/")
        .build();

service = retrofit.create(APIService.class);

5. 接口调用

service.getArticleList1(10, 1)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Consumer<ArticleResult>() {
            @Override
            public void accept(ArticleResult articleResult) {
                // 处理返回数据
            }
        }, new Consumer<Throwable>() {
            @Override
            public void accept(Throwable throwable) {
                // 处理错误数据
            }
        });

二、封装优化


1. 实体类抽象


由于返回的JSON数据有公共的状态标识:
 
   
{
    "status": "OK",
    "msg": "获取成功!",
    "data": {
        // ... ...
    }
}
这里采取将其抽象为HttpResult实体,内部的数据以泛型T标识,对应实体对象:
public class HttpResult<T> {
    public String status;
    public String msg;
    public T data;
}
内部Article对象如下:
public class ArticleListResult {
    
    public List<Article> list;

    public static class Article {

        public int id;
        public String name;    // 文章名称
        public String author;  // 作者
        public String category;// 分类
        public String time;    // 时间
        public int point;      // 点击量
        public String summary; // 摘要
        public String content; // 内容
    }
}
API接口修改如下:
public interface APIService {

    @GET("getArticleList")
    Observable<HttpResult<ArticleListResult>> getArticleList1(
            @Query("pageSize") int pageSize,
            @Query("page") int page);


    @FormUrlEncoded
    @POST("getArticleList")
    Observable<HttpResult<ArticleListResult>> getArticleList2(
            @Field("pageSize") int pageSize,
            @Field("page") int page);
}
接口调用如下:
service.getArticleList1(10, 1)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Consumer<HttpResult<ArticleListResult>>() {
            @Override
            public void accept(HttpResult<ArticleListResult> articleList) {
                // 处理返回数据
            }
        }, new Consumer<Throwable>() {
            @Override
            public void accept(Throwable throwable) {
                // 处理错误数据
            }
        });

2. 实体数据剥壳

可以看到我们处理返回数据这里返回的数据为HttpResult<ArticleListResult>,那么能不能直接是ArticleListResult呢?熟悉Rx操作符的都知道map可以满足我们的需要。
 
  
service.getArticleList1(10, 1)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        // 对返回数据进行剥壳
        .map(new Function<HttpResult<ArticleListResult>, ArticleListResult>() {
            @Override
            public ArticleListResult apply(@NonNull HttpResult<ArticleListResult> articleListResult) throws Exception {
                return articleListResult.data;
            }
        })
        .subscribe(new Consumer<ArticleListResult>() {
            @Override
            public void accept(ArticleListResult articleList) {
                // 处理返回数据
            }
        }, new Consumer<Throwable>() {
            @Override
            public void accept(Throwable throwable) {
                // 处理错误数据
            }
        });
但是这样有一个问题,就是HttpResult中定义的status为"ERROR"时data为null,我们再取data就不合适了。
public class HttpResult<T> {
    public String status;
    public String msg;
    public T data;
}
将剥壳部分改为ResultTransform类,只有返回状态为“OK”的时候返回数据,这里可以根据自己项目中进行灵活判断。
public class ResultTransform<T> implements Function<HttpResult<T>, T> {

    @Override
    public T apply(@NonNull HttpResult<T> httpResult) throws Exception {
        if (!"OK".equals(httpResult.status)) {
            throw new APIException(httpResult.status, httpResult.msg);
        }
        return httpResult.data;
    }
}
APIException为RuntimeException的简单封装,其中的字段为返回JSON数据中标志状态信息。
public class APIException extends RuntimeException {

    private String errStatus;
    private String errMessage;

    public APIException(String errStatus, String errMessage) {
        this.errStatus = errStatus;
        this.errMessage = errMessage;
    }

    public String getErrorStatus() {
        return errStatus;
    }

    public String getErrorMessage() {
        return errMessage;
    }
}
为了避免ResultTransform每次访问网络都创建一次,把ResultTransform作为单例来调用。
public class ResultTransform<T> implements Function<HttpResult<T>, T> {

    private ResultTransform() {
    }

    public static ResultTransform getInstance() {
        return Holder.INSTANCE;
    }

    private static class Holder {
        private static final ResultTransform INSTANCE = new ResultTransform();
    }

    @Override
    public T apply(@NonNull HttpResult<T> httpResult) throws Exception {
        if (!"OK".equals(httpResult.status)) {
            throw new APIException(httpResult.status, httpResult.msg);
        }
        return httpResult.data;
    }
}
接口调用就变为了这样:
service.getArticleList1(10, 1)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .map(ResultTransform.getInstance())
        .subscribe(new Consumer<ArticleListResult>() {
            @Override
            public void accept(ArticleListResult articleList) {
                // 处理返回数据
            }
        }, new Consumer<Throwable>() {
            @Override
            public void accept(Throwable throwable) {
                // 处理错误数据
            }
        });

3. 日志输出

在开发中经常需要查看server端返回的数据是否正常,或者你给server端的同学说xx接口返回不正确,他多半会说你把参数发我下我看看。这些数据最好是在开发期间都打印到控制台比较好。

这里我们使用JakeWharton大神的 Timber进行日志的输出。
添加以下依赖:
compile 'com.jakewharton.timber:timber:4.5.1'
对Timber不熟悉的可以参考下 《Timber的使用与源码解析》
在项目的Application中初始化Timber,这里只种一棵Debug树:
public class DomoApplication extends Application {
    
    @Override
    public void onCreate() {
        super.onCreate();
        if (BuildConfig.DEBUG) {
            Timber.plant(new Timber.DebugTree());
        }
    }
}
在初始化Retrofit时添加日志拦截器:
 
  
HttpLoggingInterceptor loggerInterceptor = new HttpLoggingInterceptor(
        new HttpLoggingInterceptor.Logger() {
            @Override
            public void log(String message) {
                Timber.tag("HttpLogging").i(message);
            }
        }
);
loggerInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

OkHttpClient okHttpClient = new OkHttpClient.Builder()
        .addInterceptor(loggerInterceptor)
        .build();

Retrofit retrofit = new Retrofit.Builder()
        .client(okHttpClient)
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .baseUrl("http://123.57.31.11/androidnet/")
        .build();
OK,再访问接口的时候就会在我们的控制台下输入网络日志啦。
I/HttpLogging: --> POST http://123.57.31.11/androidnet/getArticleList http/1.1
I/HttpLogging: Content-Type: application/x-www-form-urlencoded
I/HttpLogging: Content-Length: 17
I/HttpLogging: pageSize=1&page=1
I/HttpLogging: --> END POST (17-byte body)
I/HttpLogging: <-- 200 OK http://123.57.31.11/androidnet/getArticleList (56ms)
I/HttpLogging: Server: nginx
I/HttpLogging: Date: Wed, 27 Sep 2017 10:22:21 GMT
I/HttpLogging: Content-Type: application/json;charset=UTF-8
I/HttpLogging: Transfer-Encoding: chunked
I/HttpLogging: Connection: keep-alive
I/HttpLogging: Vary: Accept-Encoding
I/HttpLogging: {"status": "OK","msg": "获取成功!","data": {"list": [{"id": 0,"name": "十月秋花,人生几度","author": "心怡","category": "经典美文","time": "2016-12-04 14:47:05","point": 4953,"summary": "一梦红尘烟雨,窗外流云几许。","content": "一梦红尘烟雨,窗外流云几许。云烟深处,水雾茫茫。阴无情,再好的花开也敌不过季节的流转。"}]}}
I/HttpLogging: <-- END HTTP (9909-byte body)
这样,请求方式、请求参数、响应码、数据就都打印出来啦,有一点就是返回的JSON数据格式不良好,能格式化下就好了。
在ResultTransform中添加日志输出,这里使用的是Gson,当然使用其他json转换工具的同学也可以使用对于的工具进行替换。
public class ResultTransform<T> implements Function<HttpResult<T>, T> {

    private static final String TAG = "ResultTransform";

    private static final Gson gson = new GsonBuilder()
            .setPrettyPrinting()
            .create();

    private ResultTransform() {
    }

    public static ResultTransform getInstance() {
        return Holder.INSTANCE;
    }

    private static class Holder {
        private static final ResultTransform INSTANCE = new ResultTransform();
    }

    @Override
    public T apply(@NonNull HttpResult<T> httpResult) throws Exception {
        if (!"OK".equals(httpResult.status)) {
            throw new APIException(httpResult.status, httpResult.msg);
        }
        try {
            String json = gson.toJson(httpResult);
            Timber.tag("Response").i(json);
        } catch (Exception e) {
            Timber.tag(TAG).w(e, "Error when serialize %s", httpResult.toString());
        }
        return httpResult.data;
    }
}
访问网络的时候日志输出就很清爽啦。
I/HttpLogging: --> POST http://123.57.31.11/androidnet/getArticleList http/1.1
I/HttpLogging: Content-Type: application/x-www-form-urlencoded
I/HttpLogging: Content-Length: 17
I/HttpLogging: pageSize=1&page=1
I/HttpLogging: --> END POST (17-byte body)
I/HttpLogging: <-- 200 OK http://123.57.31.11/androidnet/getArticleList (56ms)
I/HttpLogging: Server: nginx
I/HttpLogging: Date: Wed, 27 Sep 2017 10:22:21 GMT
I/HttpLogging: Content-Type: application/json;charset=UTF-8
I/HttpLogging: Transfer-Encoding: chunked
I/HttpLogging: Connection: keep-alive
I/HttpLogging: Vary: Accept-Encoding
I/HttpLogging: {"status": "OK","msg": "获取成功!","data": {"list": [{"id": 0,"name": "十月秋花,人生几度","author": "心怡","category": "经典美文","time": "2016-12-04 14:47:05","point": 4953,"summary": "一梦红尘烟雨,窗外流云几许。","content": "一梦红尘烟雨,窗外流云几许。云烟深处,水雾茫茫。阴无情,再好的花开也敌不过季节的流转。"}]}}
I/HttpLogging: <-- END HTTP (9909-byte body)
I/Response: {
                "status": "OK",
                "msg": "获取成功!",
                "data": {
                    "list": [
                        {
                            "id": 0,
                            "name": "十月秋花,人生几度",
                            "author": "心怡",
                            "category": "经典美文",
                            "time": "2016-12-04 14:47:05",
                            "point": 4953,
                            "summary": "一梦红尘烟雨,窗外流云几许。",
                            "content": "一梦红尘烟雨,窗外流云几许。云烟深处,水雾茫茫。阴无情,再好的花开也敌不过季节的流转。"
                        }
                    ]
                }
            }

3. 全局配置


细心的同学会发现,在ResultTransform中,直接写的json格式化,这时是没有区分是否要输出日志的,尽管Timber在Release时不会输出。即没有输出时也进行了json的格式化,这是洁癖的我们不能忍受的。那就进行下全局配置参数的封装吧。
@Override
public T apply(@NonNull HttpResult<T> httpResult) throws Exception {
    if (!"OK".equals(httpResult.status)) {
        throw new APIException(httpResult.status, httpResult.msg);
    }
    try {
        String json = gson.toJson(httpResult);
        Timber.tag("Response").i(json);
    } catch (Exception e) {
        Timber.tag(TAG).w(e, "Error when serialize %s", httpResult.toString());
    }
    return httpResult.data;
}

首先创建一个Configurator类,该类用于存放配置信息,只有一个key和value都为Object的HashMap类型的成员变量,并将其作为单例。
public final class Configurator {

    private static final HashMap<Object, Object> CONFIGS = new HashMap<>();

    private Configurator() {
    }

    static Configurator getInstance() {
        return Holder.INSTANCE;
    }

    private static class Holder {
        private static final Configurator INSTANCE = new Configurator();
    }
}
创建ConfigKeys类,作为配置信息的Key。这里先配置全局Context、APIHost、是否Release,ConfigReady用于标识配置完成。
public enum ConfigKeys {
    APPLICATION_CONTEXT,
    API_HOST,
    IS_RELEASED,
    CONFIG_READY
}
Configurator中添加对应的配置方法。
public final class Configurator {

    private static final HashMap<Object, Object> CONFIGS = new HashMap<>();

    private Configurator() {
        CONFIGS.put(ConfigKeys.CONFIG_READY, false);
    }

    static Configurator getInstance() {
        return Holder.INSTANCE;
    }

    private static class Holder {
        private static final Configurator INSTANCE = new Configurator();
    }

    public final void configure() {
        CONFIGS.put(ConfigKeys.CONFIG_READY, true);
    }

    public final Configurator withContext(Context context) {
        CONFIGS.put(ConfigKeys.APPLICATION_CONTEXT, context.getApplicationContext());
        return this;
    }

    public final Configurator withApiHost(String host) {
        CONFIGS.put(ConfigKeys.API_HOST, host);
        return this;
    }

    public final Configurator withIsReleased(boolean released) {
        CONFIGS.put(ConfigKeys.IS_RELEASED, released);
        return this;
    }

    private void checkConfiguration() {
        final boolean isReady = (boolean) CONFIGS.get(ConfigKeys.CONFIG_READY);
        if (!isReady) {
            throw new RuntimeException("Configuration is not ready,call configure");
        }
    }

    final <T> T getConfiguration(Object key) {
        checkConfiguration();
        final Object value = CONFIGS.get(key);
        if (value == null) {
            throw new NullPointerException(key.toString() + " IS NULL");
        }
        return (T) value;
    }
}
添加一个对外的操作类:
public class GlobalConfig {

    public static Configurator init(Context context) {
        Configurator configurator = Configurator.getInstance()
                .withContext(context);
        return configurator;
    }

    public static Configurator getConfigurator() {
        return Configurator.getInstance();
    }

    public static <T> T getConfiguration(Object key) {
        return getConfigurator().getConfiguration(key);
    }

    public static Context getApplicationContext() {
        return getConfiguration(ConfigKeys.APPLICATION_CONTEXT);
    }
}
通GlobalConfig、Configurator、ConfigKeys,三个类,就可以方便优雅地控制全局的配置啦,当然根据项目的需要也可以灵活的添加。
在Application中配置:
public class DomoApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        GlobalConfig.init(this)
                .withApiHost("http://123.57.31.11/androidnet/")
                .withIsReleased(false)
                .configure();
        
        Timber.plant(new Timber.DebugTree());
    }
}

4. Retrofit初始化封装

由于之前Retrofit初始化是在使用时进行的,如果多个地方都要调用接口,那都要初始化一遍显然是不合适的,之前的调用方式如下,而且没有区分Debug还是Release版本都进行了网络日志输出配置。可以通过读取配置,只在Debug时添加日志拦截器。
HttpLoggingInterceptor loggerInterceptor = new HttpLoggingInterceptor(
        new HttpLoggingInterceptor.Logger() {
            @Override
            public void log(String message) {
                Timber.tag("HttpLogging").i(message);
            }
        }
);
loggerInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

OkHttpClient okHttpClient = new OkHttpClient.Builder()
        .addInterceptor(loggerInterceptor)
        .build();

Retrofit retrofit = new Retrofit.Builder()
        .client(okHttpClient)
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .baseUrl("http://123.57.31.11/androidnet/")
        .build();

service = retrofit.create(APIService.class);
新建一个类HttpBuilder进行网络访问相关的初始化,在构建全局Retrofit客户端时使用的GsonConverterFactory,如果使用FastJson、Jackson等其他Json解析工具的同学可以灵活配置。
public class HttpBuilder {

    /**
     * 构建OkHttp
     */
    private static final class OKHttpHolder {
        private static final int TIME_OUT = 30;
        private static final OkHttpClient.Builder BUILDER = new OkHttpClient.Builder();

        private static OkHttpClient.Builder addInterceptor() {
            boolean isReleased = GlobalConfig.getConfiguration(ConfigKeys.IS_RELEASED);
            if (!isReleased) {
                HttpLoggingInterceptor loggerInterceptor = new HttpLoggingInterceptor(
                        new HttpLoggingInterceptor.Logger() {
                            @Override
                            public void log(String message) {
                                Timber.tag("HttpLogging").i(message);
                            }
                        }
                );
                loggerInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
                BUILDER.addInterceptor(loggerInterceptor);
            }
            return BUILDER;
        }

        private static final OkHttpClient OK_HTTP_CLIENT = addInterceptor()
                .connectTimeout(TIME_OUT, TimeUnit.SECONDS)
                .build();
    }

    /**
     * 构建全局Retrofit客户端
     */
    private static final class RetrofitHolder {
        private static final String BASE_URL = GlobalConfig.getConfiguration(ConfigKeys.API_HOST);
        private static final Retrofit RETROFIT_CLIENT = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .client(OKHttpHolder.OK_HTTP_CLIENT)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();
    }

    /**
     * Service接口
     */
    private static final class ServiceHolder {
        private static final APIService API_SERVICE = RetrofitHolder.RETROFIT_CLIENT.create(APIService.class);
    }

    public static APIService getAPIService() {
        return ServiceHolder.API_SERVICE;
    }
} 另外,这里只有一个接口APIServce,如果有多个网络接口类的可以一并初始化,如下:
/**
 * Service接口
 */
private static final class ServiceHolder {
    private static final APIService API_SERVICE = RetrofitHolder.RETROFIT_CLIENT.create(APIService.class);
    private static final APIService API_SERVICE_1 = RetrofitHolder.RETROFIT_CLIENT.create(APIService1.class);
}

public static APIService getAPIService() {
    return ServiceHolder.API_SERVICE;
}

public static APIService getAPIService1() {
    return ServiceHolder.API_SERVICE_1;
}
接口调用时修改为:
HttpBuilder.getAPIService().getArticleList2(10, 1)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .map(ResultTransform.getInstance())
        .subscribe(new Consumer<ArticleListResult>() {
            @Override
            public void accept(ArticleListResult articleList) {
                // 处理返回数据
            }
        }, new Consumer<Throwable>() {
            @Override
            public void accept(Throwable throwable) {
                // 处理错误数据
            }
        });

5. 内存泄漏

在访问网络的时候如果关闭界面,这时网络模块会持有Activity的引用,造成内存泄漏。就要求我们在关闭界面时清除网络访问。
在接口使用时修改如下:
public class MainActivity extends AppCompatActivity {

    CompositeDisposable mSubscriptions;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mSubscriptions = new CompositeDisposable();
    }

    public void click(View view) {
        Disposable disposable = HttpBuilder.getAPIService().getArticleList2(10, 1)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .map(ResultTransform.getInstance())
                .subscribe(new Consumer<ArticleListResult>() {
                    @Override
                    public void accept(ArticleListResult articleList) {
                        // 处理返回数据
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) {
                        // 处理错误数据
                    }
                });
        mSubscriptions.add(disposable);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mSubscriptions.clear();
    }
}

来看一下接口的使用:
Disposable disposable = HttpBuilder.getAPIService().getArticleList2(10, 1)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .map(ResultTransform.getInstance())
        .subscribe(new Consumer<ArticleListResult>() {
            @Override
            public void accept(ArticleListResult articleList) {
                // 处理返回数据
            }
        }, new Consumer<Throwable>() {
            @Override
            public void accept(Throwable throwable) {
                // 处理错误数据
            }
        });
mSubscriptions.add(disposable);
可以看到还是不够简洁,那再对访问的部分封装下:
public class HttpHelper {

    public static <T> Observable<T> request(final CompositeDisposable subscriptions,
                                            final Observable<HttpResult<T>> observable) {
        final RequestDisposable disposableWrap = new RequestDisposable();
        return Observable.create(new ObservableOnSubscribe<T>() {
            @Override
            public void subscribe(@NonNull final ObservableEmitter<T> e) throws Exception {
                disposableWrap.disposable = observable
                        .map(ResultTransform.getInstance())
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .onErrorReturn(ErrorReturnHolder.ERROR_RETURN)
                        .subscribe(
                                new Consumer<Object>() {
                                    @Override
                                    public void accept(Object o) throws Exception {
                                        if (o instanceof Throwable) {
                                            e.onError((Throwable) o);
                                            Timber.tag("HttpHelper.request() ==> onNext()").w((Throwable) o);
                                        } else {
                                            e.onNext((T) o);
                                            e.onComplete();
                                        }
                                    }
                                },
                                new Consumer<Throwable>() {
                                    @Override
                                    public void accept(Throwable throwable) throws Exception {
                                        if (null != disposableWrap.disposable) {
                                            subscriptions.remove(disposableWrap.disposable);
                                        }
                                        Timber.tag("HttpHelper.request() ==> onError()").w(throwable);
                                        e.onError(throwable);
                                    }
                                },
                                new Action() {
                                    @Override
                                    public void run() throws Exception {
                                        if (null != disposableWrap.disposable) {
                                            subscriptions.remove(disposableWrap.disposable);
                                        }
                                    }
                                });
                subscriptions.add(disposableWrap.disposable);
            }
        });
    }

    private static class RequestDisposable {
        Disposable disposable;
    }

    private static final class ErrorReturnHolder {

        private static final Function ERROR_RETURN = new Function<Throwable, Throwable>() {
            @Override
            public Throwable apply(@NonNull Throwable throwable) throws Exception {
                return throwable;
            }
        };
    }

}
这样再使用的时候就精简了不少:
HttpHelper.request(mSubscriptions, HttpBuilder.getAPIService().getArticleList2(10, 1))
        .subscribe(new Consumer<ArticleListResult>() {
            @Override
            public void accept(ArticleListResult articleListResult) {
                // 处理返回数据
            }
        }, new Consumer<Throwable>() {
            @Override
            public void accept(Throwable throwable) {
                // 处理错误数据
            }
        });

到此,对于网络的封装告一段落,有些同学可能觉得有点晕,我把代码放到github上,在 step_1分支。
$ git clone https://github.com/xuehuayous/RetrofitRxjavaDemo.git
$ cd RetrofitRxjavaDemo
$ git checkout step_1

三、数据层封装

通过以上的封装,在使用的时候已经非常方便了,但是在使用的时候你会发现一个问题,就是在Activity或者Fragment中直接调用,那就是直接把Model暴露给View层了,其实View层根本不关心你的数据来自哪里,View层会说,我管你是从网络、本地文件、还是内存缓存获取的,我要数据实体展示我该展示的就可以了。其实View层直接调用Model层也是不合适的,熟悉MVVM或MVP的同学都知道操作Model的应该是VM或P层。这里暂且为了封装Model层的方便直接在View层调用,后续会演变到MVVM上来。
HttpHelper.request(mSubscriptions, HttpBuilder.getAPIService().getArticleList2(10, 1))
        .subscribe(new Consumer<ArticleListResult>() {
            @Override
            public void accept(ArticleListResult articleListResult) {
                // 处理返回数据
            }
        }, new Consumer<Throwable>() {
            @Override
            public void accept(Throwable throwable) {
                // 处理错误数据
            }
        });
的确,在View层我们不想看到关于网络的任何事情,特别是带着明显标志的HttpHelper、HttpBuilder等。那就修改为如下图的模式:

之前封装的网络访问只是APIClient → API Server这一条线,接下要添加一个数据池,View层想要数据就在池子里面取就可以了,这个池子再去处理到底是在哪获取数据。
添加DataRepository基类,这个基类只有一个参数,而且只含有一个参数的构造方法,强制子类去实现。
public abstract class DataRepository {

    protected CompositeDisposable mSubscriptions;

    public DataRepository(CompositeDisposable subscriptions) {
        this.mSubscriptions = subscriptions;
    }
}
然后创建ArticleRepository继承DataRepository,getArticleList只是对网络层的封装,后续会进行复杂的,比如先获取缓存,设置缓存一天有效,如果过期则从网络获取并写入缓存。
public class ArticleRepository extends DataRepository {
    
    public ArticleRepository(CompositeDisposable subscriptions) {
        super(subscriptions);
    }

    public Observable<ArticleListResult> getArticleList1(int pageSize, int page) {
        return HttpHelper.request(mSubscriptions, HttpBuilder.getArticleService().getArticleList1(pageSize, page));
    }

    public Observable<ArticleListResult> getArticleList2(int pageSize, int page) {
        return HttpHelper.request(mSubscriptions, HttpBuilder.getArticleService().getArticleList2(pageSize, page));
    }

}
在onCreate中初始化ArticleRepository
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    this.mTvContent = (TextView) this.findViewById(R.id.tv_content);

    mSubscriptions = new CompositeDisposable();
    mArticleRepository = new ArticleRepository(mSubscriptions);
}
调用如下,这样是不是在View层就看不到网络层的东西了,说实话只看下面的还真不知道是在网络获取的数据呢,这样我们的目的就达到啦。
mArticleRepository.getArticleList1(10, 1)
        .subscribe(new Consumer<ArticleListResult>() {
            @Override
            public void accept(ArticleListResult articleListResult) {
                // 处理返回数据
            }
        }, new Consumer<Throwable>() {
            @Override
            public void accept(Throwable throwable) {
                // 处理错误数据
            }
        });


猜你喜欢

转载自blog.csdn.net/xuehuayous/article/details/78058170