Android專案的MVC與MVP
技術標籤:android基本知識mvcmvpAndroid開發android
目錄
做了Android開發很長時間了,從畢業就一直從事Android開發。現在在翻看自己以前專案中的程式碼,發現自己以前想法寫到程式碼不是一般的爛。最近也在做一些技術沉澱,發現需要學習的東西還有好多,每天覺得時間飛快。最近也是將公司專案架構調整了一下。以前專案也就是簡單的MVC架構模式,當公司業務越來越複雜的時候,發現這種分層方式的弊端越來越明顯,Activity中堆積了大量的程式碼,業務複雜的Activity,程式碼都有幾千行。程式碼可讀性極差,後期維護性差,想想如果哪天產品要對頁面UI進行升級的話,其實改造成本還蠻高的,Activity程式碼邏輯複雜,可重用性不高,所以針對這種現象,將公司專案架構做了調整。
一 MVC
1.概念
在Android開發的介面顯示,通常在開發過程中都會將網路層請求封裝成可以之間呼叫的方法供Activity直接呼叫,其實就可以看作是一個簡單的MVC架構模式。簡單的與MVC的三層進行對比下
View(檢視)層:主要就是佈局檔案或者使用java程式碼實現的自定義view;
Model(模型)層:主要就是渲染UI時使用的資料model、網路請求、資料庫增刪改查、IO等操作封裝對應的相關模組;
Controller(控制)層:主要Activity承擔該角色,用來初始化Model層,初始化、載入View層,從而控制View層和Model層。當用戶在Activity通過View層觸發事件的時候,Activity(Controller層)就會呼叫Model層的相關程式碼來獲取渲染UI使用的資料,從而達到改變UI,完成使用者的互動。
其實在Android中,由於佈局檔案無法進行一些邏輯和互動,而Activity去承擔了View的一些UI渲染和互動的工作,所以在Android中MVC更像是Controller和Model之間的一個數據流向。
通常會將Model層設計成ModelInteface的方式,不對Controller層(Activity)提供具體的實現,這樣可以提高Model層的程式碼的擴充套件和維護性。舉個例子Model層通常用來網路請求,說不定哪天就會更新網路請求的具體實現方式。那麼如果通過ModelInteface的方式,那麼我們只需要在最新的網路請求方式上去實現ModelInteface,而不需要更改Controller層的程式碼。
2.例項
舉個程式碼例項:例如有一個登入的介面,使用者輸入賬號和密碼就可以完成登入,返回使用者的資訊更新到UI介面上(PS:例項程式碼中都是模擬網路請求過程,僅僅用來說明MVC的一種程式碼實現方式)。
public class MvcCacheBean implements IViewCacheBean {
public User user;
}
(1)Model層
- 定義ModelInterface介面,供Controller層呼叫。
public interface IMvcModelInterface {
/**
* 登入
*
* @param account
* @param password
* @param result 伺服器處理完資料返回的結果
*/
void login(String account, String password, IHttpResult result);
}
- 定義MvcCacheBean,用來接收伺服器返回的資料
public class MvcCacheBean implements IViewCacheBean {
public User user;
}
- 實現ModelInterface介面,完成網路請求。
其中的Thread與Handler僅僅用來模擬網路請求的過程,在實際專案中肯定對應這相應封裝的網路請求模組對應的API。
public class MvcModelImpl implements IMvcModelInterface {
private Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
//僅僅簡單的做個邏輯判斷,只是用來返回成功和失敗的回撥
int arg1 = msg.arg1;
IHttpResult result = (IHttpResult) msg.obj;
if (arg1 == 1) {
User user = new User();
user.name = "小劉";
user.sex = "女";
user.age = "34";
MvcCacheBean cacheBean = new MvcCacheBean();
cacheBean.user = user;
result.success(cacheBean);
} else {
result.failure("登入失敗\n賬號輸入數字即可驗證成功的邏輯");
}
break;
}
}
};
@Override
public void login(String account, String password, IHttpResult result) {
//模擬具體的網路請求方式的實現:子執行緒去請求資料,資料處理完之後返回的UI執行緒
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message msg = new Message();
msg.what = 1;
msg.obj = result;
msg.arg1 = TextUtils.isDigitsOnly(account) ? 1 : 2;
handler.sendMessage(msg);
}
}).start();
}
}
(2)Controller層(包括View層)
在Activity中載入佈局檔案,例項化控制元件,並且例項化Model層。當用戶填寫賬號和密碼之後,點選Button,去完成網路請求,返回使用者資訊。
public class MvcActivity extends Activity {
private IMvcModelInterface model;
private TextView tvUserInfo;
private EditText etAccount;
private EditText etPassword;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvc);
//初始化View
initWidget();
//初始化Model
model = new MvcModelImpl();
}
private void initWidget() {
tvUserInfo = findViewById(R.id.tv_user_info);
etAccount = findViewById(R.id.et_account);
etPassword = findViewById(R.id.et_password);
}
/**
* 登入button的點選事件
*
* @param view
*/
public void btnLogin(View view) {
tvUserInfo.setText("稍等1s之後就可以看到模擬的結果");
tvUserInfo.setTextColor(Color.BLACK);
String account = etAccount.getText().toString();
String password = etPassword.getText().toString();
if (TextUtils.isEmpty(account) || TextUtils.isEmpty(password)) {
Toast.makeText(MvcActivity.this, "賬號和密碼不能為空", Toast.LENGTH_SHORT).show();
return;
}
//呼叫Model層進行網路請求
model.login(account, password, new IHttpResult() {
@Override
public void success(IViewCacheBean cacheBean) {
MvcCacheBean mvcCacheBean = (MvcCacheBean) cacheBean;
User user = mvcCacheBean.user;
tvUserInfo.setText(String.format("使用者登入成功\n使用者名稱:%s\n性別:%s\n年齡:%s", user.name, user.sex, user.age));
tvUserInfo.setTextColor(Color.GREEN);
}
@Override
public void failure(String error) {
tvUserInfo.setText(error);
tvUserInfo.setTextColor(Color.RED);
}
});
}
}
從程式碼例項中分析事件流向:Activity負責初始化Model層和View層(Controller層控制Model層和View層),當用戶觸發Button的點選事件(View層產生事件),Activity會呼叫Model層的相關程式碼(Controller層向Model層發出請求),當Model層完成業務邏輯,會回撥給Activity的成功和失敗的結果(Model層處理完資料後,向Controller層通知事件處理完),Activity更新TextiVew的顯示(Controller層通知View層處理事件),這樣就完成了一個MVC的事件流向的過程。
3.MVC總結
(1)具有一定分層,Model層徹底解耦,但是Controller層和View層卻無法解耦
(2)層與層之間儘量使用回撥、訊息機制,避免直接持有
(3)在Android中的Activity承擔了太多的工作,既有Controller層功能,也要承擔View層的功能,所以當UI介面越來越複雜,功能越來越多的時候,造成Activity的程式碼臃腫。
二 MVP
1.概念
在Android開發中正因為Activity承擔了太多的工作,既要初始化UI,還要去處理使用者的互動,並不能單純的去做Controller,造成Activity程式碼臃腫,所以就演變除了MVP。
View(檢視)層:主要就是佈局檔案或者使用java程式碼實現的自定義view,更準確的說其實是Activity和佈局檔案共同來承擔該角色,用來初始化UI和UI互動的工作;
Model(模型)層:和MVC中的Model層功能一致,仍然是渲染UI時使用的資料model、網路請求、資料庫增刪改查、IO等操作封裝對應的相關模組;
Presenter層:作為View層和Model層的中間紐帶,用來出來使用者互動邏輯,通常持有的是View 的ViewInterface,Presenter通過ViewInterface來處理View層的互動,降低與View層的耦合,更可以不依賴於View,單獨對Presenter進行單元測試。
ViewInterface:就是View要實現的介面類。該介面類主要工作就是View的互動事件,我們可以通過該介面類,將Presenter不依賴於View層程式碼。
另外我覺得可以在增加一個Presenter層增加一個PresenterInterface,這樣可以規範Presenter程式碼,在後面提到的程式碼生成工具中會有比較好的作用。
那麼MVP的三層之間的關係就變成了下圖的事件流向:
在MVP中,我們將之前MVC中Activity中的一部分邏輯轉移到Presenter中,Activity只需要去初始化UI和完成控制元件本身的事件監聽,當需要邏輯處理的時候,直接呼叫Presenter層進行處理,而Presenter層直接去呼叫Model層的程式碼去完成資料請求;當Model層處理完資料之後,再有Presenter層來通知View層更新UI。現在我們將Activity的控制權交到了Presenter層,而Activity更多的用來轉發請求。
2.例項
仍然通過上面的一個例項簡單的說明下MVP的一個架構情況。其實我們從第一部分的概念描述中可以看到,其實MVP和MVC的View層和Model層的功能是一樣的,為了主要突出他們有區別的地方,所以在MVP中仍然採用MVC的Model層和View層的程式碼。
(1)Model層
同MVC中的Model層
(2)View層
這次Activity和佈局檔案同時承擔View層的工作。
- ViewInterface將View層和Presenter層進行解耦
在介紹Presenter層程式碼的時候,會發現Presenter層持有的只有該ViewInterface,可以使得Presenter更加靈活。
public interface IMvpViewInterface {
/**
* 介面登入成功回撥
* @param cacheBean
*/
void loginSuccess(MvcCacheBean cacheBean);
/**
* 介面登入失敗的回撥
* @param error
*/
void loginFailure(String error);
}
- 在Activity中進行例項化View和簡單的響應View的監聽事件
public class MvpActivity extends Activity implements IMvpViewInterface {
private MvpPresenter presenter;
private TextView tvUserInfo;
private EditText etAccount;
private EditText etPassword;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvc);
//初始化View
initWidget();
//初始化Presenter
presenter = new MvpPresenter(MvpActivity.this);
}
private void initWidget() {
tvUserInfo = findViewById(R.id.tv_user_info);
etAccount = findViewById(R.id.et_account);
etPassword = findViewById(R.id.et_password);
}
public void btnLogin(View view) {
tvUserInfo.setText("稍等1s之後就可以看到模擬的結果");
tvUserInfo.setTextColor(Color.BLACK);
String account = etAccount.getText().toString();
String password = etPassword.getText().toString();
//View不需要關係業務邏輯,只需到時候在回撥的方法裡面填寫程式碼即可
presenter.login(account, password);
}
/**
* 只需要在成功和失敗回撥之後處理UI,使程式碼更加簡潔
*
* @param cacheBean
*/
@Override
public void loginSuccess(MvcCacheBean cacheBean) {
MvcCacheBean mvpCacheBean = (MvcCacheBean) cacheBean;
User user = mvpCacheBean.user;
tvUserInfo.setText(String.format("使用者登入成功\n使用者名稱:%s\n性別:%s\n年齡:%s", user.name, user.sex, user.age));
tvUserInfo.setTextColor(Color.GREEN);
}
@Override
public void loginFailure(String error) {
tvUserInfo.setText(error);
tvUserInfo.setTextColor(Color.RED);
}
}
(3)Presenter層
- PresenterInterface:使得Presenter層的程式碼更加簡潔,便於程式碼規範化。
因為我們專案現在頁面大部分程式碼是可以直接通過自研的工具生成,通過介面規範化Android和iOS兩端的程式碼,那麼開發人員可以只需要關注View層程式碼邏輯即可。並且可以讓一個人同時開發兩端程式碼,因為差別在於View層的部分邏輯的差異。
public interface IMvpPresenterInterface {
void login(String account, String password);
}
- Presenter實現類實現去呼叫Model層,並完成與View層的回撥
在Presenter中僅僅持有ViewInterface,那麼一個Presenter可以應用在多個View,例如專案中肯定有一些功能是可以用在不同頁面的,那麼就可以把這些功能放到一個Presenter中,只需要View實現對應的ViewInterface,那麼該功能就可以直接在View中使用。
public class MvpPresenter implements IMvpPresenterInterface {
private IMvpViewInterface mvpViewInterface;
private IMvcModelInterface model;
private Context context;
public MvpPresenter(IMvpViewInterface viewInterface) {
this.mvpViewInterface = viewInterface;
this.context = (Context) viewInterface;
//例項化Model
model = new MvcModelImpl();
}
@Override
public void login(String account, String password) {
//部分UI邏輯可以轉移到MvpPresenter,來減輕Activity的負擔
if (TextUtils.isEmpty(account) || TextUtils.isEmpty(password)) {
Toast.makeText(context, "賬號和密碼不能為空", Toast.LENGTH_SHORT).show();
return;
}
model.login(account, password, new IHttpResult() {
@Override
public void success(IViewCacheBean cacheBean) {
mvpViewInterface.loginSuccess((MvcCacheBean) cacheBean);
}
@Override
public void failure(String error) {
mvpViewInterface.loginFailure(error);
}
});
}
}
從程式碼中來分析事件的流向:Activity負責初始化View(View層),同樣還是使用者點選了這個Button,就會呼叫到Presenter層相關程式碼,而Presenter層接到請求之後,會呼叫Model層的程式碼來完成資料請求,Model層響應資料之後,Presenter層又會呼叫到View層的相應的方法完成回撥。
現在Activity(View層)僅僅就是View的展示以及View的互動,Activity(View層)不在關心處理的邏輯過程是什麼,只需要去做初始化,事件的監聽,在對應的回撥方法中處理UI顯示就可以了,Activity(View層)責任大大減少;
而現在Presenter是整個控制中心,不僅要去決定請求Model層的哪個程式碼,並且Model層返回資料之後,Prensenter還要決定回撥View層的哪個方法,Activity(View層)和Model層不需要做什麼,Presenter會安排Activity(View層)和Model層要做什麼。
Activity(View層)僅僅就是單獨的UI操作,而Presenter來處理互動邏輯,並負責協調Model層和View層。與上面MVC的程式碼對比,Activity程式碼更加簡潔。
3.總結
(1)MVP優點
- 模型和檢視完全分離,兩者互相修改不會受影響;
- 一個Presenter可以應用在多個檢視,而不需要改變Presenter的邏輯,同樣一個View也可以擁有多個Presenter來解決View複雜的頁面功能;
- 檢視與Presenter解耦,可以脫離檢視,對Presenter進行單元測試;
- Presenter是整個架構的控制者,模型和檢視不用關心怎麼做,Presenter會告訴該怎麼做。
(2)與MVC的對比
- 在MVC中,Activity是控制者,負責去排程View層和Model層;而MVP中,Presenter是控制者,Activity層僅僅負責轉發;
- 在MVC中,View層和Model層可以直接互動;而在MVP中,View層和Model層不能直接互動,只有通過Presenter層;
- 在MVC中,Activity中承擔Controller層和View層功能,Controller層和View層互相耦合;而在MVP中Presenter層作為控制者,以及和View層完全解耦,可以不依賴於View層進行單元測試。
三 總結
MVVM是對MVP的再一次升級,其中VM為ViewModel,可以理解為View的資料模型和Presenter的合體。我覺得這樣可以拿著微信小程式的開發方式考慮一下。一個頁面對應著四個檔案:.js檔案:主要負責業務邏輯;.wxml:負責頁面;.wxss:負責頁面樣式,修飾頁面;.json:負責頁面的一些簡單配置。我覺得裡面就有一個很意思的東西,我們通常都是在.js中定義一個變數,wxml檔案中引用該變數,當我們在js中修改該變數的值的時候,wxml檔案中自動反饋出該變化,我覺得MVVM應該差不多就是這個意思。
對比下三者之間的異同點:
相同點:三者對應的Model層和View層的功能是相同的。View層就是UI以及一些相關的介面邏輯程式碼;Model層就是資料物件和對網路請求、資料庫等操作。
不同點:怎麼將View層和Model層進行通訊
(1)MVC:通過Controller層,負責對View層進行初始化和對Model層的操作,Model層的資料變更並不通過Controller層,而是直接通過回撥通知View層。
(2)MVP:通過Presenter層來接收View層變化,並決定操作那個Model;而Model資料處理完之後,Presenter層又會去決定操作哪個View來完成整個事件。
(3)MVVM:VM中不僅僅包含View的一些資料屬性還包含一些操作,就是微信小程式的wxml檔案和js檔案,View層的變化會直接影響到ViewModel層,同樣ViewModel層的變化也直接反饋到View層。
因為專案最終採用了MVP的架構方式,並且基於這種架構方式,制定了一套開發規範,自研了一套開發工具,可以生成Android和iOS專案使用的Model層、View層以及Presenter層的程式碼,那麼作為開發人員只需要關注View層程式碼,並且可以同時開發兩端的程式碼,因為差別也就是在View層。
最後本文中的程式碼已經上傳到github,可以自行下載去執行:https://github.com/wenjing-bonnie/MvcMvp.git