1. 概述
1.1 MVC中的三個角色
M:model晨川,數(shù)據(jù)的存取功能和實體模型
V:view画恰,對應(yīng)布局文件
C:controller彭谁,對應(yīng)Activity(因為view對應(yīng)布局文件,因此能做的事情很少允扇,會在Activity中進行事件處理缠局、數(shù)據(jù)綁定等则奥,使得Activity即像view又像controller,臃腫不堪)
1.2 MVP
出現(xiàn)原因
為了讓數(shù)據(jù)和視圖分離
MVP中的三個角色
M:model狭园,封裝數(shù)據(jù)存取操作(包括操作數(shù)據(jù)庫或通過網(wǎng)絡(luò)獲取數(shù)據(jù))和實體模型读处;
V:view,對應(yīng)Activity或Fragment或某個View控件唱矛。view中持有presenter的引用罚舱;
P:presenter,溝通model和view的橋梁绎谦。presenter中持有view和model的引用管闷;
MVP結(jié)構(gòu)圖
圖中說明:MVP中presenter層是view和model之間的橋梁,實現(xiàn)了數(shù)據(jù)和視圖的分離
1.3 MVC與MVP
- MVC是允許Model和View進行交互的窃肠,而MVP中Model和View之間的交互由Presenter完成包个,且Presenter和View之間的交互通過接口完成。
- 二者間最大的不同:Activity職責(zé)的變化冤留,由原來的C(控制層)變成了V(視圖層)碧囊,在MVP中控制層的角色由P來擔(dān)當(dāng)。這種架構(gòu)解決了Activity過度耦合控制層和視圖層的問題搀菩,實現(xiàn)了數(shù)據(jù)和視圖分離呕臂。
2. 使用
演示Login功能。
數(shù)據(jù)傳遞路徑
數(shù)據(jù)請求流程:當(dāng)view層某個界面需要展示數(shù)據(jù)時肪跋,首先會調(diào)用presenter層的引用——>presenter層會調(diào)用model層請求數(shù)據(jù)——>當(dāng)model層數(shù)據(jù)加載成功后會調(diào)用presenter層的回調(diào)方法通知presenter層數(shù)據(jù)加載情況——>presenter層再調(diào)用view層的接口將加載后的數(shù)據(jù)展示給用戶歧蒋。
面向接口編程
預(yù)期效果
當(dāng)用戶輸入賬號為aaa,密碼為123時跳轉(zhuǎn)到主頁面州既,其余情況給出錯誤提示谜洽。
開始寫代碼
- 從View開始寫起,當(dāng)View中需要操作數(shù)據(jù)時吴叶,調(diào)用presenter的相關(guān)方法阐虚。
點擊登錄按鈕時需要進行數(shù)據(jù)校驗,通過調(diào)用presenter中的方法進行數(shù)據(jù)操作
/**
* <pre>
* author : 楊麗金
* time : 2017/12/26
* desc : LoginView接口中說明了Presenter要求View提供的各個
* version: 1.0
* </pre>
*/
public class LoginActivity extends AppCompatActivity implements LoginView {
private EditText mEdtUsername;
private EditText mEdtPsw;
private Button mBtnLogin;
private ProgressBar mPbProgress;
// View(此處是Activity)中持有Presenter的引用
private LoginPresenter presenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initView();
setListener();
}
private void initView() {
mEdtUsername = (EditText) findViewById(R.id.edt_username);
mEdtPsw = (EditText) findViewById(R.id.edt_psw);
mBtnLogin = (Button) findViewById(R.id.btn_login);
mPbProgress = (ProgressBar) findViewById(R.id.pb_progress);
mPbProgress.setVisibility(View.GONE);
presenter = new LoginPresenterImpl(this);
}
private void setListener() {
mBtnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String username = mEdtUsername.getText().toString();
String psw = mEdtPsw.getText().toString();
User user = new User(username, psw);
// 此處需要操作數(shù)據(jù)蚌卤,activity不自己進行數(shù)據(jù)存取实束,而是調(diào)用presenter中的方法
presenter.validate(user);
}
});
}
}
- presenter再調(diào)用model中的相關(guān)方法;
/**
* <pre>
* author : 楊麗金
* time : 2017/12/26
* desc : LoginPresenter接口中說明了View中要求Presenter中提供的方法逊彭;
* OnLoginFinishedListener接口中說明了Model中要求Presenter中提供的方法
* version: 1.0
* </pre>
*/
public class LoginPresenterImpl implements LoginPresenter,OnLoginFinishedListener {
// presenter中持有view層的引用
private LoginView loginView;
// presenter中持有model層的引用先誉,LoginModel接口中指明了Presenter要求Model中提供的方法
private LoginModel loginModel;
// 在構(gòu)造函數(shù)中對loginview和loginModel進行初始化
public LoginPresenterImpl(LoginView loginView) {
this.loginView = loginView;
this.loginModel=new LoginModelImpl();
}
@Override
public void validate(User user) {
/*presenter是view和model之間的橋梁怎披,不進行什么實質(zhì)性的操作,
* 只是把要做的操作傳遞到view或Model中,view或Model才是操作的具體執(zhí)行者
*/
if (loginView != null) {
// 顯示進度條
loginView.showProgress();
}
loginModel.login(user,this);
}
}
- model加載數(shù)據(jù)完成后調(diào)用presenter中的回調(diào)方法返回數(shù)據(jù)加載完成情況
/**
* <pre>
* author : 楊麗金
* time : 2017/12/26
* desc :
* version: 1.0
* </pre>
*/
public class LoginModelImpl implements LoginModel {
@Override
public void login(final User user, final OnLoginFinishedListener listener) {
final String username = user.getName();
final String password = user.getPassword();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (TextUtils.isEmpty(username)) {
listener.onUsernameIsNull();//model層里面回調(diào)listener
return;
}
if (TextUtils.isEmpty(password)) {
listener.onPasswordIsNull();
return;
}
if ("aaa".equals(username) && "123".equals(password)) {
listener.onSuccess();
}else{
listener.onError();
}
}
}, 2000);
}
}
- presenter知道數(shù)據(jù)加載情況(加載成功或失敗或其他意外情況)后仁烹,調(diào)用view的接口指使view完成界面跳轉(zhuǎn)等操作
/**
* <pre>
* author : 楊麗金
* time : 2017/12/26
* desc : LoginPresenter接口中說明了View中要求Presenter中提供的方法;
* OnLoginFinishedListener接口中說明了Model中要求Presenter中提供的方法
* version: 1.0
* </pre>
*/
public class LoginPresenterImpl implements LoginPresenter,OnLoginFinishedListener {
@Override
public void onUsernameIsNull() {
if (loginView != null) {
loginView.setUsernameIsNull();
loginView.hideProgress();
}
}
@Override
public void onPasswordIsNull() {
if (loginView != null) {
loginView.setPswIsNull();
loginView.hideProgress();
}
}
@Override
public void onSuccess() {
if (loginView != null) {
loginView.moveToMainPage();
loginView.hideProgress();
}
}
@Override
public void onError() {
if (loginView != null) {
loginView.setUsernameOrPswError();
loginView.hideProgress();
}
}
}
注意
- mvp的寫法導(dǎo)致presenter中持有Activity引用,可能會造成內(nèi)存泄漏,故退出Activity時要將presenter中持有的Activity的引用置為null
/**
* <pre>
* author : 楊麗金
* time : 2017/12/26
* desc : LoginView接口中說明了Presenter要求View提供的各個
* version: 1.0
* </pre>
*/
public class LoginActivity extends AppCompatActivity implements LoginView {
@Override
protected void onDestroy() {
presenter.onDestroy();
super.onDestroy();
}
}
/**
* <pre>
* author : 楊麗金
* time : 2017/12/26
* desc : LoginPresenter接口中說明了View中要求Presenter中提供的方法亥宿;
* OnLoginFinishedListener接口中說明了Model中要求Presenter中提供的方法
* version: 1.0
* </pre>
*/
public class LoginPresenterImpl implements LoginPresenter,OnLoginFinishedListener {
@Override
public void onDestroy() {
loginView=null;
}
}
代碼地址
3. 參考文獻
一個優(yōu)秀的Android應(yīng)用從建項目開始
Android MVP 的簡單介紹與使用
LiveCircle
Android MVP架構(gòu)的自述