本文内容部分参考自:


MVP模式

在Android项目中,Activity和Fragment占据了大部分的开发工作。MVP模式就是为了专门优化Activty和Fragment的代码而产生的。


按照MVC的分层,Activity和Fragment应该属于View层,用于展示UI界面,以及接受用户的输入,此外还要承担一些生命周期的工作。Activity在Android开发中从当非常重要的角色,特别是生命周期的功能,所以开发的时候我们经常把一些业务逻辑直接写在Activity中,这会非常直观,代价是Activity会越来越臃肿,并且如果是一些通用的业务逻辑,写在Activity中就意味着这个逻辑不能复用。因此Activity不仅承担了View的角色,还承担了一部分Controller角色,这样一来V和C就耦合在一起了,虽然这样写很方便,但是业务调整的话,维护就十分困难。因此有必要在Activity中,把View和Controller抽离开来,而这就是MVP模式的工作。


mvp架构模式ios mvp模式android_mvp架构模式ios



MVP结构

MVP模式核心思想:

   MVP把Activity中的UI逻辑抽象成View接口,把业务逻辑抽象成presenter接口,Model类还是Model。


MVP模式作用

1、分离了视图逻辑和业务逻辑,减低了耦合

2、Activity只处理生命周期的任务,代码变得更加简洁

3、视图逻辑和业务逻辑分别抽象到View和Presenter接口中,提高代码的可阅读性。

4、Presenter被抽象成接口,可以具有多种实现方式,便于单元测试

5、把业务逻辑抽到了Presenter中去,避免后台线程引用着Activity导致Activity的资源无法被系统回收而引起内存泄漏和OOM.


MVP的使用


mvp架构模式ios mvp模式android_ide_02

1、创建Presenter接口,处理所有业务逻辑的接口。

2、创建View接口,所有视图逻辑的接口,实现类是当前的activity/fragment

3、activity中包含了一个presenter接口,presenter实现中包含了一个view接口,并且依赖了Model。Fragment/Activity只保留了对presenter接口的调用。


MVP案例

工程目录

mvp架构模式ios mvp模式android_业务逻辑_03

presenter和view的接口:

public interface LoginContract {

    interface View extends BaseView<Presenter>{
        void finish();
        void showRegister();
        void showAllStuff(String userId);
        void showToast(String message);
    }

    interface Presenter extends BasePresenter{
        void login(@NonNull String email, @NonNull String password);
        void showRegister();
    }
}

Activity:

public class LoginActivity extends AppCompatActivity {

    private LoginPresenter mLoginPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        // Set up the toolbar.
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        getSupportActionBar().setDisplayShowTitleEnabled(false);

        LoginFragment loginFragment = (LoginFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);

        if(loginFragment == null) {
            loginFragment = LoginFragment.newInstance();
            ActivityUtils.addFragmentToActivity(getSupportFragmentManager(), loginFragment, R.id.contentFrame);
        }
        AppExecutors appExecutors = new AppExecutors();
        mLoginPresenter = new LoginPresenter(UsersRepository.getInstance(UsersLocalDataSource.getInstance(appExecutors), UsersRemoteDataSource.getInstance(appExecutors),appExecutors),loginFragment);
    }
}

Fragment:

public class LoginFragment extends Fragment implements LoginContract.View{
    private static final String TAG = LoginFragment.class.getSimpleName();
    private EditText mEmailEt;
    private EditText mPasswordEt;
    private Button mLoginBt;
    private TextView mToRegisterTv;

    private LoginContract.Presenter mPresenter;

    public static LoginFragment newInstance(){
        LoginFragment fragment = new LoginFragment();
        return fragment;
    }

    @Override
    public void onResume() {
        super.onResume();
       // mPresenter.start();
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.fragment_login, container, false);
        mEmailEt = root.findViewById(R.id.et_login_email);
        mPasswordEt = root.findViewById(R.id.et_login_password);
        mLoginBt = root.findViewById(R.id.bt_login_login);
        mToRegisterTv = root.findViewById(R.id.tv_login_toregister);

        mLoginBt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try{
                    mPresenter.login(mEmailEt.getText().toString(), mPasswordEt.getText().toString());
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        });
        mToRegisterTv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mPresenter.showRegister();
            }
        });
        return root;
    }

    @Override
    public void setPresenter(LoginContract.Presenter presenter) {
        if (presenter != null) {
            mPresenter = presenter;
        }
    }

    @Override
    public void finish() {
        getActivity().finish();
    }

    @Override
    public void showRegister() {
        try{
            Intent intent = new Intent(getContext(), RegisterActivity.class);
            startActivity(intent);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public void showAllStuff(String userId) {
        try{
            Intent intent = new Intent(getContext(), AllStuffActivity.class);
            intent.putExtra("USERID", userId);
            startActivity(intent);
            getActivity().finish();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public void showToast(final String message) {
        Log.d(TAG, "showToast: " + message);
        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
            }
        });
    }
}

presenter:

public class LoginPresenter implements LoginContract.Presenter {

    private static final String TAG = LoginPresenter.class.getSimpleName();
    private final UsersRepository mUserRepository;

    private final LoginContract.View mLoginView;

    public LoginPresenter(@NonNull UsersRepository usersRepository, @NonNull LoginContract.View loginView) {
        mUserRepository = usersRepository;
        mLoginView = loginView;

        mLoginView.setPresenter(this);
    }

    @Override
    public void start() {

    }

    @Override
    public void login(@NonNull String email, @NonNull String password) {
        if (email.isEmpty() && password.isEmpty()) {
            mLoginView.showToast("请输入邮箱和密码");
        } else if (email.isEmpty()) {
            mLoginView.showToast("请输入邮箱");
        } else if (password.isEmpty()) {
            mLoginView.showToast("请输入密码");
        } else {
            mUserRepository.login(email, MD5Util.encrypt(password), new UsersDataSource.GetUserCallBack() {
                @Override
                public void onUserLoaded(final User user) {
                    Log.d(TAG, "onUserLoaded: 登录成功");
                    //登录成功,加载userID信息
                    final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(MyApplication.getContext());
                    final SharedPreferences.Editor editor = sharedPreferences.edit();
                    editor.putString("USERID", user.getUserId()).apply();
                    //登录成功,调转到所有材料页面
                    mLoginView.showAllStuff(user.getUserId());
                }

                @Override
                public void onDataNotAvailable(String message) {
                    Log.d(TAG, "onDataNotAvailable: 登录失败" + message);
                    mLoginView.showToast(message);
                }
            });
        }
    }

    @Override
    public void showRegister() {
        mLoginView.showRegister();
    }
}

数据源采用了接口和回调的方式。

public interface UsersDataSource {
    interface LoadUserCallBack {
        void onUserLoaded(User user);

        void onDataNotAvailable();
    }

    interface GetUserCallBack {
        void onUserLoaded(User user);

        void onDataNotAvailable(String message);
    }

    interface SendRequestCallBack{
        void onRequestSuccess(String message);
        void onRequestFail(String message);
    }


    void getUser(@NonNull String userId, @NonNull GetUserCallBack callBack);

    void register(@NonNull User user, @NonNull String code, @NonNull SendRequestCallBack callBack);

    void updateUser(@NonNull User user);

    void deleteUser(@NonNull String userId);

    void login(@NonNull String email, @NonNull String password, @NonNull GetUserCallBack callBack);

    void sendCode(@NonNull String email, @NonNull SendRequestCallBack callBack);
}