- 原文链接:A useful stack on android #1, architecture
- 原文作者: Saúl Molinero
- 译文出自: 小鄧子的简书
- 译者: 小鄧子
本文是如何开发一款具有扩展性,维护性和测试性的Android应用专题的第一篇。本专题将会涉及到一些设计模式和类库的使用方式,减少Android Developer日常开发的苦恼。
简介:##
作为例子,我将使用以下这个项目,事实上就是一个简单的电影概念目录,可以称之为视图或者其它。
关于电影的信息可以从一个叫做Themoviedb的公开API中获得,在这个版块中Apiary可以找到不错的文档说明。
项目基于Model View Presenter 设计模式,也参考了一些Material Design 设计规范,比如转场,(界面)结构,动画,配色等等。
所有代码都可以从Github中获得,所以请随意看,这里同样有一个视频用来展示App。
架构:##
架构的设计基于Model View Presenter ,它是Model View Controller 设计模式的一个变种。
这种设计试图抽象Presentation层的业务逻辑,在Android中这是很重要的,因为自身Framework 提倡这两部分与数据层解耦合,一个明显的例子就是Adapters和CursorLoaders。
这种架构促使业务逻辑层和数据层不再随着视图层的变换而改变,这样无论是Domain层的代码复用还是例如Database或者REST API等数据源的改变,都变得简单起来。
概述##
这种结构可以被划分为三个主要层次:
- presentation
- model
- domain
Presentation
Presentation层负责提供数据并展示图形化界面。
Model
Model层将负责提供信息,这一层并不知道Presentation层和Domain,它能够与数据库,REST API或者其他可持久化数据等实现连接。
在这一层,也可以实现一些应用程序的实体类,用来代表,电影,种类等等。
Domain
Domain层完全独立于Presentation层之外,这一层专门处理业务逻辑。
实现##
Domain层和Model层被放到两个java module中,app module也就是Android应用代表Presentation层,这里还有另外一个common module,用来存放一些公共类库和工具类们。
Domain module
Domain module存放着一些usecase
和它们的实现类,它们是应用程序的业务逻辑。
这个module完全独立于Android framework。
依赖它的模块有model module和common module。
一个usecase
可以用来获得不同类别电影的总评分,看一看哪个类别的电影最受欢迎,usecase
需要获取信息然后做出计算,所有这些信息都由Model层提供。
dependencies {
compile project (':common')
compile project (':model')
}
Model module##
model module负责处理信息,查询,保存,删除等等,我只处理了从API获取电影详情的操作。
也实现了一些实体类,比如TvMovie
,用来表现一部电影。
它目前只依赖common module,通过这个类库处理API请求,在这个例子中我使用Square出品的Retrofit,我将在接下来的博客中介绍Retrofit。
dependencies {
compile project(':common')
compile 'com.squareup.retrofit:retrofit:1.9.0'
}
Presentation module##
就是Android应用自身,包括resources
, assets
, 逻辑等等。
它与执行usecase
的Domain进行交互,比如可以用来获取某一时段的电影列表,或者从某部电影中获取特殊的数据。
这个模块只包含Presenter和View。
每一个Activity
,Fragment
,Dialog
都实现MVPView
接口,它指定了一些在View上进行显示,隐藏,显示信息等操作。
比如,PopularMoviesView
通过指定一些操作展示当前电影列表,然后MoviesActivity
实现它。
public interface PopularMoviesView extends MVPView {
void showMovies (List<TvMovie> movieList);
void showLoading ();
void hideLoading ();
void showError (String error);
void hideError ();
}
MVP设计模式就是让View变得尽可能的简单,由Presenter决定它们的行为。(译者注:View层应体现KISS原则,感兴趣的同学可以了解一下Keep it simple stupid )
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);
}
...
}
这个usecase
通过Presenter调用,并且Presenter接收相应结果,然后处理View上的表现。
通信##
对于这个项目,我选择了Message Bus(译者注:消息总线)系统,这个系统对于广播事件,或者在两个组件之间建立通信是非常有用的,尤其特别适用于后者。
基本上,通过Bus发送事件,对事件感兴趣的类,需要订阅Bus,才能消费那个事件。
适用这个系统可以降低模块间的耦合度。
为了实现这个系统总线,我使用Square出品的Otto类库。
我定义了两个Bus,一个用来使usecase
和REST API进行通信,另一个用来发送事件至Presentation
层。
REST_BUS
使用任意线程处理事件,UI_BUS
使用默认线程发送事件,这个线程就是主线程。
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 module管理。因为所有的模块都需要访问它,从而与Bus进行交互。
dependencies {
compile 'com.squareup:otto:1.3.5'
}
最后,想象一下这个场景,当用户打开应用,显示最受欢迎的电影。
当View
调用onCreate()
方法时,Presenter订阅UI_BUS
接收事件。当onStop()
方法被调用的时候Presenter取消订阅。Presenter运行GetMoviesUseCase
这个usecase
。
@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需要实现一个方法,这个方法所接受参数的数据类型必须与Bus发送的事件的数据类型一致,兵器必须使用注解:@Subscribe
。
@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