干货集中营 Android 版 --- MVP、RxJava、Retrofit 综合实践

干货集中营 分享一些技术圈儿的干货,有意思的视频图片和文章,还偶尔会推荐些靠谱的公司。
MVPRxJavaRetrofit 是 Android 开发最流行,最酷的技术,秉着学习的态度,我使用干货集中营 API 开发了一款 Android 端应用,主要使用到的技术有 MVP 、 RxJava 、 Retrofit 等。对于 RxJava 不熟悉的同学可以看看给 Android 开发者的 RxJava 详解 学习。本篇文章将介绍这款应用的架构、涉及到的技术要点、MVP、RxJava、Retrofit 的综合实践等。

干货应用介绍

下面我放几张图简要介绍一下这款应用的功能和界面。
首先是应用的主界面,首页基本借鉴(照抄)了知乎日报的首页,展示的主要内容是干货集中营的今日推荐,顶部是最近 4 天的福利。每天的推荐内容涵盖了 Android 、 iOS 、 前端等技术。
main.jpg
接下来是抽屉菜单,菜单中按类别展示了不同的条目
menu.jpg
接下来是内容页面,内容页面采用了 WebView 进行展示
detail.jpg
下面是福利列表页,福利列表页采用 RecyclerView 实现了一个瀑布流样式的列表
welfare.jpg
接下来是福利详情页,福利详情页采用了 PopupWindow 弹出的形式,展示图片采用了 PhotoView 控件,图片加载库采用的是 picasso 。
welfare_detail.jpg

打包结构

首先看一下项目的打包结构:
project.png

  • base 包中封装了 Retrofit 、 RxJava 和 picasso 数据库、Adapter 等基础库,还定义了 BaseActivity 、 BaseFragment 等。
  • biz 包也就是 MVP 中的 model 层。
  • common 包定义了 Constant 、 Url 等类。
  • entity 包中存放项目用到的实体类。
  • presenter 包中定义了 MVP 中的 Presenter 层的代码。
  • utils 包中定义了项目中用到的工具类。
  • view 包中定义了 MVP 中的 view 层,包含了 项目中的 Activity 、 Fragment 、 Adapter 和 view 层的接口。
  • widget 是项目中用到的自定义控件。
    现在感觉代码分包还有不合理的地方,包太多太乱,很多包都可以合并,这样结构会更清楚。

Retrofit 和 RxJava 的封装

项目中为了将 Retrofit 和 RxJava 隔离在 Http 层,所以对 Retrofit 和 RxJava 进行了封装,通过封装后 RxJava 被隔离在了 Http 层,通过这种方式使得上层代码不用直接使用 RxJava ,减小了代码的复杂度,但是缺点很明显,上层也使用不到了 RxJava 的优点,孰优孰略大家可以自己思考自己抉择。下面详细介绍一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class CommonRx {
public static Retrofit initRetrofit(String baseUrl) {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(60, TimeUnit.SECONDS)
.build();


Gson gson = new GsonBuilder().setLenient().create();
return new Retrofit.Builder()
.baseUrl(baseUrl)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.client(okHttpClient)
.build();
}
}

CommonRx 类只有一个方法 initRetrofit ,需要传入参数 baseUrl 进行构造 Retrofit 对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class HttpProvider<T> {
private HttpProvider() {
}

private static HttpProvider instance;

public static HttpProvider getInstance() {
if (instance == null) {
synchronized (HttpProvider.class) {
if (instance == null) {
instance = new HttpProvider();
}
}
}
return instance;
}

public T getService(String baseUrl, Class<T> service) {
return CommonRx.initRetrofit(baseUrl).create(service);
}
}

HttpProvider 使用了单例模式的设计,HttpProvider 中的 getService 方法调用 CommonRx 的 initRetrofit 方法创建了 service 对象。对这一块不熟悉的同学可以访问Retrofit 官方文档 进行学习。
1
2
3
4
5
6
7
public interface CallBack<T> {
void onCompleted();

void onError(Throwable e);

void onNext(T t);
}

为了隔离 RxJava ,因此封装了 CallBack 接口,它的三个回调方法和 RxJava 的三个回调方法一致。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class HttpResult<T> {
public boolean error;
public List<T> results;
public int count;
}

