點(diǎn)此進(jìn)入目錄:[干貨] 十天 教你從創(chuàng)意到上線APP
一、架構(gòu)設(shè)計的目的
通過設(shè)計使程序模塊化专筷,做到模塊內(nèi)部的高聚合和模塊之間的低耦合强霎。這樣做的好處是使得程序在開發(fā)的過程中,開發(fā)人員只需要專注于一點(diǎn)哼转,提高程序開發(fā)的效率,并且更容易進(jìn)行后續(xù)的測試以及定位問題槽华。但設(shè)計不能違背最初的目的壹蔓,對于不同量級的工程,具體架構(gòu)的實(shí)現(xiàn)方式必然是不同的猫态,切忌犯為了設(shè)計而設(shè)計庶溶,為了架構(gòu)而架構(gòu)的毛病。
下面我們詳細(xì)介紹下Android中常用的兩種架構(gòu)類型懂鸵,然后結(jié)合實(shí)例講解具體實(shí)現(xiàn)流程,最后根據(jù)業(yè)務(wù)場景選取合適我們的架構(gòu)模式行疏。
舉個簡單的例子:
一個Android App如果只有幾個Java文件匆光,那只需要做點(diǎn)模塊和層次的劃分就可以,引入框架或者架構(gòu)反而提高了工作量酿联,降低了生產(chǎn)力终息;
但如果當(dāng)前開發(fā)的App最終代碼量在10W行以上夺巩,本地需要進(jìn)行復(fù)雜操作,同時也需要考慮到與其余的Android開發(fā)者以及后臺開發(fā)人員之間的同步配合周崭,那就需要在架構(gòu)上進(jìn)行一些思考柳譬!
二、MVC設(shè)計架構(gòu)
1续镇、MVC簡介
MVC全名是Model View Controller美澳,如圖,是模型(model)-視圖(view)-控制器(controller)的縮寫摸航,一種軟件設(shè)計典范制跟,用一種業(yè)務(wù)邏輯、數(shù)據(jù)酱虎、界面顯示分離的方法組織代碼雨膨,在改進(jìn)和個性化定制界面及用戶交互的同時,不需要重新編寫業(yè)務(wù)邏輯读串。
其中M層處理數(shù)據(jù)聊记,業(yè)務(wù)邏輯等;V層處理界面的顯示結(jié)果恢暖;C層起到橋梁的作用排监,來控制V層和M層通信以此來達(dá)到分離視圖顯示和業(yè)務(wù)邏輯層。
2胀茵、Android中的MVC
視圖層(View)
一般采用XML文件進(jìn)行界面的描述社露,這些XML可以理解為AndroidApp的View。使用的時候可以非常方便的引入琼娘。同時便于后期界面的修改峭弟。邏輯中與界面對應(yīng)的id不變化則代碼不用修改,大大增強(qiáng)了代碼的可維護(hù)性脱拼。
控制層(Controller)
Android的控制層的重任通常落在了眾多的Activity的肩上瞒瘸。這句話也就暗含了不要在Activity中寫代碼,要通過Activity交割Model業(yè)務(wù)邏輯層處理熄浓,這樣做的另外一個原因是Android中的Actiivity的響應(yīng)時間是5s情臭,如果耗時的操作放在這里,程序就很容易被回收掉赌蔑。
模型層(Model)
我們針對業(yè)務(wù)模型俯在,建立的數(shù)據(jù)結(jié)構(gòu)和相關(guān)的類,就可以理解為AndroidApp的Model娃惯,Model是與View無關(guān)跷乐,而與業(yè)務(wù)相關(guān)的,比如:對數(shù)據(jù)庫的操作趾浅、對網(wǎng)絡(luò)等的操作都應(yīng)該在Model里面處理愕提,當(dāng)然對業(yè)務(wù)計算等操作也是必須放在的該層的馒稍。就是應(yīng)用程序中二進(jìn)制的數(shù)據(jù)。
3浅侨、MVC框架在Activity中的體現(xiàn)
我們這里以一個按鈕點(diǎn)擊后進(jìn)行網(wǎng)絡(luò)請求然后將請求數(shù)據(jù)同步更新到界面的Demo為例纽谒,向大家展示MVC在Android中具體的應(yīng)用場景和控制流程:
(1)Controller控制器&View:
public class MainActivity extends ActionBarActivity implements OnWeatherListener, View.OnClickListener {
private WeatherModel weatherModel;
private EditText cityNOInput;
private TextView city;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
weatherModel = new WeatherModelImpl();
initView();
}
// 初始化View
private void initView() {
cityNOInput = findView(R.id.et_city_no);
city = findView(R.id.tv_city);
findView(R.id.btn_go).setOnClickListener(this);
}
// 顯示結(jié)果
public void displayResult(Weather weather) {
WeatherInfo weatherInfo = weather.getWeatherinfo();
city.setText(weatherInfo.getCity());
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_go:
weatherModel.getWeather(cityNOInput.getText().toString().trim(), this);
break;
}
}
@Override
public void onSuccess(Weather weather) {
displayResult(weather);
}
@Override
public void onError() {
Toast.makeText(this, 獲取天氣信息失敗, Toast.LENGTH_SHORT).show();
}
private T findView(int id) {
return (T) findViewById(id);
}
}
從上面的代碼可以看到,Activity持有了WeatherModel模型的對象如输,當(dāng)用戶有點(diǎn)擊Button的時候鼓黔,Activity作為Controller控制層讀取View視圖層EditTextView的數(shù)據(jù)然后向Model模型發(fā)起數(shù)據(jù)請求,也就是調(diào)用WeatherModel對象的getWeather()方法挨决。當(dāng)Model模型處理數(shù)據(jù)結(jié)束后请祖,通過接口OnWeatherListener通知View視圖層數(shù)據(jù)處理,然后View視圖層調(diào)用displayResult()方法更新UI脖祈。至此肆捕,整個MVC框架流程就在Activity中體現(xiàn)出來了。
(2)WeatherModelImpl代碼實(shí)現(xiàn)&Model模型
public interface WeatherModel {
void getWeather(String cityNumber, OnWeatherListener listener);
}
public class WeatherModelImpl implements WeatherModel {
@Override
public void getWeather(String cityNumber, final OnWeatherListener listener) {
// 數(shù)據(jù)層操作
VolleyRequest.newInstance().newGsonRequest(http://www.weather.com.cn/data/sk/ + cityNumber + .html, Weather.class, new Response.Listener<weather>() {
@Override
public void onResponse(Weather weather) {
if (weather != null) {
listener.onSuccess(weather);
} else {
listener.onError();
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
listener.onError();
}
});
}
}
通過以上代碼看出盖高,這里設(shè)計了一個WeatherModel模型接口慎陵,然后實(shí)現(xiàn)了接口WeatherModelImpl類,Controller控制器Activity調(diào)用WeatherModelImpl類中的方法發(fā)起網(wǎng)絡(luò)請求喻奥,然后通過實(shí)現(xiàn)OnWeatherListener接口來獲得網(wǎng)絡(luò)請求的結(jié)果通知View視圖層更新UI席纽。至此,Activity就將View視圖顯示和Model模型數(shù)據(jù)處理隔離開了撞蚕。Activity擔(dān)當(dāng)Contronller完成了Model和View之間的協(xié)調(diào)作用润梯。
至于這里為什么不直接設(shè)計成類里面的一個getWeather()方法直接請求網(wǎng)絡(luò)數(shù)據(jù)呢?我們考慮下面這種情況:現(xiàn)在代碼中的網(wǎng)絡(luò)請求是使用Volley框架來實(shí)現(xiàn)的甥厦,如果哪天老板非要你使用Afinal框架實(shí)現(xiàn)網(wǎng)絡(luò)請求纺铭,問題要怎么解決呢?難道是修改getWeather()方法的實(shí)現(xiàn)嗎刀疙?這樣當(dāng)然不行舶赔,因為這樣修改不僅破壞了以前的代碼而且還不利于維護(hù)∏恚考慮到以后代碼的擴(kuò)展和維護(hù)性竟纳,我們選擇設(shè)計接口的方式來解決這個問題:我們實(shí)現(xiàn)另外一個WeatherModelWithAfinalImpl類,繼承自WeatherModel重寫里面的方法疚鲤,這樣不僅保留了以前的WeatherModelImpl類請求網(wǎng)絡(luò)方式锥累,還增加了WeatherModelWithAfinalImpl類的請求方式,這樣Activity調(diào)用代碼無需要任何修改集歇。
三揩悄、MVP設(shè)計架構(gòu)
1、MVP的誕生
在App開發(fā)過程中經(jīng)常出現(xiàn)的問題就是某一部分的代碼量過大鬼悠,雖然做了模塊劃分和接口隔離删性,但也很難完全避免,而且從實(shí)踐中看出焕窝,這更多的出現(xiàn)在UI部分蹬挺,也就是Activity里。不過Activity內(nèi)容過多的原因也很好解釋它掂,因為Activity本身需要擔(dān)負(fù)與用戶之間的操作交互巴帮、界面的展示,所以它不是單純的Controller或View虐秋,這就造成了Activity的臃腫榕茧。所以為了解決這個問題,讓我們引入MVP框架客给。
2用押、MVC的缺點(diǎn)
正如剛才所說,在Android開發(fā)中Activity并不是一個標(biāo)準(zhǔn)的MVC模式中的Controller靶剑,它的首要職責(zé)是加載應(yīng)用的布局和初始化用戶界面蜻拨,并接受和處理來自用戶的操作請求,進(jìn)而作出響應(yīng)桩引。隨著界面及其邏輯的復(fù)雜度不斷提升缎讼,Activity類的職責(zé)不斷增加,以致變得龐大臃腫坑匠,這便是MVC的缺點(diǎn)所在血崭。
3、什么是MVP厘灼?
MVP是從MVC框架演變過來的夹纫,當(dāng)然也就與MVC有一定的相似性:Controller/Presenter負(fù)責(zé)邏輯的處理,Model提供數(shù)據(jù)手幢,View負(fù)責(zé)顯示捷凄。
MVP框架由3部分組成:View負(fù)責(zé)顯示;Presenter負(fù)責(zé)邏輯處理围来;Model提供數(shù)據(jù)跺涤。在MVP模式里通常包含3個要素(加上View interface是4個):
- View:負(fù)責(zé)繪制UI元素、與用戶進(jìn)行交互(在Android中體現(xiàn)為Activity)监透;
- Model:負(fù)責(zé)存儲桶错、檢索、操縱數(shù)據(jù)(有時也實(shí)現(xiàn)一個Model interface用來降低耦合)胀蛮;
- Presenter:作為View與Model交互的中間紐帶院刁,處理與用戶交互的負(fù)責(zé)邏輯;
- View interface:需要View實(shí)現(xiàn)的接口粪狼,View通過View interface與Presenter進(jìn)行交互退腥,以此降低耦合并且方便進(jìn)行單元測試任岸;
Tips:View interface的必要性
回想一下你在開發(fā)Android應(yīng)用時是如何對代碼邏輯進(jìn)行單元測試的?是否每次都要將應(yīng)用部署到Android模擬器或真機(jī)上狡刘,然后通過模擬用戶操作進(jìn)行測試享潜?然而由于Android平臺的特性,每次部署都耗費(fèi)了大量的時間嗅蔬,這直接導(dǎo)致開發(fā)效率的降低剑按。而在MVP模式中,處理復(fù)雜邏輯的Presenter是通過interface與View(Activity)進(jìn)行交互的澜术,這說明我們可以通過自定義類實(shí)現(xiàn)這個interface來模擬Activity的行為對Presenter進(jìn)行單元測試艺蝴,省去了大量的部署及測試的時間。
4鸟废、MVC → MVP
當(dāng)我們將Activity復(fù)雜的邏輯處理移至另外的一個類(Presenter)中時猜敢,Activity其實(shí)就是MVP模式中的View,它負(fù)責(zé)UI元素的初始化侮攀,建立UI元素與Presenter的關(guān)聯(lián)(Listener之類)锣枝,同時自己也會處理一些簡單的邏輯(復(fù)雜的邏輯交由Presenter處理)。
MVP的Presenter是框架的控制者兰英,承擔(dān)了大量的邏輯操作撇叁,而MVC的Controller更多時候承擔(dān)一種轉(zhuǎn)發(fā)的作用。因此在App中引入MVP的原因畦贸,是為了將此前在Activty中包含的大量邏輯操作放到控制層中唆缴,避免Activity的臃腫奴饮。
因此我們可以發(fā)現(xiàn)MVP的優(yōu)點(diǎn)如下:
- 模型與視圖完全分離梁丘,我們可以修改視圖而不影響模型缝龄;
- 可以更高效地使用模型,因為所有的交互都發(fā)生在一個地方 —— Presenter內(nèi)部胶坠;
- 我們可以將一個Presenter用于多個視圖君账,而不需要改變Presenter的邏輯。這個特性非常的有用沈善,因為視圖的變化總是比模型的變化頻繁乡数;
- 如果我們把邏輯放在Presenter中,那么我們就可以脫離用戶接口來測試這些邏輯(單元測試)闻牡。
具體到Android App中净赴,一般可以將App根據(jù)程序的結(jié)構(gòu)進(jìn)行縱向劃分,根據(jù)MVP可以將App分別為模型層(M)罩润、UI層(V)和邏輯層(P)玖翅。UI層一般包括Activity、Fragment等直接和UI相關(guān)的類,UI層的Activity在啟動之后實(shí)例化相應(yīng)的Presenter金度,App的控制權(quán)后移应媚,由UI轉(zhuǎn)移到Presenter,兩者之間的通信通過BroadCast猜极、Handler或者接口完成珍特,只傳遞事件和結(jié)果。
舉個簡單的例子魔吐,UI層通知邏輯層(Presenter)用戶點(diǎn)擊了一個Button,邏輯層(Presenter)自己決定應(yīng)該用什么行為進(jìn)行響應(yīng)莱找,該找哪個模型(Model)去做這件事酬姆,最后邏輯層(Presenter)將完成的結(jié)果更新到UI層。
MVP的變種:Passive View
MVP的變種有很多奥溺,其中使用最廣泛的是Passive View模式辞色,即被動視圖。在這種模式下浮定,View和Model之間不能直接交互相满,而是通過Presenter與Model打交道。Presenter接受View的UI請求并完成簡單的UI處理邏輯并調(diào)用Model進(jìn)行業(yè)務(wù)處理桦卒,然后調(diào)用View將相應(yīng)的結(jié)果反映出來立美。View直接依賴Presenter,但是Presenter間接依賴View方灾,它直接依賴的是View實(shí)現(xiàn)的接口建蹄。
相對于View的被動,Presenter就是主動的一方:
- Presenter是整個MVP體系的控制中心裕偿,而不是單純的處理View請求的人洞慎;
- View僅僅是用戶交互請求的匯報者,對于響應(yīng)用戶交互相關(guān)的邏輯和流程嘿棘,View不參與決策劲腿,真正的決策者是Presenter;
- View向Presenter發(fā)送用戶交互請求應(yīng)該采用這樣的口吻:“我現(xiàn)在將用戶交互請求發(fā)送給你鸟妙,你看著辦焦人,需要我的時候我會協(xié)助你”,不應(yīng)該是這樣:“我現(xiàn)在處理用戶交互請求了圆仔,我知道該怎么辦垃瞧,但是我需要你的支持,因為實(shí)現(xiàn)業(yè)務(wù)邏輯的Model只信任你”坪郭;
- 對于綁定到View上的數(shù)據(jù)个从,不應(yīng)該是View從Presenter上“拉”回來的,應(yīng)該是Presenter主動“推”給View的;
- View盡可能不維護(hù)數(shù)據(jù)狀態(tài)嗦锐,因為其本身僅僅實(shí)現(xiàn)單純的嫌松、獨(dú)立的UI操作;
- Presenter才是整個體系的協(xié)調(diào)者奕污,它根據(jù)處理用于交互的邏輯給View和Model安排工作萎羔。
5、MVP架構(gòu)存在的問題與解決辦法
加入模板方法(Template Method)
轉(zhuǎn)移邏輯操作之后可能部分較為復(fù)雜的Activity內(nèi)代碼量還是不少碳默,于是需要在分層的基礎(chǔ)上再加入模板方法(Template Method)贾陷。
具體做法是在Activity內(nèi)部分層,其中最頂層為BaseActivity嘱根,不做具體顯示而是提供一些基礎(chǔ)樣式:Dialog髓废、ActionBar在內(nèi)的內(nèi)容等。展現(xiàn)給用戶的Activity繼承BaseActivity该抒,重寫B(tài)aseActivity預(yù)留的方法慌洪。如有必要再進(jìn)行二次繼承,App中Activity之間的繼承次數(shù)最多不超過3次凑保。
“愛閱”中采用的即是這樣的方法冈爹,加入模板方法不僅省去了很多重復(fù)的邏輯工作量,也更方便不同子視圖層的個性化定制欧引;Model內(nèi)部分層
模型層中的整體代碼量是最大的频伤,一般由大量的Package組成,針對這部分需要做的就是在程序設(shè)計的過程中维咸,做好模塊的劃分剂买,進(jìn)行接口隔離,在內(nèi)部進(jìn)行分層癌蓖。強(qiáng)化Presenter
強(qiáng)化Presenter的作用瞬哼,將所有邏輯操作都放在Presenter內(nèi)也容易造成Presenter內(nèi)的代碼量過大,對于這點(diǎn)有一個方法是在UI層和Presenter之間設(shè)置中介者M(jìn)ediator租副,將例如數(shù)據(jù)校驗坐慰、組裝在內(nèi)的輕量級邏輯操作放在Mediator中;在Presenter和Model之間使用代理Proxy用僧;通過上述兩者分擔(dān)一部分Presenter的邏輯操作结胀,但整體框架的控制權(quán)還是在Presenter手中。Mediator和Proxy不是必須的责循,只在Presenter負(fù)擔(dān)過大時才建議使用糟港。
四 、MVP代碼實(shí)例
接下來我們看看MVP在Android開發(fā)中是怎么應(yīng)用的院仿!
(1)建立Bean
public class UserBean {
private String mFirstName;
private String mLastName;
public UserBean(String firstName, String lastName) {
this. mFirstName = firstName;
this. mLastName = lastName;
}
public String getFirstName() {
return mFirstName;
}
public String getLastName() {
return mLastName;
}
}
(2)建立Model
public interface UserModel {
void setID(int id);
void setFirstName(String firstName);
void setLastName(String lastName);
int getID();
UserBean load(int id);// 通過id讀取user信息,返回一個UserBean
}
(3)Presenter控制器
建立presenter(通過iView和iModel接口操作Model和view)秸抚,Activity可以把所有邏輯給Presenter處理速和,這樣Java邏輯就從手機(jī)的Activity中分離出來。
public class UserPresenter {
private IUserView mUserView;
private IUserModel mUserModel;
public UserPresenter(IUserView view) {
mUserView = view;
mUserModel = new UserModel();
}
public void saveUser(int id, String firstName, String lastName) {
mUserModel.setID(id);
mUserModel.setFirstName(firstName);
mUserModel.setLastName(lastName);
}
public void loadUser(int id) {
UserBean user = mUserModel.load(id);
mUserView.setFirstName(user.getFirstName()); // 通過調(diào)用IUserView的方法來更新顯示
mUserView.setLastName(user.getLastName());
}
}
(4)View視圖
建立view(更新ui中的view狀態(tài))剥汤,這里列出需要操作當(dāng)前view的方法颠放,也是接口
public interface IUserView {
int getID();
String getFristName();
String getLastName();
void setFirstName(String firstName);
void setLastName(String lastName);
}
(5)MainActivity
public class MainActivity extends Activity implements OnClickListener, IUserView {
UserPresenter presenter;
EditText id, first, last;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout. activity_main);
findViewById(R.id. save).setOnClickListener( this);
findViewById(R.id. load).setOnClickListener( this);
id = (EditText) findViewById(R.id. id);
first = (EditText) findViewById(R.id. first);
last = (EditText) findViewById(R.id. last);
presenter = new UserPresenter( this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id. save:
presenter.saveUser(getID(), getFristName(), getLastName());
break;
case R.id. load:
presenter.loadUser(getID());
break;
default:
break;
}
}
@Override
public int getID() {
return new Integer( id.getText().toString());
}
@Override
public String getFristName() {
return first.getText().toString();
}
@Override
public String getLastName() {
return last.getText().toString();
}
@Override
public void setFirstName(String firstName) {
first.setText(firstName);
}
@Override
public void setLastName(String lastName) {
last.setText(lastName);
}
}
因此,Activity及從MVC中的Controller中解放出來了吭敢,這會Activity主要做顯示View的作用和用戶交互碰凶。每個Activity可以根據(jù)自己顯示View的不同實(shí)現(xiàn)View視圖接口IUserView。
五鹿驼、“愛閱”中的架構(gòu)設(shè)計
上面介紹了Android應(yīng)用中目前最火熱的兩種架構(gòu)模式欲低,那么“愛閱”中采用的架構(gòu)是什么呢?是MVC和MVP共存的架構(gòu)模式畜晰!大家可能有些奇怪伸头,難道MVC和MVP是可以共存的嗎?那是當(dāng)然的了舷蟀,只是我們在具體的業(yè)務(wù)場景中要根據(jù)不同的情況做出恰當(dāng)?shù)倪x擇就好。
比如在“愛閱”的整個架構(gòu)當(dāng)中面哼,MainActivity扮演了整個APP主要的業(yè)務(wù)角色野宜,在APP運(yùn)行的過程中,無論是側(cè)滑菜單的打開魔策、搜索界面的呈現(xiàn)匈子、收藏界面的展現(xiàn)等等,MainActivity都屬于常駐內(nèi)存的角色闯袒,所以對于MainActivity中的一些基本功能而言采用MVC模式最為合適虎敦,比如:左右兩側(cè)菜單欄的初始化、主界面ViewPager的更新和顯示等政敢。而在每個Fragment當(dāng)中其徙,選擇MVP架構(gòu)最為合適,因為涉及到了許多相似的界面設(shè)計(比如主頁面中的頁面點(diǎn)選和滑動等)喷户,這樣不僅減少了界面重復(fù)邏輯的代碼編寫唾那,而且把相同的業(yè)務(wù)邏輯處理抽取到Presenter中進(jìn)行處理后,更容易實(shí)現(xiàn)不同界面的個性化定制并減少模塊之間的耦合褪尝。
另外闹获,在MainActivity的設(shè)計中,對于一些長連接的邏輯處理同樣采用了MVP的架構(gòu)設(shè)計河哑。比如:對數(shù)據(jù)庫和網(wǎng)絡(luò)獲取的原始數(shù)據(jù)解析完成后避诽,我采用了EventBus對事件進(jìn)行分發(fā)并在MainActivity中進(jìn)行捕獲,這樣就實(shí)現(xiàn)了高度的業(yè)務(wù)邏輯解耦璃谨,即前面所說的 —— 數(shù)據(jù)不是從MainActivity中拉取沙庐,而是把數(shù)據(jù)推給MainActivity鲤妥;MainActivity只關(guān)心要什么、接受什么轨功,而不關(guān)心具體是怎么獲得的旭斥;MainActivity不是整個APP的業(yè)務(wù)邏輯承擔(dān)者,而僅僅輕量化的實(shí)現(xiàn)與用戶的UI交互功能古涧。
這樣垂券,我們就根據(jù)具體的業(yè)務(wù)場景做出了合理的架構(gòu)選擇和設(shè)計,在下一篇的課程當(dāng)中羡滑,我們就根據(jù)此架構(gòu)框架進(jìn)行主頁面的開發(fā)工作菇爪,See You!
聯(lián)系方式: