本文是介紹有關如何搭建一個可擴展、維護和測試的Android環境系列教程的第一篇。在這一系列中我會涉及到一些Android開發者會用到的模式和庫。
應用場景
我將基於一個簡單的電影分類項目作為示例。在這個項目中,分類信息可以用視圖(Views)展示出來。
影片信息可通過叫做themoviedb的公共API獲取,你可以在Apiary中查閱相關說明文檔。
這個項目是基於MVP(Model View Presenter)模式進行設計的,以及一些Material Design指南中的實現,如傳輸(transitions)、結構、動畫、色彩等……
所有的代碼都可以從Github上獲得,所以請隨便查看。另外這里還有一個視頻(注:需翻牆)演示。

架構:
架構設計采用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
