Android MVP架构实现

最近在学习Android的MVP架构,在网上找了许多资料都没有一个清晰的认识,偶然看到简书上骆驼骑士前辈的文章,对MVP的实现过程有了一个较为清晰的认识。后又研究了一下Google官方Demo,分别对两种实现方式做了一个Demo进行对比。

对于Android MVP实现方式,借用一下骆驼骑士前辈的分析

目前常见的MVP在Android里的实践有两种解决方案:
1.直接将Activity看作View,让它只承担View的责任。
2.将Activity看作一个MVP三者以外的一个Controller,只控制生命周期。

下面我们来讲述一下这两种方式的实现过程。


第一种:Activity承担MVP中V的角色

我们以一个不需要网络请求的登录作为示例进行实现。

在这个MVP架构中,我们需要以下几个文件:

类型 文件名
Model 接口 ILoginModel
View 接口 ILoginView
Presenter 接口 ILoginPresenter
Model 接口实现 ILoginModelImpl
View 接口实现 LoginActivity
Presenter 接口实现 ILoginPresenterImpl

首先,我们先定义一个实体类UserEntity,模拟服务器返回的数据。

UserEntity.java
public class UserEntity {
    /**
     * 响应码
     */
    private int code;
    /**
     * 提示信息
     */
    private String msg;
    /**
     * 用户名
     */
    private String user_name;

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }

    public String getUser_name() {
        return user_name;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public void setUser_name(String user_name) {
        this.user_name = user_name;
    }
}

接下来,我们需要编写ILoginModelILoginViewILoginPresenter三个接口

ILoginModel.java
import com.example.mvp.login.CallBack;

public interface ILoginModel {
    /**
     * 登录相关的业务逻辑在此处理
     *
     * @param username 账号
     * @param password 密码
     * @param callBack 回调接口
     */
    void login(String username, String password, CallBack callBack);
}
ILoginView.java
import com.example.mvp.login.entity.UserEntity;

public interface ILoginView {
    /**
     * 登录成功的回调显示
     *
     * @param userEntity 实体类
     */
    void showLoginSuccessMsg(UserEntity userEntity);

    /**
     * 登录失败的回调显示
     *
     * @param errorMsg 失败信息
     */
    void showLoginFailMsg(String errorMsg);
}
ILoginPresenter.java
public interface ILoginPresenter {
    /**
     * 连接 Model 和 View 的登录方法
     *
     * @param username 账号
     * @param password 密码
     */
    void login(String username, String password);
}

因为在Model中用到了一个回调接口CallBack,所以我们接下来定义一下这个接口:

CallBack.java
import com.example.mvp.login.entity.UserEntity;

public interface CallBack {
    /**
     * 登录成功的回调
     *
     * @param userEntity 实体类
     */
    void onSuccess(UserEntity userEntity);

    /**
     * 登录失败的回调
     *
     * @param errorMsg 错误信息
     */
    void onFail(String errorMsg);
}

至此,MVP架构的准备工作都做好了,接下来我们开始实现这些接口,将各个接口连接起来,实现一个登录功能。

大家知道,Model是承担了数据处理和业务逻辑的功能,所以我们来实现一下ILoginModel这个接口。在这个类中,我们假设当账号为 user,密码为 123456 时即登录成功。

LoginModelImpl.java
import com.example.mvp.login.CallBack;
import com.example.mvp.login.entity.UserEntity;

public class LoginModelImpl implements ILoginModel {
    @Override
    public void login(String username, String password, CallBack callBack) {
        if (username.equals("user") && password.equals("123456")) {
            // 模拟服务器返回的数据
            UserEntity userEntity = new UserEntity();
            userEntity.setCode(1);
            userEntity.setMsg("登录成功");
            userEntity.setUser_name("Vip User");

            callBack.onSuccess(userEntity);
        } else {
            callBack.onFail("用户名或密码错误");
        }
    }
}

接下来我们实现一下作为中间联系人的ILoginPresenter接口:

LoginPresenterImpl.java
import com.example.mvp.login.CallBack;
import com.example.mvp.login.entity.UserEntity;
import com.example.mvp.login.model.ILoginModel;
import com.example.mvp.login.view.ILoginView;

public class LoginPresenterImpl implements ILoginPresenter {
    /**
     * View
     */
    private ILoginView view;
    /**
     * Model
     */
    private ILoginModel model;

    /**
     * 构造方法
     *
     * @param view  实现接口的类
     * @param model 实现接口的类
     */
    public LoginPresenterImpl(ILoginView view, ILoginModel model) {
        this.view = view;
        this.model = model;
    }

    @Override
    public void login(String username, String password) {
        //主动调用model中的login方法实现登录
        model.login(username, password, new CallBack() {
            @Override
            public void onSuccess(UserEntity userEntity) {
                //如果登录成功,将登录成功的数据传递给View进行显示
                view.showLoginSuccessMsg(userEntity);
            }

            @Override
            public void onFail(String errorMsg) {
                //如果登录失败,将登陆失败的数据传递给View显示
                view.showLoginFailMsg(errorMsg);
            }
        });
    }
}

