本文是介绍有关如何搭建一个可扩展、维护和测试的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<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); } ... } |
用例将被展现器执行,它们将接收应答并且管理视图的行为。
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