有关MVC架构的详见:
Android找工作系列之MVC模式
MVP架构主要是要解决什么问题呢?主要是为了解决MVC架构中C的冗余问题,简单的说就是在Android中解决Activity的这个Controller层太重的问题,换句话说就是代码太多的问题。
在MVC架构中,Model层和View层的交互发生在Controller中,即是发生在Activity中,Activity即要从View中获取事件,又要反馈事件,那么Model层和View层就和Activity耦合程度过多,而且代码臃肿。为了解决这个问题,引入了Presenter层,那么Activity做什么用呢?Activity此时反而单纯的充当View层,只View层的工作:即使提供View数据,更新View数据等只和View层相关的工作。
详见代码解释:
用户登录MVC做法:
public class RegisterActivity extends AppCompatActivity { private EditText mUsername; private EditText mPassword; private String mUsername1; private String mPassword1; private ProgressDialog mProgressDialog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_register); mUsername = (EditText) findViewById(R.id.username); mPassword = (EditText) findViewById(R.id.password); } public void register(View view) { mUsername1 = mUsername.getText().toString().trim(); mPassword1 = mPassword.getText().toString().trim(); User user = new User(); user.setUserName(mUsername1); user.setPassword(mPassword1); mProgressDialog = ProgressDialog.show(this, "注册", "玩命儿注册中....."); RetrofitClient.getInstance().getAPI().postRequest(user).enqueue(new Callback<UserResult>() { @Override public void onResponse(Call<UserResult> call, Response<UserResult> response) { mProgressDialog.dismiss(); if (response.isSuccessful()){ UserResult userResult = response.body(); if (userResult!=null){ if (userResult.getErrcode()==1){ Toast.makeText(RegisterActivity.this,"注册成功!!",Toast.LENGTH_SHORT).show(); return; } Toast.makeText(RegisterActivity.this,userResult.getErrmsg(),Toast.LENGTH_SHORT).show(); return; } Toast.makeText(RegisterActivity.this,"未知错误!",Toast.LENGTH_SHORT).show(); } } @Override public void onFailure(Call<UserResult> call, Throwable t) { mProgressDialog.dismiss(); Toast.makeText(RegisterActivity.this,"注册失败",Toast.LENGTH_SHORT).show(); } }); } }
很明显Activity即要获取视图数据,mUsername1,mPassword1,响应视图事件 register(View view),还要从Model层中获取数据,并且根据成功还是失败来更新视图。
如果只是一个登录事件需要触发,那以上代码没什么问题,代码少,一言就能看出来。但是如果这个Activity的按钮多,即使视图事件多,那么这个Activity就会膨胀起来,代码多得烦。
来看下MVP的解决代码:
View层:
public interface LoginView { /** * 登录时,需要账号 */ String getUserName(); /** * 登录时,需要密码 */ String getPassword(); /** * 用户登录成功的处理 */ void loginSuccess(); /** * 用户登录成功的处理 */ void loginFailed(); }
Presenter层:
import android.os.Handler; public class LoginPresenter { private LoginView loginView; LoginPresenter(LoginView loginView) { this.loginView = loginView; } void UserLogin() { // 模拟网络请求 -- 这层就是model层,我一般称之为数据来源层,我这里懒得写了,直接用Handler替代 new Handler().postDelayed(new Runnable() { @Override public void run() { // 逻辑处理 if (loginView.getUserName().equals("111") && loginView.getPassword().equals("000")) { loginView.loginSuccess(); } else { loginView.loginFailed(); } } }, 2000); } }
Mode层,我就不写了,直接在Presenter层上用Handler模拟。
附上Activity:
public class LoginActivity extends AppCompatActivity implements LoginView { LoginPresenter mLoginPresenter; EditText editText, editText2; Button button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); editText = findViewById(R.id.editText); editText2 = findViewById(R.id.editText2); button = findViewById(R.id.button); mLoginPresenter = new LoginPresenter(this); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { mLoginPresenter.UserLogin(); } }); } @Override public String getUserName() { return editText.getText().toString().trim(); } @Override public String getPassword() { return editText2.getText().toString().trim(); } @Override public void loginSuccess() { Toast.makeText(this, "恭喜登录成功", Toast.LENGTH_LONG).show(); } @Override public void loginFailed() { Toast.makeText(this, "账号或者密码错误", Toast.LENGTH_LONG).show(); } }
很好,一个完整的简单的MVP模式就写好了。
详解一下:
在上面的Model层的用户Login接口需要什么?需要账号和密码2个参数,那么View层的接口就需要提供getUserName和getPassword接口,另外登录会成功,也可能会失败,则需要View层去处理,那么View层就提供了2个接口loginSuccess和loginFailed。简单的说来,Model层需要参数,并且响应的结果,即是View层处理的,View层都需要提供接口,需要强调的是这个View层是个Interface,并非具体的实现,具体的实现交由Activity去提供,或者其他视图去提供。
Presenter层需要做什么事情呢?就是链接View层和Model层,即是提供View层和Model层交互的地方,在Android MVC中,View层和Model层的交互发生在什么地方呢?发生在Activity中,而MVP模式则是发生在Presenter中,那么Activity的耦合性就下降了。那么Activity要做的事情是什么呢?就是实现View层接口。提供相关的数据和响应的结果。
MVP对于MVC的优点在哪里?就是解放了Activity,耦合性降低。缺点是啥?代码变多了,还得多写Presenter层。
其实以上代码是有风险的,如果Activity被销毁了,loginView.loginSuccess();中的LoginView可能就是个空指针了。所以我们写个基类来优化下。
BaseView:
public interface MvpBaseView { /** * 显示正在加载view */ void showLoading(); /** * 关闭正在加载view */ void hideLoading(); /** * 显示提示 * * @param msg */ void showToast(String msg); /** * 获取上下文 * * @return 上下文 */ Context getContext(); }
BasePresenter:
public class MvpBasePresenter<T extends MvpBaseView> { /** * 绑定的view */ protected T mvpView; /** * 绑定view,一般在初始化中调用该方法 */ public void attachView(T mvpView) { this.mvpView = mvpView; } /** * 断开view,一般在onDestroy中调用 */ public void detachView() { this.mvpView = null; } /** * 是否与View建立连接 * 每次调用业务请求的时候都要出先调用方法检查是否与View建立连接 */ public boolean isViewAttached() { return mvpView != null; } /** * 获取连接的view */ public T getView() { return mvpView; } }
BaseMvpActivity:
public abstract class MvpBaseActivity extends AppCompatActivity implements MvpBaseView { private ProgressDialog mProgressDialog; private MvpBasePresenter mMvpBasePresenter; public abstract MvpBasePresenter setPresenter(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mProgressDialog = new ProgressDialog(this); mProgressDialog.setCancelable(false); mMvpBasePresenter = setPresenter(); mMvpBasePresenter.attachView(this); } @Override protected void onDestroy() { super.onDestroy(); mMvpBasePresenter.detachView(); } @Override public void showLoading() { if (!mProgressDialog.isShowing()) { mProgressDialog.show(); } } @Override public void hideLoading() { if (mProgressDialog.isShowing()) { mProgressDialog.dismiss(); } } @Override public void showToast(String msg) { Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); } @Override public Context getContext() { return MvpBaseActivity.this; } }
实际使用:
/** * 接口继承 */ public interface LoginView extends MvpBaseView { /** * 用户登录成功的处理 */ void loginSuccess(); String getUserName(); String getPassword(); }
public class LoginPresenter extends MvpBasePresenter<LoginView> { void UserLogin() { // 视图层的显示 mvpView.showLoading(); // 如果当前视图已经被销毁了,就不要进行Model层的数据请求了 if (!this.isViewAttached()) { return; } // 模拟网络请求 -- 这层就是model层,我一般称之为数据来源层 new Handler().postDelayed(new Runnable() { @Override public void run() { // 视图层的显示 mvpView.hideLoading(); // 逻辑处理 if (mvpView.getUserName().equals("111") && mvpView.getPassword().equals("000")) { // 如果当前视图已经被销毁了,就不要进行View层的数据更新了 if (!LoginPresenter.this.isViewAttached()) { return; } mvpView.loginSuccess(); } else { // 如果当前视图已经被销毁了,就不要进行View层的数据更新了 if (!LoginPresenter.this.isViewAttached()) { return; } mvpView.showToast("账号或者密码错误"); } } }, 2000); } }
public class MainActivity extends MvpBaseActivity implements LoginView { LoginPresenter mLoginPresenter; EditText editText, editText2; Button button, button2; @Override public MvpBasePresenter setPresenter() { mLoginPresenter = new LoginPresenter(); return mLoginPresenter; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); editText = findViewById(R.id.editText); editText2 = findViewById(R.id.editText2); button = findViewById(R.id.button); button2 = findViewById(R.id.button2); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { mLoginPresenter.UserLogin(); } }); button2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { startActivity(new Intent(MainActivity.this, LoginActivity.class)); } }); } @Override public void loginSuccess() { Toast.makeText(this, "恭喜登录成功", Toast.LENGTH_LONG).show(); } @Override public String getUserName() { return editText.getText().toString().trim(); } @Override public String getPassword() { return editText2.getText().toString().trim(); } }
一样Model层,我任然没写。懒得写...
代码:
https://github.com/hbolin/android_mvp_demo
总之,不管是MVC也好,还是MVP也好,要基本要解决的问题都是数据和视图的交互问题:获取视图提供的数据,比如输入,响应视图事件,然后反馈给视图。为什么会提供这两种解决方案呢?就是要解决耦合,重用。