Android中MVP的使用
以前在写项目的时候,没有过多考虑架构模式的问题,所以一开始使用的都是类似MVC的架构,严格来讲,之前的项目都不算MVC模式,只是简单的将网络请求与界面分离,然后通过Handler通知更新界面。随着项目越来越大,Activity和Fragment中代码越来越多,导致项目的维护变得越来越复杂,一开始重构成了MVC模式,随着深入了解,发现MVP模式能够大大减少Model和View层之间的耦合度,遂又将项目重构成MVP模式。最近又发现MVP已经开始不能满足需求,正在研究MVVM,后期会将MVVM的研究成果奉上。
当然不是所有的模块都使用MVP,MVP适合在一些逻辑复杂的模块中使用,如果业务逻辑简单,一个Activity就能搞定,也没有必要生搬硬套的做成MVP模式的。只要整体的模式思路使用MVP模式,其中夹杂一些MVC模式,甚至简单的模块不使用模块,不会对整个项目有太大的影响,反而是最优选择。
MVP模式也有其缺点,首先就是代码量多了,文件数量大大提升,但换来的好处是项目结构清晰,在前期只有原型图的时候,我们也可以提前开发,Model层负责数据的请求和数据的处理,写好Presenter层和View层后,基本不用改,只需关注Model层的变化就可以了;还是就是Activity内部类基本不再使用了,部分UI逻辑判断也可以转到Presenter去处理,View层(UI)也非常清晰干净。
什么是MVP
MVP是 模型(Model)、视图(View)、主持人(Presenter)的缩写,代表不同模块。
模型(Model):负责处理数据的加载或者存储;比如从网络或者本地数据库获取数据。
视图(View):负责界面数据的展示,与用户进行交互。
主持人(Presenter):相当于协调者,是Model和View层之间的桥梁,占据主导地位。
如图所示,Model层和View并不直接交互,而是通过Presenter层进行交互。Presenter持有Model层和View层的Interface的引用,而View层持有Presenter层Interface的引用。当View层需要展示数据的时候,会调用Presenter层的某个接口,然后Presenter层会调用Model层请求数据,当Model层数据获取成功后会调用Presenter层的回调方法通知Presenter层数据加载完毕,然后Presenter层再调用View层的接口将获取到的数据展示给用户。
整个过程,View层告知Presenter层需要什么数据,Presenter层调用Model获取数据,Model数据获取完成告知Presenter层,Presenter层再调用View层将数据展示给用户。
这样分层减少了Model层和View层的耦合度:
1、可以使得Model层和View层单独开发与测试,互补依赖;
2、可以将Model层封装复用,减少代码量
3、方便构建单元测试,测试逻辑中存在的潜在BUG
4、清晰的结构,便于维护
准备工作
创建LoginBean类
public class LoginBean { private String mName; private String mPassWord; public String getmName(){ return mName; } public void setmName(String name){ mName = name; } public String getmPassWord(){ return mPassWord; } public void setmPassWord(String passWord){ mPassWord = passWord; } } |
模型层(Model)
1. 先写一个接口
public interface ILoginModel { void login(String username, String password, OnLoginListener loginListener); interface OnLoginListener{ void LoginSuccess(LoginBean loginBean); void LoginFail(); } } |
2. 继承接口进行数据请求
public class LoginModel implements ILoginModel { @Override public void login(String username, String password, OnLoginListener loginListener) { //模拟子线程耗时操作 simulationRequest(username, password, loginListener); } private void simulationRequest(final String username, final String password,final OnLoginListener loginListener) { new Thread(){ @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } if(username.equals("Lking") && password.equals("123456")){//登录成功 LoginBean loginBean = new LoginBean(); loginBean.setmName(username); loginBean.setmPassWord(password); loginListener.LoginSuccess(loginBean); }else{//登录失败 loginListener.LoginFail(); } } }.start(); } } |
视图层(View)
1、先写一个接口
public interface ILoginView { /** 获取账号*/ String getName(); /** 获取密码*/ String getPassword(); /** 显示加载圈*/ void showLoading(); /** 关闭加载圈*/ void hideLoading(); /** 跳转到主界面*/ void toMainActivity(LoginBean loginBean); /** 显示失败提示*/ void showFailToast(); } |
2、编写布局文件activity_mvp.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/color_ffffff" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="45dp" android:layout_margin="10dp"> <TextView android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center" android:text="账 号:" android:textColor="@color/color_000000" android:textSize="18sp" /> <EditText android:id="@+id/lking_mvp_name_edt" android:layout_width="match_parent" android:layout_height="match_parent" android:hint="请输入账号" android:textColor="@color/color_000000" android:textSize="16sp" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="45dp" android:layout_margin="10dp"> <TextView android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center" android:text="密 码:" android:textColor="@color/color_000000" android:textSize="18sp" /> <EditText android:id="@+id/lking_mvp_pas_edt" android:layout_width="match_parent" android:layout_height="match_parent" android:hint="请输入密码" android:textColor="@color/color_000000" android:textSize="16sp" /> </LinearLayout> <TextView android:id="@+id/lking_mvp_login_txt" android:layout_width="match_parent" android:layout_height="45dp" android:gravity="center" android:text="登录" android:textColor="@color/color_000000" android:textSize="18sp" /> <ProgressBar android:id="@+id/lking_mvp_login_pro" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:visibility="gone" android:layout_marginTop="20dp" /> </LinearLayout> |
3、继承接口进行数据显示
public class MvpActivity extends Activity implements ILoginView { private EditText mNameEdt; private EditText mPasswordEdt; private TextView mLoginBtn; private ProgressBar mLoading; private LoginPresenter mLoginPresenter; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_mvp); initViews(); } /** * 初始化控件 */ private void initViews() { mNameEdt = (EditText) findViewById(R.id.lking_mvp_name_edt); mPasswordEdt = (EditText) findViewById(R.id.lking_mvp_pas_edt); mLoginBtn = (TextView) findViewById(R.id.lking_mvp_login_txt); mLoading = (ProgressBar) findViewById(R.id.lking_mvp_login_pro); mLoginPresenter = new LoginPresenter(this); mLoginBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mLoginPresenter.login(); } }); } @Override public String getName() { return mNameEdt.getText().toString().trim(); } @Override public String getPassword() { return mPasswordEdt.getText().toString().trim(); } @Override public void showLoading() { mLoading.setVisibility(View.VISIBLE); } @Override public void hideLoading() { mLoading.setVisibility(View.GONE); } @Override public void toMainActivity(LoginBean loginBean) { Toast.makeText(this, "登录成功,跳转到主界面", Toast.LENGTH_SHORT).show(); } @Override public void showFailToast() { Toast.makeText(this, "登录失败,请重新输入账号和密码", Toast.LENGTH_SHORT).show(); mNameEdt.setText(""); mPasswordEdt.setText(""); } } |
主持人(Presenter)
创建管理逻辑类 public class LoginPresenter { private ILoginView mILoginView; private LoginModel mLoginModel; private Handler mHandler = new Handler(); public LoginPresenter(ILoginView iLoginView) { mILoginView = iLoginView; mLoginModel = new LoginModel(); } public void login(){ mILoginView.showLoading(); mLoginModel.login(mILoginView.getName(), mILoginView.getPassword(), new ILoginModel.OnLoginListener() { @Override public void LoginSuccess(final LoginBean loginBean) { mHandler.post(new Runnable() { @Override public void run() { mILoginView.hideLoading(); mILoginView.toMainActivity(loginBean); } }); } @Override public void LoginFail() { mHandler.post(new Runnable() { @Override public void run() { mILoginView.hideLoading(); mILoginView.showFailToast(); } }); } }); } } |