public class ApiProvider<T> {
//....
public void execute(Observable<HttpResult<T>> observable, final CallBack<HttpResult<T>> callBack) {

if (checkNetWork()) {
return;
}

observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<HttpResult<T>>() {
@Override
public void onCompleted() {
callBack.onCompleted();
}

@Override
public void onError(Throwable e) {
callBack.onError(e);
}

@Override
public void onNext(HttpResult<T> tHttpResult) {
if (tHttpResult.error) {
callBack.onError(new HttpErrorException("请求数据出错"));
} else {
callBack.onNext(tHttpResult);
onCompleted();
}
}
});
}
//....
}

根据干货集中营 API的返回结果封装了 HttpResult 类。最后的调用示例如下:
1
2
3
CategoryService categoryService = mHttpProvider.getService(Url.URL_CATEGORY, CategoryService.class);
ApiProvider<DryGoods> apiProvider = ApiProvider.getInstance();
apiProvider.execute(categoryService.loadCategory(category, pageSize, page), callBack);

可以看到最终的调用方式还是比较简单的, RxJava 和 Retrofit 都被隔离在了 Http 层,上层不需要关心 RxJava 和 Retrofit 的具体使用方法,但是劣势很明细,在这个过程中并没有使用到 RxJava 的特点,具体怎么抉择,大家可以思考。

MVP 实践

我们选择项目中按类别查找干货的业务介绍一下项目中的 MVP 实践。
首先是 Model 层:

1
2
3
4
5
6
7
8
9
public class CategoryBizImpl extends BaseBiz<CategoryService> implements CategoryBiz {
@Override
public void loadData(String category, int page, int pageSize, CallBack<HttpResult<DryGoods>> callBack) {
CategoryService categoryService = mHttpProvider.getService(Url.URL_CATEGORY, CategoryService.class);

ApiProvider<DryGoods> apiProvider = ApiProvider.getInstance();
apiProvider.execute(categoryService.loadCategory(category, pageSize, page), callBack);
}
}

CategoryBizImpl 实现了接口 CategoryBiz ,继承于 BaseBiz ,其中 mHttpProvider 便是在 BaseBiz 中进行初始化的。在 Model 层使用 ApiProvider 向服务器请求数据,请求成功后会回调 CallBack 接口的回调方法。
下面是 View 层:
1
2
3
4
5
6
7
8
public interface ICategoryView extends IListView<DryGoods> {
}

public interface IListView<T> {
void refreshListView(List<T> data);

void updateListView(List<T> data);
}

ICategoryView 比较简单,它只是继承了 IListView 的 refreshListView 和 updateListView 方法。
最后看一下 Presenter 层:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class CategoryPresenter {
private CategoryBiz mCategoryBiz;
private ICategoryView mCategoryView;

public CategoryPresenter(ICategoryView categoryView) {
mCategoryView = categoryView;
mCategoryBiz = new CategoryBizImpl();
}

public void initCategory(String category, int pageSize) {
CallBack<HttpResult<DryGoods>> callBack = new CallBack<HttpResult<DryGoods>>() {
@Override
public void onCompleted() {
}

@Override
public void onError(Throwable e) {
}

@Override
public void onNext(HttpResult<DryGoods> welfareHttpResult) {
if (welfareHttpResult.results.size() > 0) {
mCategoryView.refreshListView(welfareHttpResult.results);
}
}
};
mCategoryBiz.loadData(category, 1, pageSize, callBack);
}
//....
}

Presenter 层连接了 Model 层和 View 层, initCategory 调用了 CategoryBiz 的 loadData 方法进行数据请求,并将 callBack 传到了 CategoryBiz ,请求数据成功后会回调 callBack 中相应的方法,在 callBack 的回调方法中调用 ICategoryView 中相应的 View 这就是项目中 MVP 的实践。

总结

更多内容可访问:利用责任链模式实现加载不同来源的数据和我的 GitHub
这个代码最早提交是在4个月前,当时还觉得代码写的不错,可是现在回头再看真的是不忍直视,但是还是坚持把这篇博客写完了,看自己过去的代码就是最好的学习。知乎上有一个问题:看自己几年前写的代码是怎样的一种感受?大家可以去领略一下。
不行了,看完这些代码好难受/(ㄒoㄒ)/~~