转载请标明出处: http://blog.csdn.net/airsaid/article/details/51315096
本文出自:周游的博客
MVC、MVP、MVVP相信大家已经耳熟能详了,作为Android最出名的三个框架,它们的应用是非常的广泛。这篇博客就来简单介绍下其中二种框架。也加强下自己对这方面的了解。由于自己菜鸟一枚,有不对和需要补充的地方欢迎评论~
MVC全名是:Model(模型) View(视图) Controller(控制器) 是软件架构中最常见的框架,简单来说,就是通过Controller的控制去操作Model层的数据,并且返回给View作展示,具体见下图:
当用户触发了一个事件,View层会将这个事件发送给Controller控制层,然后Controller会通知Model层更新数据,Model层更新完数据之后会直接显示在View层上,这就是MVC的工作原理了。
说到这里可能就有人会说了,明明一个Activity就能搞定的事,为什么还分三个类来写,烦不烦?
So,那为什么要采用架构设计呢?
通过设计,可以使我们的程序模块化,做到模块内部的高聚合和模块之间的低耦合。这样做的好处是,当我们在开发中只需专注一点即可!提高程序的开发效率,并且更容器做后期的测试以及定位问题。但是,我们不能为了设计而设计,为了架构而架构。对于不同量级的工程,具体的架构实现方法必然是不同的。
在Android中,MVC的应用无处不在,如:我们经常写的layout.xml就对应MVC的View层,里面都是一些View的布局代码,而各种JavaBean和Adapter就类似与MVC中的Model层,Activity就是Controller层了。下面用一个登录的小例子来演示一下。
首先看下常规写法,把所有逻辑都放在Activity里面的:
* activity_login.xml布局:
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:stretchColumns="1" tools:context="com.airsaid.mvcdemo.LoginActivity">
<TableRow>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="用户名:" />
<EditText android:id="@+id/et_login_username" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="请输入用户名" />
</TableRow>
<TableRow>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="密码:" />
<EditText android:id="@+id/et_login_password" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="请输入密码" />
</TableRow>
<Button android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:onClick="login" android:text="登录" />
</TableLayout>
public class LoginActivity extends AppCompatActivity {
private EditText mUsername;
private EditText mPassword;
private ProgressDialog dialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initView();
}
private void initView() {
dialog = new ProgressDialog(this);
mUsername = (EditText) findViewById(R.id.et_login_username);
mPassword = (EditText) findViewById(R.id.et_login_password);
}
// 登录
public void login(View v){
String username = mUsername.getText().toString();
String password = mPassword.getText().toString();
final User user = new User();
user.username = username;
user.password = password;
if(checkUser(user)){
dialog.show();
// 模拟请求登录接口
new Thread(new Runnable() {
@Override
public void run() {
// 模拟耗时操作
SystemClock.sleep(2000);
if ("123".equals(user.username) && "123".equals(user.password)){
runOnUiThread(new Runnable() {
@Override
public void run() {
onLoginSuccess();
}
});
}else{
runOnUiThread(new Runnable() {
@Override
public void run() {
onLoginError();
}
});
}
}
}).start();
}else{
Toast.makeText(getApplicationContext(), "用户名或密码不能为空", Toast.LENGTH_SHORT).show();
}
}
/** * 登录成功 */
private void onLoginSuccess() {
Toast.makeText(getApplicationContext(), "登录成功", Toast.LENGTH_SHORT).show();
dialog.dismiss();
}
/** * 登录失败 */
private void onLoginError() {
Toast.makeText(getApplicationContext(), "登录失败", Toast.LENGTH_SHORT).show();
dialog.dismiss();
}
/** * 判断用户输入的信息是否正确 * @param user * @return */
private boolean checkUser(User user) {
// 在这里由于演示,所以只做简单非空判断,实际开发中会判断更多情况
if(TextUtils.isEmpty(user.username) || TextUtils.isEmpty(user.password)){
return false;
}
return true;
}
}
public class User {
public String username;
public String password;
}
这是一个超级简化版的登录,但是差不多已经有上百行代码了,如果都写出来的话,那么就太多了,所以我们就需要遵循某种模式来进行拆分,下面是用MVC模式进行拆分后的代码:
新增一个类,将请求网络的操作抽取出来(这里由于模拟请求网络所以代码并不多):
public class LoginModel {
/** * 请求网络,发送登录消息 * @return */
public boolean sendLoginInfo(User user){
SystemClock.sleep(2000);
if ("123".equals(user.username) && "123".equals(user.password)){
return true;
}else{
return false;
}
}
}
修改Activity中的请求网络为通过创建Model调用方法去判断登录是否成功:
LoginModel loginModel = new LoginModel();
if(loginModel.sendLoginInfo(user)) {
runOnUiThread(new Runnable() {
@Override
public void run() {
onLoginSuccess();
}
});
}else{
runOnUiThread(new Runnable() {
@Override
public void run() {
onLoginError();
}
});
}
到这里一个简单版的MVC抽取就完成了,其实代码并没有少很多,这是因为Activity存在了两部分内容,一部分是业务相关的,一部分是界面相关的。在这里问题就产生了,如果界面复杂或业务多的话,会导致Activity里面的东西会很庞大,V里面的信息就只有一个Layout,而C也就是Activity里的东西则异常庞大。
那么这个时候,我们可以选择将业务部分的代码进行拆分,其对应的模式就是:MVP。
如果将Activity中界面相关的内容进行拆分,其对应的模式就是MVVM。
下面简单讲解一下MVP模式,MVVM由于涉及到data binding框架,东西比较多,下次再写。
MVP是从经典的MVC中演变过来的,在思想上,它们有想通的地方:
Controller/Presenter负责逻辑的处理,Model用于提供数据,View负责显示。
原来的Model层不变,Activity合并到View层,Presenter用于链接View层和Model层。
下面用MVP模式重构上面的登录小例子:
布局文件和Bean如上,就不贴了。
public class LoginActivity extends AppCompatActivity {
private EditText mUsername;
private EditText mPassword;
private ProgressDialog mDialog;
private LoginPresenter mPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
mUsername = (EditText) findViewById(R.id.et_login_username);
mPassword = (EditText) findViewById(R.id.et_login_password);
mDialog = new ProgressDialog(this);
mPresenter = new LoginPresenter(this);
}
public void login(View v){
String username = mUsername.getText().toString();
String password = mPassword.getText().toString();
final User user = new User();
user.username = username;
user.password = password;
if(mPresenter.checkUserInfo(user)){
mDialog.show();
mPresenter.login(user);
}else{
Toast.makeText(getApplicationContext(), "用户名或密码不能为空", Toast.LENGTH_SHORT).show();
}
}
/** * 登录成功 */
public void loginSuccess() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "登录成功", Toast.LENGTH_SHORT).show();
mDialog.dismiss();
}
});
}
/** * 登录失败 */
public void loginError(){
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "登录失败", Toast.LENGTH_SHORT).show();
mDialog.dismiss();
}
});
}
}
P (LoginPresenter)代码:
public class LoginPresenter {
private LoginActivity mView;
public LoginPresenter(LoginActivity view){
this.mView = view;
}
/** * 校验用户信息 * @param user 用户信息 * @return true:校验成功 false:校验失败 */
public boolean checkUserInfo(User user){
// 在这里由于演示,所以只做简单非空判断,实际开发中会判断更多情况
if(TextUtils.isEmpty(user.username) || TextUtils.isEmpty(user.password)){
return false;
}
return true;
}
/** * 登录 * @param user 用户信息 */
public void login(final User user){
new Thread(new Runnable() {
@Override
public void run() {
LoginModel loginModel = new LoginModel();
if(loginModel.sendLoginInfo(user)){
mView.loginSuccess();
}else{
mView.loginError();
}
}
}).start();
}
}
M(LoginModel)代码:
public class LoginModel {
/** * 请求网络,发送登录消息 */
public boolean sendLoginInfo(User user) {
// 模拟请求网络操作
SystemClock.sleep(2000);
if ("123".equals(user.username) && "123".equals(user.password)) {
return true;
} else {
return false;
}
}
}
至此,一个简单的MVP模式的登录小例子就出来了,相比于MVC,MVP中的代码已经相当简洁了,各模块之间分工明确。
View层负责和UI进行交互,Model层负责提供数据,Presenter层负责处理业务逻辑并处理Model层和View层的通信。
相信大家也看出来了,MVP主要解决就是把逻辑层给抽出来成P层,还有就是不和MVC中的Molder层可以操作View层一样,而是由Presenter负责通信的角色。这样做的好处是,如果有逻辑上的修改,那么我们直接修改Presenter层的东西就可以了。
到这里,并没有完善,我们还可以扩展下,上面的LoginActivity的构参中,我们传入了一个LoginActivity:
public LoginPresenter(LoginActivity view){
this.mView = view;
}
这里其实并不合理,因为LoginActivity是属于V层的东西,而我们在开发过程中,V层不光会有Activity,也会有Fragment,如果按照上面写,通用性并不好,这个时候,接口开发就派上用场了。
修改构参为接口:
public LoginPresenter(IUserLoginView view){
this.mView = view;
}
创建IUserLoginView接口:
package com.airsaid.mvpdemo.i;
/** * Created by zhouyou on 2016/5/4. */
public interface IUserLoginView {
/** * 登录成功 */
void loginSuccess();
/** * 登录失败 */
void loginError();
}
让LoginActivity去实现这个接口:
public class LoginActivity extends AppCompatActivity implements IUserLoginView
到这里就扩展完成了,这只是一个MVP的简单扩展,具体大家可以看看Android官方的MVP架构实例项目。
在这里推荐一篇相关文章,大家感兴趣可以看看:Android官方MVP架构实例项目解析