最后一步,新建一个LoginActivity,实现ILoginView,该activity的内容非常简单,只有两个EditText用于输入账号和密码,一个Button用于点击登录,在这里,只贴出Activity的代码:

LoginActivity.java
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.example.mvp.R;
import com.example.mvp.login.Presenter.ILoginPresenter;
import com.example.mvp.login.Presenter.LoginPresenterImpl;
import com.example.mvp.login.entity.UserEntity;
import com.example.mvp.login.model.LoginModelImpl;

public class LoginActivity extends AppCompatActivity implements ILoginView {
    /**
     * Presenter
     */
    private ILoginPresenter presenter;
    /**
     * 登录按钮
     */
    private Button btn;
    /**
     * 用户名输入框
     */
    private EditText etName;
    /**
     * 密码输入框
     */
    private EditText etPwd;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        //实例化Presenter
        presenter = new LoginPresenterImpl(this, new LoginModelImpl());

        //绑定控件
        btn = findViewById(R.id.btn);
        etName = findViewById(R.id.et_name);
        etPwd = findViewById(R.id.et_pwd);

        //实现登录的点击事件
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                String username = etName.getText().toString();
                String password = etPwd.getText().toString();

                //检测账号密码均不为空时调用presenter的登录方法
                if ("".equals(username) || "".equals(password)) {
                    Toast.makeText(LoginActivity.this, "用户名或密码不能为空", Toast.LENGTH_SHORT).show();
                } else {
                    presenter.login(username, password);
                }
            }
        });
    }


    @Override
    public void showLoginSuccessMsg(UserEntity userEntity) {
        Toast.makeText(this, userEntity.getUser_name() + "登录成功", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void showLoginFailMsg(String errorMsg) {
        Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show();
    }
}

以上就是本例的全部代码,现在我们来整理一下整个创建流程:

  1. 创建一个实体类UserEntity,用于模拟服务器返回的数据;
  2. 创建ILoginModelILoginViewILoginPresenter接口;
  3. 创建一个CallBack接口,用于登录回调;
  4. 实现M、P、V接口,创建LoginModelImplLoginPresenterImplLoginActivity,实现登录功能。

实现的效果如下:
登陆成功登录失败
我们再来整理一下整个登录过程是如何实现的:

  1. 在 LoginActivity 中声明一个 ILoginPresenter,初始化为 LoginPresenterImpl,向构造函数中传入两个参数—— LoginActivity、LoginModelImpl。
  presenter = new LoginPresenterImpl(this, new LoginModelImpl());
  1. 点击 button,调用 presenter.login() —— 实际上调用的是 LoginPresenterImpl.login()
 presenter.login(username, password);
  1. 在 LoginPresenterImpl.login() 中调用 model.login(),实现 CallBack 接口,将 CallBack 回调的数据推送给 View 进行显示 —— 实际上调用的是 LoginModelImpl.login()
model.login(username, password, new CallBack() {
            @Override
            public void onSuccess(UserEntity userEntity) {
                //如果登录成功,将登录成功的数据传递给View进行显示
                view.showLoginSuccessMsg(userEntity);
            }

            @Override
            public void onFail(String errorMsg) {
                //如果登录失败,将登陆失败的数据传递给View显示
                view.showLoginFailMsg(errorMsg);
            }
        });
  1. 在 model.login() 中实现登录判断,将登录结果通过 CallBack 接口提交给 Presenter;
if (username.equals("user") && password.equals("123456")) {
	// 模拟服务器返回的数据
      UserEntity userEntity = new UserEntity();
      userEntity.setCode(1);
      userEntity.setMsg("登录成功");
      userEntity.setUser_name("Vip User");

      callBack.onSuccess(userEntity);
} else {
     callBack.onFail("用户名或密码错误");
}
  1. 在 LoginActivity 实现的 onLoginSuccessMsg() 、onLoginFailMsg() 中处理登陆成功和失败的提示。
@Override
public void showLoginSuccessMsg(UserEntity userEntity) {
	Toast.makeText(this, userEntity.getUser_name() + "登录成功", Toast.LENGTH_SHORT).show();
}

@Override
public void showLoginFailMsg(String errorMsg) {
   Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show();
}

第二种:将Activity看作一个MVP三者以外的一个Controller,只控制生命周期

我们仍旧以一个不需要网络请求的登录作为示例进行实现。

在这个个MVP架构中,我们需要以下几个文件:

类型 文件名
Model 接口 ILoginModel
View 接口 ILoginView
Presenter 接口 ILoginPresenter
Model 接口实现 ILoginModelImpl
View 接口实现 LoginFragment
Presenter 接口实现 ILoginPresenterImpl
Controller LoginActivity

大家可以看到,和上一个例子相比,我们多了一个 LoginFragment,并且对 View 接口的实现并不是 LoginActivity 来实现了,而是改为LoginFragment。( 其实在Google官方Demo里,View 和 Presenter 的接口是放在一个名为 Contract 的接口文件里的,此处为了更好的理解,将 View 和 Presenter 分别放置在一个文件里。

因为这个Demo和上一个Demo并没有太大的区别,所以此处只贴出不同部分的代码片段。

LoginModelImpl.java (增加如下代码)
/**
 * 生成实例
 */
private static LoginModelImpl instance = null;

public static LoginModelImpl getInstance() {
	if (instance == null) {
		instance = new LoginModelImpl();
    }
    return instance;
}

private LoginModelImpl() {  }
LoginPresenterImpl.java(修改变量和构造方法)
/**
 * View
 */
private ILoginView view;
/**
 * Model
 */
private LoginModelImpl model;
/**
 * 构造方法
 *
 * @param view  实现接口的类
 * @param model 实现接口的类
 */
public LoginPresenterImpl(ILoginView view, LoginModelImpl model) {
	this.view = view;
    view.setPresenter(this);
    this.model = model;
}
ILoginView.java(增加如下方法)
/**
 * 设置Presenter
 *
 * @param presenter
 */
void setPresenter(ILoginPresenter presenter);

下面贴出 LoginActivityLoginFragment 的代码供大家进行比较:

LoginActivity.java
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.example.mvp.R;
import com.example.mvp.login.Presenter.LoginPresenterImpl;
import com.example.mvp.login.model.LoginModelImpl;

public class LoginActivity extends AppCompatActivity {
    /**
     * Presenter
     */
    LoginPresenterImpl presenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

		 // 绑定Fragment
        FragmentManager fragmentManager = getSupportFragmentManager();
        LoginFragment loginFragment = (LoginFragment) fragmentManager.findFragmentById(R.id.frame_layout);
        if (loginFragment == null) {
            loginFragment = LoginFragment.newInstance();
            if (fragmentManager != null && loginFragment != null) {
                FragmentTransaction transaction = fragmentManager.beginTransaction();
                transaction.add(R.id.frame_layout, loginFragment);
                transaction.commit();
            }
        }
        
        //实例化Presenter
        presenter = new LoginPresenterImpl(loginFragment, LoginModelImpl.getInstance());
    }
}
LoginFragment.java
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.example.mvp.R;
import com.example.mvp.login.Presenter.ILoginPresenter;
import com.example.mvp.login.entity.UserEntity;

public class LoginFragment extends Fragment implements ILoginView {
    /**
     * presenter
     */
    private ILoginPresenter presenter;
    /**
     * 登录按钮
     */
    private Button btn;
    /**
     * 用户名输入框
     */
    private EditText etName;
    /**
     * 密码输入框
     */
    private EditText etPwd;

    /**
     * 生成实例
     *
     * @return loginFragment
     */
    public static LoginFragment newInstance() {
        return new LoginFragment();
    }

    public LoginFragment() {

    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_login, container, false);
        //绑定控件
        btn = view.findViewById(R.id.btn);
        etName = view.findViewById(R.id.et_name);
        etPwd = view.findViewById(R.id.et_pwd);

        //实现登录的点击事件
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                String username = etName.getText().toString();
                String password = etPwd.getText().toString();

                //检测账号密码均不为空时调用presenter的登录方法
                if ("".equals(username) || "".equals(password)) {
                    Toast.makeText(getContext(), "用户名或密码不能为空", Toast.LENGTH_SHORT).show();
                } else {
                    presenter.login(username, password);
                }
            }
        });
        return view;
    }

    @Override
    public void setPresenter(ILoginPresenter presenter) {
        this.presenter = presenter;
    }

    @Override
    public void showLoginSuccessMsg(UserEntity userEntity) {
        Toast.makeText(getContext(), userEntity.getUser_name() + "登录成功", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void showLoginFailMsg(String errorMsg) {
        Toast.makeText(getContext(), errorMsg, Toast.LENGTH_SHORT).show();
    }
}

在本例中,大家可以看到,真正的 View 角色是由 Fragment 承担的,View 相关的操作都在 Fragment 中进行,Activity 不再承担View的角色,而是独立于MVP之外,成为一个单独的个体:

每一个Activity都拥有一个Fragment来作为View,然后每个Activity也对应一个Presenter,在Activity里只处理与生命周期有关的内容,并跳出MVP之外,负责实例化Model,View,Presenter,并负责将三者合理的建立联系,承担的就是一个上帝视角

上述两种模式,一种是实际操作上的简便,将Activity看做View,直接在Activity中实例化Presenter和Model;另一种是建立一个规范且正确的依赖关系,让Activity跳出MVP,负责建立M、V、P 三者的联系,相应的,操作上也会相对复杂,每个Activity都必须有一个独立的Fragment。两种模式,大家根据自己的需要进行取舍即可。

以上呢,即是本人的一些浅薄的见解,如有不全之处,欢迎大家指正。

猜你喜欢

转载自blog.csdn.net/m0_37063730/article/details/84635538