[置顶] android应用框架系列一,架构
A useful stack on android #1, architecture
02 Feb 2015 原文链接:原文地址原文作者:Saúl Molinero
这是一个关于如何设置开发一个可扩展、可维护、可测试的安卓环境主题系列文章的第一篇,在这个系列中,我将介绍一些设计模式和类库的使用方法避免android开发者在日常开发中感到发狂。
Scenario
在例子中我将会依赖以下项目,一个简单电影分类项目做概念验证,一个能被标记作为视图或挂起的项目。
电影的有关信息是调用themoviedb这个公共接口,你能在Apiary里找到详细的文档。
这个项目基于Model View Presenter设计模式,也实现了一些Material Design的设计,像页面转换、结构、动画、颜色、等等。。。
所有的可用代码在Github, 可以随意看看,同时这里也有一个视频展示这个应用的行为。
Architecture
在架构设计中我采用了设计模式:Model View Presenter,是Model View Controller模式的演变模式。
这种模式尝试去抽象表现层的业务逻辑,这在android中是很重要的,因为在我们的框架促进了这两部分与数据层的耦合,一个清晰的例子是Adapters或者CursorLoaders.
这种架构促使替换界面不需要修改业务逻辑层和数据层。这样就可以很简单的去重用代码或者改变不同的数据源例如数据库和REST API。
Overview
架构可以被分割为三个主要的层:
- presentation
- model
- domain
Presentation
表现层是负责显示图形界面和为其提供数据。
Model
模型层是负责提供信息,这一层不知道领域和表现层的内容,可以实现一个数据库的连接和接口、REST API、或者其它任何数据持久的方式。
在这一层同样实现了应用的的实体类,表示一个电影的类,类别等等。。。
Domain
领域层完全独立与表现层,它里面是属于应用的业务逻辑。
Implementation
在Android 应用里,domain 和 model 层是分离的两个java模块,presentation层由app模块代表,还有一个common模块是用来提供公共类库和工具类的。
Domain module
在domain模块里存放着用例和它们的实现,是应用的业务逻辑。
这个模块完全独立于android框架。
他依赖于model模块和common模块。
一个用例需要获得各种主题电影的总评分,看看哪一类最受欢迎,用例需要获得电影信息然后做出计算,电影信息由model层提供。
dependencies { compile project (':common') compile project (':model')}
Model module
model module负责管理信息,选择、保存、删除信息等等。。。在第一个版本我仅仅管理电影信息API的操作。
它同样实现了实体类,例如TvMovie
,表示一个电影。
这个模块至今只依赖common模块,这个类库被用来管理API的请求,这样我需要使用Square的Retrofit,我会在下面介绍retrofit。
dependencies { compile project(':common') compile 'com.squareup.retrofit:retrofit:1.9.0'}
Presentation module
是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()); }}
Communication
这个项目我选择了一个消息总线系统,这个系统在广播事件和建立两个组件之间的通信非常有用,尤其是后者配合的十分完美。
主要的事件都通过总线发送,感兴趣的消费类需要在总线订阅。
使用这个系统,模块之间将会有一个低耦合度。
我使用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
方法在 AndroidView
调用时,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
更多相关文章
- Android(安卓)源码分析 - 事件分发机制
- Android(安卓)HAL层模块的加载过程
- Afinal 介绍
- 使用Django搭建android后台(登录模块)
- Android中TouchEvent触摸事件机制
- Android游戏开发之旅系列一
- Android(安卓)事件分发
- 从源码角度分析Activity、Window和DecorView的关系
- Android项目中编译 C的模块