博文位址
我的GitHub | 我的部落格 | 我的微信 | 我的郵箱 |
---|---|---|---|
baiqiantao | bqt20094 | [email protected] |
目錄
- MVC 架構
- MVC各層的作用
- Android中的實際情況
- 示範案例
- BaseModel
- Callback
- SampleModel
- SampleActivity
- 案例總結
- MVP 架構
- 基本概念
- 特點
- MVP各層的作用
- 用MVP架構編寫登入子產品完整版
- 定義Presenter接口(可選)
- 定義Model接口(可選)及MP回調接口(必選)
- 定義View接口(必選)
- 定義Presenter的實作類
- 定義Model的實作類
- 讓Activity實作View接口
- 用MVP架構編寫登入子產品簡潔版
- View層接口
- Activity
- Presenter
- Model
- MVVM 架構
- 如何選擇

- M層:Model,SQL、XML、JSON,資料模型。負責與資料處理相關的業務邏輯的處理,比如資料庫讀寫操作,網絡請求操作,複雜的算法,耗時的任務等。Model是一個應用系統的核心部分,代表了該系統實際要實作的所有功能。當M層完成資料處理後,會通知Controller更新View。
- V層:View,XML布局、自定義View,Java編寫的View。負責在螢幕上渲染出相應的圖形資訊展示給使用者看。
- C層:Controlle,Activity或者Fragmentr,控制器。負責接收如點選、觸摸、電話呼入、網絡改變等外部事件,并向Model層發送資料請求。同時負責接收Model層處理完資料後發的通知,并更新View。Controller是View和Model之間通信的橋梁。
其實Android中隻有MV
Android下MVC中的控制層是由Activity來承擔的,Activity本來主要是作為初始化頁面,展示資料的操作,但是因為XML視圖功能太弱,是以Activity既要負責視圖的顯示又要加入控制邏輯,承擔的功能過多。
比如對于登入頁面,MVC的基本流程為:
使用者與View互動,View接收并回報使用者的動作,View把使用者的請求傳給相應的控制器,由控制器決定調用哪個模型,然後由模型調用相應的業務邏輯對使用者請求進行加工處理,如果需要傳回資料,模型會把相應的資料傳回給控制器,由控制器調用相應的視圖,最終由視圖渲染傳回的資料。
在Android開發中,Activity并不是一個标準的MVC模式中的Controller,它的首要職責是加載應用的布局和初始化使用者界面,接受并處理來自使用者的操作請求,進而作出響應。随着界面及其邏輯的複雜度不斷提升,Activity類的職責不斷增加,以緻變得龐大臃腫。
比如在Android中,對于登入頁面,典型的互動過程是這樣的:
使用者點選登入按鈕 → Activity中注冊的監聽器檢測到點選事件 → Activity通過轉動View中的ProgressBar來響應使用者點選事件 → Activity通過View中的EditText擷取使用者輸入的賬戶密碼 → Activity将資料交由業務邏輯層(Model層)處理 → Model層處理完成後通過回調将資料傳回給Activity → Activity更新UI回報給使用者
由上面的案例可以看出,其實這個View對應于布局檔案能做的事情特别少,實際上關于該布局檔案中的資料綁定、事件處理的代碼都在Activity中,造成了Activity既像View又像Controller,這可能也就是為何:
Most of the modern Android applications just use
View-Model
architecture,everything is connected with Activity.
BaseModel顧名思義就是所有業務邏輯model的父類,這裡的
onDestroy()
方法用于跟activity或者fragment生命周期同步,在destroy做一些銷毀操作
public interface BaseModel {
void onDestroy();
}
Callback是根據View或者Controller調用Model時回調的參數個數選擇使用
public interface Callback1<T> {
void onCallBack(T t);
}
public interface Callback2<T, P> {
void onCallBack(T t, P p);
}
SampleModel是我們業務邏輯的具體實作
public class SampleModel implements BaseModel {
public void getUserInfo(String uid, Callback1<UserInfo> callback) {
UserInfo userInfo = new UserInfo();
//...從網絡或資料庫等擷取資料
callback.onCallBack(userInfo);
}
public void getUserInfo2(String uid, Callback2<UserInfo, String> callback) {
UserInfo userInfo = new UserInfo();
//...從網絡或資料庫等擷取資料
callback.onCallBack(userInfo, "其他資料");
}
@Override
public void onDestroy() {
}
public class UserInfo {
private int age;
private String name;
//...
}
}
public class SampleActivity extends AppCompatActivity {
private SampleModel sampleModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sample);
sampleModel = new SampleModel();
String uid = "123456";
findViewById(R.id.button).setOnClickListener(view -> getUserInfo(uid));
}
@Override
protected void onDestroy() {
super.onDestroy();
sampleModel.onDestroy();
}
//擷取使用者資訊
private void getUserInfo(String uid) {
sampleModel.getUserInfo(uid, userInfo -> {
//...設定使用者資訊到view
});
}
}
事件的流向
- button點選事件的觸發:View → Controller
- 擷取使用者資訊事件的觸發:Controller → Model → Controller
- 綁定使用者資訊到View:Controller → View
總結
- 具有一定的分層,model徹底解耦,controller和view并沒有解耦
- 層與層之間的互動盡量使用回調或者去使用消息機制去完成,盡量避免直接持有
- controller和view在android中無法做到徹底分離,但在代碼邏輯層面一定要厘清
- 業務邏輯被放置在model層,能夠更好的複用和修改增加業務
MVP的核心就是:
讓M和V完全解耦,通過Presenter統一排程管理
MVP的基本流程:Presenter從View中擷取需要的參數,交給Model去處理,Model執行過程中的回報以及結果告訴Presenter,Presenter再讓View做對應的顯示。
MVP跟MVC很相像,根據MVC的發展來看,我們把MVP當成MVC來看也不為過,因為MVP也是三層,唯一的差别是
Model和View之間不進行通訊
,都是通過Presenter完成。
前面介紹MVC的時候提到了算是緻命缺點吧,在android中由于activity(god object)的存在,
Controller和View很難做到完全解耦
。但在MVP中就可以很好的解決這個問題
- 是MVC的演化版本
-
讓Model和View完全解耦,由Presenter負責完成View與Model的互動
-
,減少了Activity的職責,将Actvity視為View層
将複雜的邏輯代碼提取到了Presenter中
-
Presenter與View之間的互動完全是通過接口的
- 代碼很清晰,不過增加了很多類;耦合度更低,更友善的進行測試;有助于協同開發,降低維護成本
- Model:資料模型,和MVC中的Model一樣
- View:對應UI界面(包括Activity、Fragment、以及所有視圖),負責View的繪制以及與使用者互動
- Presenter:排程者,負責完成View和Model間的互動
分析這個子產品需要哪些業務邏輯,或者說有哪些複雜的功能,以此定義Presenter接口。
對于登入子產品,主要的就是登入功能。為了增加接口的複雜度,這裡我又添加了一個退出前清理功能。
public interface Login_Presenter_I {
void login(String username, String password);//登入過程可能涉及到很工作,是以把它抽出來。此過程需要與View互動
void onFinishActivity();//退出前可能要做很多清理工作,是以也把它抽出來。此過程不需要與View互動
}
分析上述Presenter層中的功能在被Model層處理過程中,Model需要通知Presenter哪些内容,以此定義Model接口。
Model層在執行過程中,是通過接口通知Presenter執行過程中的狀态以及執行完畢後的結果的,為了邏輯更清晰,建議此此接口定義為Model層接口的内部接口。
public interface Login_Model_I {
//參數 listener:Model層通過此接口通知Presenter執行過程中的狀态以及執行完畢後的結果
void login(String username, String password, OnLoginListener listener);//執行過程中需要通知Presenter
void clearBeforeFinishActivity(boolean clearSp, boolean deleteCache);//執行過程中不需要通知Presenter
//将Model層需要通知Presenter的内容,按照類别,定義在不同的接口中
interface OnLoginListener {
void onUsernameError();
void onPasswordError();
void onSuccess();
}
}
分析所有可能會操作UI的最基礎邏輯,以此定義View接口。
Presenter層是通過接口來操作View層的。
public interface Login_Activity_I {
void showProgress();
void hideProgress();
void setUsernameError();
void setPasswordError();
void showToast(String msg, int duration);
}
public class Login_Presenter_Impl implements Login_Presenter_I, Login_Model_I.OnLoginListener {
private Login_Activity_I loginActivityI; //拿到的是接口,整個Presenter中沒有導入任何View和Activity
private Login_Model_I loginIModel; //拿到的是Model層的實作類
public Login_Presenter_Impl(Login_Activity_I loginActivityI) {
this.loginActivityI = loginActivityI;//View層的實作類,由Activity傳過來
this.loginIModel = new Login_Model_Impl();//Model層的實作類,由Activity傳過來或自己建立均可
}
@Override
public void login(String username, String password) {
if (loginActivityI != null) loginActivityI.showProgress();//通知View更新UI
loginIModel.login(username, password, this);//交給Model層處理
}
@Override
public void onFinishActivity() {
if (loginActivityI != null) loginActivityI.showProgress();//通知View更新UI
boolean clearSp = new Random().nextBoolean();
boolean deleteCache = new Random().nextBoolean();
loginIModel.clearBeforeFinishActivity(clearSp, deleteCache);//交給Model層處理
if (loginActivityI != null) loginActivityI.hideProgress();//通知View更新UI
}
@Override
public void onUsernameError() {
if (loginActivityI != null) {
loginActivityI.setUsernameError();
loginActivityI.hideProgress();
}
}
@Override
public void onPasswordError() {
if (loginActivityI != null) {
loginActivityI.setPasswordError();
loginActivityI.hideProgress();
}
}
@Override
public void onSuccess() {
if (loginActivityI != null) loginActivityI.showToast("登入成功", Toast.LENGTH_SHORT);
}
}
Model層的實作類,隻處理邏輯,完全不操作View,對邏輯處理的狀态回報給Presenter
public class Login_Model_Impl implements Login_Model_I {
@Override
public void login(String username, String password, OnLoginListener listener) {
if (TextUtils.isEmpty(username)) listener.onUsernameError();
else if (TextUtils.isEmpty(password)) listener.onPasswordError();
else listener.onSuccess();
}
@Override
public void clearBeforeFinishActivity(boolean clearSp, boolean deleteCache) {
if (clearSp) Log.i("bqt", "【清理SP】");
if (deleteCache) Log.i("bqt", "【清理緩存】");
}
}
public class Login_Activity extends Activity implements Login_Activity_I, View.OnClickListener {
private ProgressBar progressBar;
private EditText username;
private EditText password;
private Login_Presenter_I presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
progressBar = (ProgressBar) findViewById(R.id.progress);
username = (EditText) findViewById(R.id.username);
password = (EditText) findViewById(R.id.password);
findViewById(R.id.button).setOnClickListener(this);
presenter = new Login_Presenter_Impl(this);//new一個Presenter的實作類,把自己傳過去。實際上接收的隻是LoginView接口的執行個體
}
@Override
public void finish() {
presenter.onFinishActivity();
super.finish();
}
@Override
public void showProgress() {
progressBar.setVisibility(View.VISIBLE);
}
@Override
public void hideProgress() {
progressBar.setVisibility(View.GONE);
}
@Override
public void setUsernameError() {
username.setError(getString(R.string.username_error));
}
@Override
public void setPasswordError() {
password.setError(getString(R.string.password_error));
}
@Override
public void showToast(String msg, int duration) {
Toast.makeText(this, msg, duration).show();
}
@Override
public void onClick(View v) {
//點選登入時,View把資料傳給presenter,presenter處理完資料後通知View處理事件
presenter.login(username.getText().toString(), password.getText().toString());
}
}
public interface LoginView {
void showProgress();
void hideProgress();
void setUsernameError();
void setPasswordError();
void navigateToHome();
}
public class LoginActivity extends AppCompatActivity implements LoginView {
private ProgressBar progressBar;
private EditText username;
private EditText password;
private LoginPresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
progressBar = findViewById(R.id.progress);
username = findViewById(R.id.username);
password = findViewById(R.id.password);
findViewById(R.id.button).setOnClickListener(v -> login());
presenter = new LoginPresenter(this, new LoginModel());
}
@Override
protected void onDestroy() {
presenter.onDestroy();
super.onDestroy();
}
@Override
public void showProgress() {
progressBar.setVisibility(View.VISIBLE);
}
@Override
public void hideProgress() {
progressBar.setVisibility(View.GONE);
}
@Override
public void setUsernameError() {
username.setError("使用者名錯誤");
}
@Override
public void setPasswordError() {
password.setError("密碼錯誤");
}
@Override
public void navigateToHome() {
startActivity(new Intent(this, MainActivity.class));
finish();
}
private void login() {
presenter.login(username.getText().toString(), password.getText().toString());
}
}
public class LoginPresenter implements LoginModel.OnLoginFinishedListener {
private LoginView LoginView;
private LoginModel loginModel;
LoginPresenter(LoginView LoginView, LoginModel loginModel) {
this.LoginView = LoginView;
this.loginModel = loginModel;
}
public void login(String username, String password) {
if (LoginView != null) {
LoginView.showProgress();
}
loginModel.login(username, password, this);
}
public void onDestroy() {
LoginView = null;
}
@Override
public void onUsernameError() {
if (LoginView != null) {
LoginView.setUsernameError();
LoginView.hideProgress();
}
}
@Override
public void onPasswordError() {
if (LoginView != null) {
LoginView.setPasswordError();
LoginView.hideProgress();
}
}
@Override
public void onSuccess() {
if (LoginView != null) {
LoginView.navigateToHome();
}
}
}
public class LoginModel {
interface OnLoginFinishedListener {
void onUsernameError();
void onPasswordError();
void onSuccess();
}
public void login(final String username, final String password, final OnLoginFinishedListener listener) {
new Handler().postDelayed(() -> {
if (TextUtils.isEmpty(username)) {
listener.onUsernameError();
return;
}
if (TextUtils.isEmpty(password)) {
listener.onPasswordError();
return;
}
listener.onSuccess();
}, 2000);
}
}
MVP中,随着業務邏輯的增加,UI的改變多的情況下,會有非常多的跟UI相關的case,這樣就會造成View的接口會很龐大。而MVVM就解決了這個問題,通過
雙向綁定
的機制,實作資料和UI内容,隻要想改其中一方,另一方都能夠及時更新的一種設計理念,這樣就省去了很多在View層中寫很多case的情況,隻需要改變資料就行。
先看下MVVM設計圖:
這看起來跟MVP好像沒啥差别,其實差別還是挺大的,在MVP中,View和presenter要互相持有,友善調用對方,而在MVP中,View和ViewModel通過Binding進行關聯,他們之前的關聯處理通過
DataBinding
完成。
MVVM與DataBinding的關系用一句話表述就是,
MVVM是一種思想,DataBinding是谷歌推出的友善實作MVVM的工具
。
在google推出DataBinding之前,因為xml layout功能較弱,想實作MVVM非常困難。而DataBinding的出現可以讓我們很友善的實作MVVM。
看起來MVVM很好的解決了MVC和MVP的不足,但是由于
資料和視圖的雙向綁定,導緻出現問題時不太好定位來源
,有可能資料問題導緻,也有可能業務邏輯中對視圖屬性的修改導緻。如果項目中打算用MVVM的話可以考慮使用官方的架構元件
ViewModel、LiveData、DataBinding
去實作MVVM。
關于ViewModel、LiveData、DataBindin這些類或架構的使用,因為涉及到的内容比較多,這裡不詳細介紹。
前面在介紹MVC、MVP、MVVM時并沒有去詳細列出他們的優缺點,主要原因是:關于架構,設計,子產品化等等,它們的優缺點沒有絕對的,主要看實作者如何去做
比如在mvp中我們要實作根據業務邏輯和頁面邏輯做很多Present和View的具體實作,如果這些case太多,會導緻代碼的可讀性變差。但是通過引入contract契約類,會讓業務邏輯變得清晰許多。是以不管是用哪種設計模式,隻要運用得當,都可以達到想要的結果。
如果非要說怎麼選的話,一般的建議如下:
- 如果
,沒什麼複雜性,未來改動也不大的話,那就不要用設計模式或者架構方法,隻需要将每個子產品封裝好,友善調用即可,不要為了使用設計模式或架構方法而使用。項目簡單
- 對于偏向
的app,絕大多數業務邏輯都在後端,app主要功能就是展示資料,互動等,建議使用展示型
mvvm
- 對于工具類或者需要寫很多
app,使用mvp或者mvvm都可。業務邏輯
- 如果想通過一個項目去學習架構和設計模式,建議用
然後在此基礎上慢慢挖掘改進。最後你可能發現,改進的最終結果可能就變成了mvp,mvvm。MVC
2019-5-9
本文來自部落格園,作者:白乾濤,轉載請注明原文連結:https://www.cnblogs.com/baiqiantao/p/10840064.html