聲明:作者原創,轉載注明出處。
作者:帥氣陳吃蘋果
一、MVC
Model:模型,處理業務邏輯。
View:視圖,呈現用戶界面。
Controller:控制器,處理用戶交互。
(圖片來源:MVC圖片)
二、MVP
Model:模型,處理業務邏輯。
View:視圖,呈現用戶界面。
Presenter:中間者,負責調控View和Model之間的交互。
MVP是MVC模式經過改良演變而來,二者都是用來分離UI、數據、業務和UI邏輯和的軟件開發模式,controller/presenter負責交互的處理,model負責提供數據和邏輯處理,view負責顯示和接收數據。
區別是:MVP模式中,View和Model不直接進行交互,而是采用Presenter這個中間者,通過綁定View和Model的接口,進行間接的交互。而在MVC中,View和Model是可以直接進行通信的。
三、MVP For Android
架構的意義之一在于,讓應用程序提高可擴展性。
大部分的Android應用采用的都是如下的開發模式:
(圖片來源:《Amdrid MVP詳解(上)》)
Activity既承擔著View顯示用戶界面的任務,又包含了Controller處理業務邏輯的任務,因此Android中的MVC并不嚴格。
當項目規模大到一定程度,Activity就會像一個臃腫的胖子,行動不便。
行動不便體現在,當項目需求變更時,由于View和Model之間耦合度過高,導致代碼改動變得復雜而龐大,不利于項目的功能擴展,也不便于進行單元測試。
在Android中,UI是線程不安全的,也就是只能在MainThread中才能進行UI更新,所以對View和Model的分離是合理的。
四、示例
這個示例采用我上一篇博客《Bmob后端云初體驗》的Demo,使用Bmob后端云實現一個登陸注冊的例子。如果你感興趣可以點擊閱讀,當然,不讀也沒多大影響。
首先,先看一下項目結構:
1.MyUser.class
public class MyUser extends BmobObject {
//用戶名
private String userName;
//密碼
private String userPwd;
public MyUser() {
}
public MyUser(String name, String pwd) {
this.userName = name;
this.userPwd = pwd;
}
public String getUserName() {
return this.userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserPwd() {
return this.userPwd;
}
public void setUserPwd(String userPwd) {
this.userPwd = userPwd;
}
}
2.IUserView.class
對用戶輸入進行數據抽象,得到View的接口。
public interface IUserView {
/**
* 獲取用戶輸入的用戶名
*/
String getUserName();
/**
* 獲取用戶輸入的密碼
*/
String getUserPwd();
/**
* 加載進度對話框
*/
void showLoading();
/**
* 隱藏進度對話框
*/
void hideLoading();
}
3.IUserModel.class
對需要用到的數據進行抽象,得到Model的接口,通過回調的方式進行過程的判斷(在這里體現為,當用戶注冊或登陸時,可分為操作開始、操作成功、因用戶原因導致的操作失敗、因系統原因導致的操作失敗四個過程)。
public interface IUserModel {
/**
* 用戶登錄
* @param name
* @param pwd
* @param listener
*/
void checkUser(String name,String pwd,OnUserOperationListener listener);
/**
* 用戶注冊
* @param name
* @param pwd
* @param listener
*/
void registerUser(String name,String pwd,OnUserOperationListener listener);
interface OnUserOperationListener {
/**
* 操作開始
*/
void onOperationBegin();
/**
* 操作成功
*/
void onSuccess();
/**
* 因用戶原因,導致操作失敗
*/
void onUserFailed();
/**
* 因系統原因,導致操作失敗
*/
void onSysFailed();
}
}
4.IUserModelImpl.class
接著對Model接口進行實現,其中涉及到Bmob后端云的數據服務,可以看成是在這里進行業務邏輯的具體操作(包括網絡請求、后臺進程、數據加載等)。
public class IUserModelImpl implements IUserModel {
/**
* 由model進行具體的業務邏輯操作,檢查用戶名和密碼
* @param name
* @param pwd
*/
@Override
public void checkUser(String name, String pwd, final OnUserOperationListener listener) {
//開始檢查過程
listener.onOperationBegin();
BmobQuery<MyUser> userQuery = new BmobQuery<MyUser>();
userQuery.addWhereEqualTo("userName",name);
userQuery.addWhereEqualTo("userPwd",pwd);
userQuery.findObjects(new FindListener<MyUser>() {
@Override
public void done(List<MyUser> list, BmobException e) {
if(e == null) {
if(list.size() == 1) {
listener.onSuccess();
} else {
listener.onUserFailed();
}
} else {
listener.onSysFailed();
}
}
});
}
@Override
public void registerUser( String name, String pwd, final OnUserOperationListener listener) {
listener.onOperationBegin();
MyUser mUser = new MyUser();
mUser.setUserName(name);
mUser.setUserPwd(pwd);
mUser.save(new SaveListener<String>() {
@Override
public void done(String s, BmobException e) {
if(e == null) {;
listener.onSuccess();
} else {
listener.onSysFailed();
}
}
});
}
}
5.UserPresenter.class
然后創建一個中間者Presenter,持有View和Model的引用,通過對View和Model的綁定,將原本在View中的那些繁雜的操作指定給Model去實現,而不是View直接與Model進行交互。
public class UserPresenter extends BasePresenter<IUserView>{
//model
private IUserModel mUserModel;
//view
private IUserView mUserView;
/**
* 實例化view
* @param mUserview
*/
public UserPresenter(IUserView mUserview) {
super();
this.mUserModel = new IUserModelImpl();
this.mUserView = mUserview;
}
/**
* bind view and model for user login
*
* @param context 上下文環境
* @param name 用戶名
* @param pwd 密碼
*/
public void check(final Context context, String name, String pwd) {
//顯示進度對話框
mUserView.showLoading();
if(mUserModel != null) {
mUserModel.checkUser(name, pwd, new IUserModel.OnUserOperationListener() {
@Override
public void onOperationBegin() {
}
@Override
public void onSuccess() {
mUserView.hideLoading();
Toast.makeText(getApplicationContext(),
"登錄成功!",
Toast.LENGTH_SHORT).show();
Intent intent = new Intent(getApplicationContext(),MainActivity.class);
context.startActivity(intent);
}
@Override
public void onUserFailed() {
mUserView.hideLoading();
Toast.makeText(getApplicationContext(),
"用戶名或密碼錯誤,請重新輸入!",
Toast.LENGTH_SHORT).show();
}
@Override
public void onSysFailed() {
mUserView.hideLoading();
Toast.makeText(getApplicationContext(),
"登錄失敗,請檢查網絡設置!",
Toast.LENGTH_SHORT).show();
}
});
}
}
/**
* bind view and model for user register
*
* @param context
* @param name
* @param pwd
*/
public void register(final Context context,String name,String pwd) {
//顯示進度對話框
mUserView.showLoading();
if(mUserModel != null) {
mUserModel.registerUser(name, pwd, new IUserModel.OnUserOperationListener() {
@Override
public void onOperationBegin() {
}
@Override
public void onSuccess() {
mUserView.hideLoading();
Toast.makeText(getApplicationContext(),
"注冊成功!",
Toast.LENGTH_SHORT).show();
((Activity) context).finish();
}
@Override
public void onUserFailed() {
mUserView.hideLoading();
Toast.makeText(getApplicationContext(),
"用戶名或密碼不合法,請重新輸入!",
Toast.LENGTH_SHORT).show();
}
@Override
public void onSysFailed() {
mUserView.hideLoading();
Toast.makeText(getApplicationContext(),
"你可能長得太丑,網絡都看不下去了 ^_^ ",
Toast.LENGTH_SHORT).show();
}
});
}
}
}
6.LoginActivity.class
Activity就是View層中很典型的一個體現,所以要讓他實現抽象出來的View接口。在View層中,只與中間者Presenter進行交互。
public class LoginActivity extends BaseActivity<IUserView,UserPresenter> implements View.OnClickListener,IUserView{
private EditText editName;
private EditText editPwd;
private Button btnLogin;
private Button btnToRegister;
//進度對話框
ProgressDialog progressDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//初始化BmobSDK,
Bmob.initialize(this, "4fd01c1c4eaca3d97c85e36494554549");
setContentView(R.layout.activity_login);
initView();
}
/**
* 控件初始化
*/
private void initView() {
editName = (EditText) findViewById(R.id.edit_login_name);
editPwd = (EditText) findViewById(R.id.edit_login_pwd);
btnLogin = (Button) findViewById(R.id.btn_login);
btnToRegister = (Button) findViewById(R.id.btn_to_register);
btnLogin.setOnClickListener(this);
btnToRegister.setOnClickListener(this);
progressDialog = new ProgressDialog(this);
progressDialog.setTitle("登陸");
progressDialog.setMessage("正在登陸...");
progressDialog.setCancelable(false);
}
/**
* 重寫按鈕的點擊事件
* @param view
*/
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_login:
userLogin();
break;
case R.id.btn_to_register:
toRegister();
break;
default:
break;
}
}
/**
* 去注冊
*/
private void toRegister() {
Intent intentReg = new Intent(LoginActivity.this,RegisterActivity.class);
startActivity(intentReg);
}
/**
* 登陸
*/
private void userLogin() {
//初始化中間者
mPresenter = new UserPresenter(this);
//通過中間者進行用戶名和密碼的檢查
mPresenter.check(this,getUserName(),getUserPwd());
}
@Override
public String getUserName() {
return editName.getText().toString();
}
@Override
public String getUserPwd() {
return editPwd.getText().toString();
}
@Override
public void showLoading() {
progressDialog.show();
}
@Override
public void hideLoading() {
progressDialog.hide();
}
@Override
protected UserPresenter createPresenter() {
return new UserPresenter(this);
}
}
因為Presenter是用過View和Model的接口對View、Model進行訪問的,它持有他們的引用。
存在這樣一種情況,當Activity通過Presenter在Model進行業務邏輯的具體實現操作時,很可能這些操作是耗時的,假設有一個耗時長達5s的操作,而在這5s里,如果Activity被銷毀(用戶離開此界面),而Presenter還持有對View的引用,就會造成內存泄漏了。
如果不對這個問題進行處理,當一個應用有很多個Activity,假設一個Activity需要進行十個耗時操作,那么將嚴重降低應用的性能。
所以,需要讓Activity繼承一個父類BaseActivity,在這個BaseActivity中,當onCreate()方法執行,則關聯Presenter,當onDestroy()執行,則解除對Presenter的關聯,讓Presenter繼承一個BasePresenter,當系統內存不足時,優先釋放Model,而不是View,這樣用戶體驗才好。就好像一個愛臭美的人要被打時說,有事好商量,別打臉行么。當敵人來勢不洶,門面重要。
7.BaseActivity.class
public abstract class BaseActivity<V,T extends BasePresenter<V>> extends AppCompatActivity {
protected T mPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_base);
//創建Presenter
mPresenter = createPresenter();
//關聯View
mPresenter.attachView((V) this);
}
@Override
protected void onDestroy() {
super.onDestroy();
//解除關聯
mPresenter.detachView();
}
protected abstract T createPresenter();
}
8.BasePresenter.class
public abstract class BasePresenter<T> {
//當內存不足時,釋放內存
protected WeakReference<T> mViewRef;
/**
* bind view with presenter
* @param view
*/
public void attachView(T view) {
mViewRef = new WeakReference<T>(view);
}
public void detachView() {
if(mViewRef != null) {
mViewRef.clear();
mViewRef = null;
}
}
protected T getView() {
return mViewRef.get();
}
}
9.RegisterActivity.class
可以看到,在這里,我們的登錄界面和注冊界面所需要的數據時一樣的,那么,可以看出MVP的優勢之一:
當項目需求變動,如數據的展現方式不一樣而數據本身不存在變動時,我們只需要新建一個Activity或Fragment,在這個Activity或Fragment里同樣對Presenter進行關聯就可以了,而Presenter層和Model層的代碼都不需要變動,這就是可擴展性的體現。
public class RegisterActivity extends BaseActivity<IUserView,UserPresenter> implements IUserView{
private EditText editName;
private EditText editPwd;
private Button btnRegister;
private UserPresenter mUserPresenter;
//進度對話框
ProgressDialog progressDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_register);
initView();
}
/**
* 控件初始化
*/
private void initView() {
editName = (EditText) findViewById(R.id.edit_register_name);
editPwd = (EditText) findViewById(R.id.edit_register_pwd);
btnRegister = (Button) findViewById(R.id.btn_register);
btnRegister.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
userRegister();
}
});
progressDialog = new ProgressDialog(this);
progressDialog.setTitle("注冊");
progressDialog.setMessage("正在注冊...");
progressDialog.setCancelable(false);
}
/**
* 用戶注冊,即添加一行數據
*/
private void userRegister() {
mUserPresenter = new UserPresenter(this);
mUserPresenter.register(this,getUserName(),getUserPwd());
}
@Override
public String getUserName() {
return editName.getText().toString();
}
@Override
public String getUserPwd() {
return editPwd.getText().toString();
}
@Override
public void showLoading() {
progressDialog.show();
}
@Override
public void hideLoading() {
progressDialog.hide();
}
@Override
protected UserPresenter createPresenter() {
return new UserPresenter(this);
}
}
10.MainActivity.class
登錄之后的主界面就是顯示一段文本,沒什么特別的。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
總結:
MVP For Android對View層和Model實現了解耦,有利于提高應用的擴展性、健壯性,便于進行單元測試,但增加了很多代碼量。
除了MVP,還有MVVM有待學習。
不同的項目有不同的業務需求,要根據具體需求和項目規模進行開發模式的選擇。
源碼下載:Github下載
個人博客:帥氣陳吃蘋果