Android:MVP模式
2016-03-07 15:37
477 查看
简介
关于MVP的介绍网上有很多,这里不再累述,虽然大家的实现方式也许各不相同,但是有一些基本的共识:Model:数据模型;
View:用户界面,一般对应于Activity、Fragment;
Presenter:作为View和Model的桥梁,实现两者解耦。
MVP的核心是:将View中与UI无关的部分逻辑转移到Presenter中。
其依赖关系如下:
View与Presenter相互依赖,Presenter依赖于Model,View和Model解耦。
实例
经典的登陆例子Model
数据来源,一般是从网络或者数据库异步加载通过接口回调。我们从内存模拟,然后通过handler#postDelayed来模拟加载过程。public class LoginModel { public static Map<String, String> maps = new HashMap<>(); private Handler mHandler = new Handler(); static { maps.put("key1", "password1"); maps.put("key2", "password2"); maps.put("key3", "password3"); } public void getPassword(final String username, final OnGetPasswordListener listener) { mHandler.postDelayed(new Runnable() { @Override public void run() { String password = maps.get(username); if (password == null) { listener.onGetError(); } else { listener.onGet(password); } } }, new Random().nextInt(2000)); } public void addUser(final String username, final String password, final OnRegisterListener registerListener) { mHandler.postDelayed(new Runnable() { @Override public void run() { try { if (LoginModel.maps.containsKey(username)) { registerListener.onUserExist(); } else { LoginModel.maps.put(username, password); registerListener.onSuccessful(); } } catch (Exception e) { e.printStackTrace(); registerListener.onNetError(); } } }, new Random().nextInt(2000)); } public interface OnGetPasswordListener { void onGet(String password); void onGetError(); } public interface OnRegisterListener { void onSuccessful(); void onUserExist(); void onNetError(); } }
View
首先明确一个View的职责,以LoginActivity为例,他要做的是:用户点击登陆:
检查账号密码合法性;
登陆过程给用户提示;
如果登陆成功,用户提示消失,执行其他操作;
如果登陆失败,用户提示消失,执行其他操作;
用户点击清除:清除已经输入的内容
用户点击注册:跳转到注册界面
我们将其抽象为一个接口:
public interface ILoginView { void usernameIllegal(); void passwordIllegal(); void showProgress(); void dismissProgress(); void loginSuccessfully(); void loginUnsuccessfully(); void clearState(); }
然后View去实现上诉接口:
public class LoginActivity extends AppCompatActivity implements ILoginView { @InjectView(R.id.et_username) EditText mEtUsername; @InjectView(R.id.et_password) EditText mEtPassword; @InjectView(R.id.btn_login) Button mBtnLogin; @InjectView(R.id.btn_clear) Button mBtnClear; private ProgressDialog mProgressDialog; private LoginPresenter mLoginPresenter = new LoginPresenter(this); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.inject(this); mProgressDialog = new ProgressDialog(this); mBtnLogin.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mLoginPresenter.doLogin(mEtUsername.getText().toString(), mEtPassword.getText().toString()); } }); mBtnClear.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mLoginPresenter.doClear(); } }); } @Override public void showProgress() { mProgressDialog.show(); } @Override public void dismissProgress() { mProgressDialog.dismiss(); } @Override public void loginSuccessfully() { Toast.makeText(LoginActivity.this, "login successfully", Toast.LENGTH_SHORT).show(); } @Override public void loginUnsuccessfully() { Toast.makeText(LoginActivity.this, "login unsuccessfully, please check", Toast.LENGTH_SHORT).show(); } @Override public void clearState() { mEtUsername.getText().clear(); mEtPassword.getText().clear(); } @Override public void usernameIllegal() { mEtUsername.setError("should not be empty"); } @Override public void passwordIllegal() { mEtPassword.setError("should not be empty"); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.action_settings) { startActivity(new Intent(this, RegisterActivity.class)); return true; } return super.onOptionsItemSelected(item); } }
可以看到上诉的View只执行UI相关的操作,没有任何逻辑,逻辑都转移到了Prensenter。有一些比较简单的逻辑可以不必转移到Presenter Layer,比如clearState,这一逻辑完全可以直接在View层调用而不经过Presenter回调。还有输入合法性判断,只判断其是否为空也是很简单的逻辑,同样可以在View层实现。总而言之,简单的逻辑可以在View中处理。
Presenter
最后也是最关键的,在Presenter中连接View与Model,承担View的大部分逻辑。public class LoginPresenter { private ILoginView mLoginView; private LoginModel mModel = new LoginModel(); public LoginPresenter(ILoginView loginView) { mLoginView = loginView; } public void doLogin(String inputUsername, final String inputPassword) { if (inputUsername.equals("")) { mLoginView.usernameIllegal(); return; } if (inputPassword.equals("")) { mLoginView.passwordIllegal(); return; } mLoginView.showProgress(); mModel.getPassword(inputUsername, new LoginModel.OnGetPasswordListener() { public void onGet(String password) { if (inputPassword.equals(password)) { mLoginView.loginSuccessfully(); } else { mLoginView.loginUnsuccessfully(); } mLoginView.dismissProgress(); } public void onGetError() { mLoginView.loginUnsuccessfully(); mLoginView.dismissProgress(); } }); } public void doClear() { mLoginView.clearState(); } }
比较
假设没有使用MVP架构,常见的做法会是:mBtnLogin.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mEtUsername.getText().toString().equals("")) { usernameIllegal(); return; } if (mEtPassword.getText().toString().equals("")) { passwordIllegal(); return; } LoginModel loginModel = new LoginModel(); showProgress(); loginModel.getPassword(mEtUsername.getText().toString(), new LoginModel.OnGetPasswordListener() { public void onGet(String password) { if (mEtPassword.getText().toString().equals(password)) { loginSuccessfully(); } else { loginUnsuccessfully(); } dismissProgress(); } public void onGetError() { loginUnsuccessfully(); dismissProgress(); } }); } });
这种写法,虽然一开始写起来会非常方便,但是越到后面越臃肿而难以维护。所以使用MVP架构会使得层次清晰,View与Model解耦,代码也较为整洁。
假设现在我要测试一下逻辑是否有误,如果是上诉写法,除了run一下工程然后手动点击测试之外,仿佛无从下手。使用了MVP架构,将View职责抽象成接口后,我们写起单元测试会比较方便。
最后,很多文章会把Model和Prensenter也抽象出一个接口,目前我不知道这样做有什么好处,所以我倾向于Model和Presenter先不需要抽象接口,需要时再进行重构。
项目地址:
https://github.com/leelit/android-mvp-pattern
参考:
http://blog.csdn.net/vector_yi/article/details/24719873
https://segmentfault.com/a/1190000003927200
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0227/2503.html
http://blog.csdn.net/lmj623565791/article/details/46596109
https://github.com/antoniolg/androidmvp
更新
使用这套MVP组合开发了一段时间,有几点新的认识,为了不和上面的弄混乱,新开一个标题。IPresenter接口
public interface IPresenter { void doDestroy(); }
每个Presenter都应该实现这个接口,并将View依赖清除。这样做是为了防止内存泄露,如果你的Model使用RxJava,一般最好调用unsubscribe()以取消网络请求。
@Override public void doDestroy() { mLoginView = null; // if you do with RxJava, you should call unsubscribe() here }
最后当View回调onDestroy()时调用Presenter的doDestroy();
@Override protected void onDestroy() { super.onDestroy(); mLoginPresenter.doDestroy(); }
AS开发步骤
AS环境下使用以下步骤进行MVP开发会非常便捷1、创建一个空的接口,IView;
2、View(Fragment、Activity)实现IView;
public class MyActivity extends Activity implements IView
3、在Presenter里面聚合IView;
public class MyPresenter { private IView mView; public MyPresenter(IView view) { mView = view; } }
4、在View里面组合Presenter;
public class MyActivity extends Activity implements IView { private MyPresenter mPresenter = new MyPresenter(this); }
5、当View需要Model的逻辑时,用mPresenter来调用一个尚未实现的方法;
if(needModel) { mPresenter.doSomeThing(); }
6、按alt + enter,MyPresenter里面会自动添加这个方法,然后进行业务处理;
7、当业务需要回调View时,用mView来调用一个尚未实现的方法;
if(needView) { mView.showSomeThing(); }
8、按alt + enter,IView接口将自动添加这个方法,并且View也会实现这个方法。
项目地址:
https://github.com/leelit/android-mvp-pattern
相关文章推荐
- 【转】Android系统中的.apk文件和dex文件
- Android 图片缓存机制
- listview嵌套listview ScrollView嵌套listview 的冲突问题
- 【android】LayoutInflater 的 inflater 方法浅析
- Xamarin for Windows, 用C#开发android( 环境配置篇)
- Android应用安全之Content Provider安全
- Android Context相关总结
- android 中数据库
- Android三种常用动画分享
- Android开发之获取手机号码
- Android安全输入设计与思考,android设计思考 为什么使用安全键盘? 安全的输入 各大公司的安全键盘设计 开始自定义安全键盘 安全键盘还需要注意的
- Android 拦截短信
- Android开发之开机启动没有界面的应用程序
- 关于android应用程序的入口
- Android Monkey具体解释
- jenkins远程构建Android的过程
- Android入门:Activity四种启动模式
- Android命名规范
- 【Android】【小知识】如何获取指定目录下的所有文件的集合
- 如何实现一个只有广播和service的android应用没有activity