原文链接:原文地址原文作者:Saúl Molinero
这是一个关于如何设置开发一个可扩展、可维护、可测试的安卓环境主题系列文章的第一篇,在这个系列中,我将介绍一些设计模式和类库的使用方法避免android开发者在日常开发中感到发狂。
在例子中我将会依赖以下项目,一个简单电影分类项目做概念验证,一个能被标记作为视图或挂起的项目。
电影的有关信息是调用themoviedb 这个公共接口,你能在 Apiary里找到详细的文档。
这个项目基于 Model View Presenter 设计模式,也实现了一些 Material Design 的设计,像页面转换、结构、动画、颜色、等等。。。
所有的可用代码在 Github, 可以随意看看,同时这里也有一个 视频展示这个应用的行为。
在架构设计中我采用了设计模式:Model View Presenter,是Model View Controller模式的演变模式。
这种模式尝试去抽象表现层的业务逻辑,这在android中是很重要的,因为在我们的框架促进了这两部分与数据层的耦合,一个清晰的例子是 Adapters 或者CursorLoaders.
这种架构促使替换界面不需要修改业务逻辑层和数据层。这样就可以很简单的去重用代码或者改变不同的数据源例如数据库和REST API。
架构可以被分割为三个主要的层:
表现层是负责显示图形界面和为其提供数据。
模型层是负责提供信息,这一层不知道领域和表现层的内容,可以实现一个数据库的连接和接口、REST API、或者其它任何数据持久的方式。
在这一层同样实现了应用的的实体类,表示一个电影的类,类别等等。。。
领域层完全独立与表现层,它里面是属于应用的业务逻辑。
在Android 应用里,domain 和 model 层是分离的两个java模块,presentation层由app模块代表,还有一个common模块是用来提供公共类库和工具类的。
在domain模块里存放着用例和它们的实现,是应用的业务逻辑。
这个模块完全独立于android框架。
他依赖于model模块和common模块。
一个用例需要获得各种主题电影的总评分,看看哪一类最受欢迎,用例需要获得电影信息然后做出计算,电影信息由model层提供。
dependencies {
compile project (':common')
compile project (':model')
}
model module负责管理信息,选择、保存、删除信息等等。。。在第一个版本我仅仅管理电影信息API的操作。
它同样实现了实体类,例如TvMovie
,表示一个电影。
这个模块至今只依赖common模块,这个类库被用来管理API的请求,这样我需要使用Square的Retrofit,我会在下面介绍retrofit。
dependencies {
compile project(':common')
compile 'com.squareup.retrofit:retrofit:1.9.0'
}
是Android应用本身的模块,有自己resources, assets, 逻辑等。。。
他也与domain通过运行的用例互相作用,在例子中需要获取某一时间的当前电影列表,或者从电影请求具体的数据。
在这个模块里有presenters 和 views。
每个Activity
, Fragment
, Dialog
, 都实现一个 MVPView
接口,它指定了显示、隐藏、打印信息等需要支持显示的操作。
例如,PopularMoviesView
视图接口指定了显示当前电影的列表的操作,由MoviesActivity
类实现这些方法。
public interface PopularMoviesView extends MVPView {
void showMovies (List<TvMovie> movieList);
void showLoading ();
void hideLoading ();
void showError (String error);
void hideError ();
}
模式:MVP 中的视图尽可能简单,presenter决定视图的行为。
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执行,它将会接收response 和管理views的行为。
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实现这个系统。
我定义了两个总线,一个使用REST API和用例通信,另一个向表现层发送事件。
REST_BUS
使用任何线程处理事件,UI_BUS
发送事件通过主线程(默认UI主线程)。
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;
}
}
这个类由common模块管理,因为所有的模块都会获取它并与主线互相作用。
dependencies {
compile 'com.squareup:otto:1.3.5'
}
最后,思考下面的例子,在用户打开应用的时候显示最受欢迎的电影。
当 onCreate
方法在 Android View
调用时,presenter 在UI_BUS上订阅接收事件, onStop()
方法调用时presenter取消自身订阅,例如 presenter 运行 GetMoviesUseCase
用例。
@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
。
@Subscribe
@Override
public void onPopularMoviesReceived(PopularMoviesApiResponse popularMovies) {
popularMoviesView.hideLoading();
popularMoviesView.showMovies(popularMovies.getResults());
}
Resources:
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