本文是这一系列的开篇,主要涉及一些基础环境设置,目的是能开发出及扩展、及维护和易测试的项目,而本系列将会涵盖一些模式和库,它们会或多或少改善让Android开发者的体验。
场景
本篇使用的Android项目救命,是个电影类别的原型,可以标记是否已经观看,影片的信息可以通过themoviedb提供的API获得,Apiary上有相关的文档,这个工程基于MVP模式,遵循了Material Design,如转场、结构、动画、动画等。
Github上有示例代码,并配有视频。

架构:
正如前面所说,本项目基于的Model View Presenter是Model View Controller的变种。MVP试图抽取表示层中的逻辑,因为Android的Framework很容易把两者混合在数据层中,如Adapters或者CursorLoaders。
这种架构在修改视图时,不用修改逻辑层和数据层,这有既利于逻辑的重用又方便数据源的修改,比如将它从数据库更改为REST API。
概观
整个结构分为下面3层:

表示层
表示层负责显示图形界面并为提供要显示的内容。
模型层
表示层通常和领域逻辑联系,而模型层只负责提供数据,它可以连接数据库、REST API或者其它形式的持久化方式。
该层也实现了应用的实体,比如用来表示一部电影,一个类别等等。
领域层
领域层完全独立于表示层,应用的业务逻辑都归类在这里。
实现
下图显示了一个Android项目的目录结构(虚拟),模型层和领域层置于两个模块(module)中,而表示层出现在了app模块中,其它的模块包含了一些公用库和工具。
ps:app模块是Android工程模块。

领域模块
领域模块包含了应用的业务逻辑,比如用户用例和一些实现。并且完全独立于Android的框架。它依赖于模型层和公共模块。
比如:从影片各个分类的排名榜中,找出是受欢迎的类别;对模型层提供的信息进行统计。
1
2
3
4
| dependencies {
compile project (':common')
compile project (':model')
}
|
模型模块
负责管理数据(增、删、改、查)。在目前的1.0版本中,只有一些电影信息的API。它还实现了实体,比如用TvMovie类表示一部电影。现在它只依赖common模块,用来发起API调用,我还使用了Retrofit(Square开发),随后的文章中我会再谈谈Retrofit。
1
2
3
4
| dependencies {
compile project(':common')
compile 'com.squareup.retrofit:retrofit:1.9.0'
}
|
表示层模块
其实就是一个Andorid应用本身了,比如一些资源、逻辑等。
它常常和业务逻辑交互,比如显示在某个时间点上显示所有电影的排期、获得某部影片的信息。
Presenter和视图也出现在这里。
每个Activity, Fragment, Dialog都实现了在MVPView接口中,声明了电影相关的显示、隐藏、绘制方法,任何与电影相关的表示层组件(Activity, Fragment, Dialog),都必须实现该接口。
下图示例,PopularMoviesView视图接口,声明了用来显示电影列表的一些方法,而在MoviesActivity中实现了这些方法。
1
2
3
4
5
6
7
8
9
10
11
12
| public interface PopularMoviesView extends MVPView {
void showMovies (List<TvMovie> movieList);
void showLoading ();
void hideLoading ();
void showError (String error);
void hideError ();
}
|
MVP模式目的是让视图尽可能的简单,让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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
| public class MoviesActivity extends ActionBarActivity implements
PopularMoviesView, ... {
...
private PopularShowsPresenter popularShowsPresenter;
private RecyclerView popularMoviesRecycler;
private ProgressBar loadingProgressBar;
private MoviesAdapter moviesAdapter;
private TextView errorTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
popularShowsPresenter = new PopularShowsPresenterImpl(this);
popularShowsPresenter.onCreate();
}
@Override
protected void onStop() {
super.onStop();
popularShowsPresenter.onStop();
}
@Override
public Context getContext() {
return this;
}
@Override
public void showMovies(List<TvMovie> movieList) {
moviesAdapter = new MoviesAdapter(movieList);
popularMoviesRecycler.setAdapter(moviesAdapter);
}
@Override
public void showLoading() {
loadingProgressBar.setVisibility(View.VISIBLE);
}
@Override
public void hideLoading() {
loadingProgressBar.setVisibility(View.GONE);
}
@Override
public void showError(String error) {
errorTextView.setVisibility(View.VISIBLE);
errorTextView.setText(error);
}
@Override
public void hideError() {
errorTextView.setVisibility(View.GONE);
}
...
}
|
下面的示例中,都是由presenters来执行,负责接收事件并管理视图的行为。
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
| public class PopularShowsPresenterImpl implements PopularShowsPresenter {
private final PopularMoviesView popularMoviesView;
public PopularShowsPresenterImpl(PopularMoviesView popularMoviesView) {
this.popularMoviesView = popularMoviesView;
}
@Override
public void onCreate() {
...
popularMoviesView.showLoading();
Usecase getPopularShows = new GetMoviesUsecaseController(GetMoviesUsecase.TV_MOVIES);
getPopularShows.execute();
}
@Override
public void onStop() {
...
}
@Override
public void onPopularMoviesReceived(PopularMoviesApiResponse popularMovies) {
popularMoviesView.hideLoading();
popularMoviesView.showMovies(popularMovies.getResults());
}
}
|
通讯
这里也选用了消息队列,它既让广播事件变得好用又能方便组件间的通讯,从上面的例子就能看得出来。
事件会发往一个队列,所有订阅这一事件的类都会接收到这一事件。这一系统降低了模块间的耦合。
我采用了Square开发的Otto库来实现这个系统
我声明了2个队列,一个用来和REST API通讯,另一个则向presentation层发送事件。
REST_BUS
在另一个线程中处理事件,而UI_BUS
则在默认线程中发送事件,也就是主线程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| public class BusProvider {
private static final Bus REST_BUS = new Bus(ThreadEnforcer.ANY);
private static final Bus UI_BUS = new Bus();
private BusProvider() {};
public static Bus getRestBusInstance() {
return REST_BUS;
}
public static Bus getUIBusInstance () {
return UI_BUS;
}
}
|
BusProvider类放置在common模块中,因为所有模块都要和它进行数据交换。
1
2
3
| dependencies {
compile 'com.squareup:otto:1.3.5'
}
|
最后来看一个例子,用户打开应用查看最受欢迎的影片列表。
在onCreate
方法中,presentersubscribe了UI_BUS
发出的事件,并且在onStop
被调用时,进行对应的unsubscribe操作,然后presenter调用了GetMoviesUseCase。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| @Override
public void onCreate() {
BusProvider.getUIBusInstance().register(this);
Usecase getPopularShows = new GetMoviesUsecaseController(GetMoviesUsecase.TV_MOVIES);
getPopularShows.execute();
}
...
@Override
public void onStop() {
BusProvider.getUIBusInstance().unregister(this);
}
}
|
要接收事件,presenter必须实现相应的方法,并且遵循下面的约定:
- 方法中的参数类型要和发送时事件的类型相同
- 使用@Subscribe注解
1
2
3
4
5
6
7
| @Subscribe
@Override
public void onPopularMoviesReceived(PopularMoviesApiResponse popularMovies) {
popularMoviesView.hideLoading();
popularMoviesView.showMovies(popularMovies.getResults());
}
|
相关资源:
译自:A useful stack on android #1, architecture