本文是介紹有關如何搭建一個可擴充套件、維護和測試的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; <a href='http://www.jobbole.com/members/wx610506454'>@Override</a> protected void onCreate(Bundle savedInstanceState) { ... popularShowsPresenter = new PopularShowsPresenterImpl(this); popularShowsPresenter.onCreate(); } <a href='http://www.jobbole.com/members/wx610506454'>@Override</a> protected void onStop() { super.onStop(); popularShowsPresenter.onStop(); } <a href='http://www.jobbole.com/members/wx610506454'>@Override</a> public Context getContext() { return this; } <a href='http://www.jobbole.com/members/wx610506454'>@Override</a> public void showMovies(List<TvMovie> movieList) { moviesAdapter = new MoviesAdapter(movieList); popularMoviesRecycler.setAdapter(moviesAdapter); } <a href='http://www.jobbole.com/members/wx610506454'>@Override</a> public void showLoading() { loadingProgressBar.setVisibility(View.VISIBLE); } <a href='http://www.jobbole.com/members/wx610506454'>@Override</a> public void hideLoading() { loadingProgressBar.setVisibility(View.GONE); } <a href='http://www.jobbole.com/members/wx610506454'>@Override</a> public void showError(String error) { errorTextView.setVisibility(View.VISIBLE); errorTextView.setText(error); } <a href='http://www.jobbole.com/members/wx610506454'>@Override</a> 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; } <a href='http://www.jobbole.com/members/wx610506454'>@Override</a> public void onCreate() { ... popularMoviesView.showLoading(); Usecase getPopularShows = new GetMoviesUsecaseController(GetMoviesUsecase.TV_MOVIES); getPopularShows.execute(); } <a href='http://www.jobbole.com/members/wx610506454'>@Override</a> public void onStop() { ... } <a href='http://www.jobbole.com/members/wx610506454'>@Override</a> 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 |
<a href='http://www.jobbole.com/members/wx610506454'>@Override</a> public void onCreate() { BusProvider.getUIBusInstance().register(this); Usecase getPopularShows = new GetMoviesUsecaseController(GetMoviesUsecase.TV_MOVIES); getPopularShows.execute(); } ... <a href='http://www.jobbole.com/members/wx610506454'>@Override</a> public void onStop() { BusProvider.getUIBusInstance().unregister(this); } } |
接收事件時,展現器必須實現一個方法——它帶有與傳遞過來的事件所相同的資料型別引數,並且必須使用標註@Subscribe。
1 2 3 4 5 6 7 |
@Subscribe <a href='http://www.jobbole.com/members/wx610506454'>@Override</a> 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