本文是介绍有关如何搭建一个可扩展、维护和测试的Android环境系列教程的第一篇。在这一系列中我会涉及到一些Android开发者会用到的模式和库。
应用场景
我将基于一个简单的电影分类项目作为示例。在这个项目中,分类信息可以用视图(Views)展示出来。
影片信息可通过叫做themoviedb的公共API获取,你可以在Apiary中查阅相关说明文档。
这个项目是基于MVP(Model View Presenter)模式进行设计的,以及一些Material Design指南中的实现,如传输(transitions)、结构、动画、色彩等……
所有的代码都可以从Github上获得,所以请随便查看。另外这里还有一个视频(注:需FQ)演示。
架构:
架构设计采用MVP模式,它由MVC模式演变而来。
此模式是将应用层中的业务逻辑抽象出来,这在Android中十分重要。因为Android本身的框架将这一层与数据层耦合在了一起,一个明显的例子就是Adapter或者CursorLoader。
这种架构的方便之处在于,视图的变化无需涉及业务逻辑层和数据层的修改。可在领域内方便地进行复用,或者在不同的数据源间进行切换(如数据库或REST API)。
总览
架构可分为三个主要层次:
- 展现层
- 模型(数据)层
- 领域层
展现层
展现层负责显示图形界面并且为它提供数据。
模型(数据)层
模型层负责提供信息(数据),它不需要知道领域层和展现层,它只是单纯地实现与数据库的连接和接口(与REST API或者其它任何存储方式)。
在这一层中也实现应用的实体,如影片、类别等类。
领域层
领域层与展现层完全分开,是单独存在的,它只于应用程序的业务逻辑相关。
实现
领域层与数据层作为单独的Java模块存在,展现层则表现为Android应用程序模块。此外还有一个公共模块用于共享库和工具类。
领域模块
领域模块承载了用例和它们的实现,即应用程序的业务逻辑。
这个模块是完全独立于Android框架的。
它依赖于模型模块和公共模块。
一个用例可能是指获取各种类型影片的总排名、查看哪一类影片被请求得最多等。用例可能需要获得信息并对其进行计算,这些信息是由模型(Model)提供的。
1
2
3
4
|
dependencies {
compile project (':common')
compile project (':model')
}
|
模型模块
模型模块负责管理信息、选择、保存、删除等等。在第一个版本中我仅仅管理一些与影片信息API相关的内容。
另外它也实现了实体,如TvMovie —— 表示一部影片。
这个模块只与公共模块相依赖,引用的库是用来管理请求API的。这里我使用了Square的Retrofit,我会在以后的文章中再讨论Retrofit的。
1
2
3
4
|
dependencies {
compile project(':common')
compile 'com.squareup.retrofit:retrofit:1.9.0'
}
|
表现模块
就是Android应用程序本身,包括资源、assets、逻辑等等……
它通过运行用例的方式与领域模块进行交互,例如获得某一时间的影片列表、请求一个影片数据等。
这个模块中有展现与视图。
每个Activity、Fragment、Dialog都实现一个MVPView接口,它定义了在视图上用于显示、隐藏和“画”出信息的操作。
例如视图PopularMoviesView,定义了显示当前影片列表的操作,它被MoviesActivity所实现。
1
2
3
4
5
6
7
8
9
10
11
12
|
public interface PopularMoviesView extends MVPView {
void showMovies (List
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
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);
}
...
}
|
用例将被展现器执行,它们将接收应答并且管理视图的行为。
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
|
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());
}
}
|
通信
在这个项目中我选择了一个消息总线系统(Message Bus system),这个系统对广播事件或者建立组件间的通信非常有用(尤其是对后者)。
基本上说,事件就是由总线被发送出来,然后由感兴趣的类订阅并接收处理该事件。
使用这个系统会极大地降低模块间的耦合。
实现系统总线,我使用Square的Otto库。
我定义了两个总线,分别用于用例与REST API的通信,以及发送事件给展现层。
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;
}
}
|
这个类被公共模块所管理,因为所用模块都通过访问它来与总线交互。
1
2
3
|
dependencies {
compile 'com.squareup:otto:1.3.5'
}
|
最后,考虑下面的示例,用户打开应用程序然后最流行的影片就被展现出来了。
当Android视图中的onCreate方法被调用时,展现器在UI_BUS上订阅所需接收的事件。当onStop被调用时取消订阅。然后,展现器运行GetMovieUseCase用例。
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);
}
}
|
接收事件时,展现器必须实现一个方法——它带有与传递过来的事件所相同的数据类型参数,并且必须使用标注@Subscribe。
1
2
3
4
5
6
7
|
@Subscribe
@Override
public void onPopularMoviesReceived(PopularMoviesApiResponse popularMovies) {
popularMoviesView.hideLoading();
popularMoviesView.showMovies(popularMovies.getResults());
}
|
资源:
- Architecting Android…The clean way? - Fernando Cejas
- Effective Android UI - Pedro Vicente Gómez Sanchez
- Reactive programming and message buses for mobile - Csaba Palfi
- The clean architecture - Uncle Bob
- MVP Android - Antonio Leiva
转载:http://android.jobbole.com/82051