转载请标明出处:
http://blog.csdn.net/xuehuayous/article/details/78058170;
本文出自:【Kevin.zhou的博客】
前言:现在网络访问已经基本都是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) {
// 处理错误数据
}
});