- 原文鏈接: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