- 原文鏈接:A useful stack on android #1, architecture
- 原文作者: Saúl Molinero
- 譯文出自: 小鄧子的簡書
- 譯者: 小鄧子
本文是如何開發(fā)一款具有擴(kuò)展性,維護(hù)性和測(cè)試性的Android應(yīng)用專題的第一篇。本專題將會(huì)涉及到一些設(shè)計(jì)模式和類庫的使用方式菇存,減少Android Developer日常開發(fā)的苦惱彰居。
簡介:##
作為例子,我將使用以下這個(gè)項(xiàng)目撰筷,事實(shí)上就是一個(gè)簡單的電影概念目錄陈惰,可以稱之為視圖或者其它。
關(guān)于電影的信息可以從一個(gè)叫做Themoviedb的公開API中獲得毕籽,在這個(gè)版塊中Apiary可以找到不錯(cuò)的文檔說明抬闯。
項(xiàng)目基于Model View Presenter 設(shè)計(jì)模式,也參考了一些Material Design 設(shè)計(jì)規(guī)范关筒,比如轉(zhuǎn)場(chǎng)溶握,(界面)結(jié)構(gòu),動(dòng)畫蒸播,配色等等睡榆。
所有代碼都可以從Github中獲得,所以請(qǐng)隨意看袍榆,這里同樣有一個(gè)視頻用來展示App胀屿。
架構(gòu):##
架構(gòu)的設(shè)計(jì)基于Model View Presenter ,它是Model View Controller 設(shè)計(jì)模式的一個(gè)變種包雀。
這種設(shè)計(jì)試圖抽象Presentation層的業(yè)務(wù)邏輯宿崭,在Android中這是很重要的,因?yàn)樽陨?em>Framework 提倡這兩部分與數(shù)據(jù)層解耦合才写,一個(gè)明顯的例子就是Adapters和CursorLoaders葡兑。
這種架構(gòu)促使業(yè)務(wù)邏輯層和數(shù)據(jù)層不再隨著視圖層的變換而改變,這樣無論是Domain層的代碼復(fù)用還是例如Database或者REST API等數(shù)據(jù)源的改變赞草,都變得簡單起來讹堤。
概述##
這種結(jié)構(gòu)可以被劃分為三個(gè)主要層次:
- presentation
- model
- domain
Presentation
Presentation層負(fù)責(zé)提供數(shù)據(jù)并展示圖形化界面。
Model
Model層將負(fù)責(zé)提供信息厨疙,這一層并不知道Presentation層和Domain洲守,它能夠與數(shù)據(jù)庫,REST API或者其他可持久化數(shù)據(jù)等實(shí)現(xiàn)連接轰异。
在這一層岖沛,也可以實(shí)現(xiàn)一些應(yīng)用程序的實(shí)體類暑始,用來代表搭独,電影,種類等等廊镜。
Domain
Domain層完全獨(dú)立于Presentation層之外牙肝,這一層專門處理業(yè)務(wù)邏輯。
實(shí)現(xiàn)##
Domain層和Model層被放到兩個(gè)java module中,app module也就是Android應(yīng)用代表Presentation層配椭,這里還有另外一個(gè)common module虫溜,用來存放一些公共類庫和工具類們。
Domain module
Domain module存放著一些usecase
和它們的實(shí)現(xiàn)類股缸,它們是應(yīng)用程序的業(yè)務(wù)邏輯衡楞。
這個(gè)module完全獨(dú)立于Android framework。
依賴它的模塊有model module和common module敦姻。
一個(gè)usecase
可以用來獲得不同類別電影的總評(píng)分瘾境,看一看哪個(gè)類別的電影最受歡迎,usecase
需要獲取信息然后做出計(jì)算镰惦,所有這些信息都由Model層提供迷守。
dependencies {
compile project (':common')
compile project (':model')
}
Model module##
model module負(fù)責(zé)處理信息,查詢旺入,保存兑凿,刪除等等,我只處理了從API獲取電影詳情的操作茵瘾。
也實(shí)現(xiàn)了一些實(shí)體類礼华,比如TvMovie
,用來表現(xiàn)一部電影拗秘。
它目前只依賴common module卓嫂,通過這個(gè)類庫處理API請(qǐng)求,在這個(gè)例子中我使用Square出品的Retrofit聘殖,我將在接下來的博客中介紹Retrofit晨雳。
dependencies {
compile project(':common')
compile 'com.squareup.retrofit:retrofit:1.9.0'
}
Presentation module##
就是Android應(yīng)用自身,包括resources
, assets
, 邏輯等等奸腺。
它與執(zhí)行usecase
的Domain進(jìn)行交互餐禁,比如可以用來獲取某一時(shí)段的電影列表,或者從某部電影中獲取特殊的數(shù)據(jù)突照。
這個(gè)模塊只包含Presenter和View帮非。
每一個(gè)Activity
,Fragment
讹蘑,Dialog
都實(shí)現(xiàn)MVPView
接口末盔,它指定了一些在View上進(jìn)行顯示,隱藏座慰,顯示信息等操作陨舱。
比如,PopularMoviesView
通過指定一些操作展示當(dāng)前電影列表版仔,然后MoviesActivity
實(shí)現(xiàn)它游盲。
public interface PopularMoviesView extends MVPView {
void showMovies (List<TvMovie> movieList);
void showLoading ();
void hideLoading ();
void showError (String error);
void hideError ();
}
MVP設(shè)計(jì)模式就是讓View變得盡可能的簡單误墓,由Presenter決定它們的行為。(譯者注:View層應(yīng)體現(xiàn)KISS原則益缎,感興趣的同學(xué)可以了解一下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);
}
...
}
這個(gè)usecase
通過Presenter調(diào)用谜慌,并且Presenter接收相應(yīng)結(jié)果,然后處理View上的表現(xiàn)莺奔。
通信##
對(duì)于這個(gè)項(xiàng)目欣范,我選擇了Message Bus(譯者注:消息總線)系統(tǒng),這個(gè)系統(tǒng)對(duì)于廣播事件令哟,或者在兩個(gè)組件之間建立通信是非常有用的熙卡,尤其特別適用于后者。
基本上励饵,通過Bus發(fā)送事件驳癌,對(duì)事件感興趣的類,需要訂閱Bus役听,才能消費(fèi)那個(gè)事件颓鲜。
適用這個(gè)系統(tǒng)可以降低模塊間的耦合度。
為了實(shí)現(xiàn)這個(gè)系統(tǒng)總線典予,我使用Square出品的Otto類庫甜滨。
我定義了兩個(gè)Bus,一個(gè)用來使usecase
和REST API進(jìn)行通信瘤袖,另一個(gè)用來發(fā)送事件至Presentation
層衣摩。
REST_BUS
使用任意線程處理事件,UI_BUS
使用默認(rèn)線程發(fā)送事件捂敌,這個(gè)線程就是主線程艾扮。
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;
}
}
這個(gè)類通過common module管理。因?yàn)樗械哪K都需要訪問它占婉,從而與Bus進(jìn)行交互泡嘴。
dependencies {
compile 'com.squareup:otto:1.3.5'
}
最后,想象一下這個(gè)場(chǎng)景逆济,當(dāng)用戶打開應(yīng)用酌予,顯示最受歡迎的電影。
當(dāng)View
調(diào)用onCreate()
方法時(shí)奖慌,Presenter訂閱UI_BUS
接收事件抛虫。當(dāng)onStop()
方法被調(diào)用的時(shí)候Presenter取消訂閱。Presenter運(yùn)行GetMoviesUseCase
這個(gè)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需要實(shí)現(xiàn)一個(gè)方法,這個(gè)方法所接受參數(shù)的數(shù)據(jù)類型必須與Bus發(fā)送的事件的數(shù)據(jù)類型一致涎劈,兵器必須使用注解:@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