MVP模式 学习笔记

MVP(Model-View-Presenter)模式:
Model: 数据层,提供数据,负责与网络层和数据库层的逻辑交互.
View: UI层,负责显示数据, 并向Presenter报告用户行为.
Presenter: 逻辑处理层,从Model拿数据, 应用到UI层, 管理UI的状态, 决定要显示什么, 响应用户的行为.

1. 主要优势

耦合降低, Presenter变为纯Java的代码逻辑, 不再与Android Framework中的类如Activity, Fragment等关联, 便于写单元测试.

2. MVP的原则
1). Activity/Fragment实现View接口, View中的方法都只是和UI显示相关的. View要尽可能的dummy, 不涉及业务逻辑, presenter告诉它干什么它干什么就行了.
2). Presenter中没有Android相关的类, 是一个纯Java的程序. 这样有利于解耦和测试. (所以一个检查方法是看你的presenter的import中有没有android的包名.)
3).注意生命周期的处理, 因为异步任务callback返回之后View的状态不一定还是活跃的, 所以要有一定的措施检查View是否还在以及处理注销等, 避免crash.比如,在Presenter中数据回调的方法中, 先检查View.isActive()是否为true, 来保证对Fragment的操作安全。验证是否存在空指针异常的方法:网络请求返回之前,按back键,销毁当前界面,此时mView为null,程序crash。

3. 官方demo

每个界面会定义一个Contract, 里面分别定义View和Presenter的接口. 用Repository包装local和remote的数据, local和remote的数据源会和repository实现相同的data source接口, 

4. 自己实现一个中规中矩的mvp - login

定义一个 Contract契约接口,把 Login的Model、View的接口都放入 Contract 的内部,这里的一个 Contract 就对应一个页面(一个 Activity 或者一个 Fragment).放在 Contract 内部是为了让同一个页面的逻辑方法都放在一起,方便查看和修改

public interface LoginContract {
    interface Model{
        void doLogin(String uName, String pwd, Callback callback);
    }

    interface View{
        void onShowPB(boolean show);
        void onLoginSuccess();
        void onLoginFailure(String msg);
    }

    interface Callback{
        void success();
        void failure(String msg);
    }
}

LoginContract.Model由LoginModel实现,LoginContract.View由LoginActivity实现。当 View 需要更新数据时,首先去找 Presenter,然后 Presenter 去找 Model 请求数据,Model 获取到数据之后通知 Presenter,Presenter 再通知 View 更新数据,这样 Model 和 View 就不会直接交互了,所有的交互都由 Presenter 进行,Presenter 充当了桥梁的角色。很显然,Presenter 必须同时持有 View 和 Model 的对象的引用,才能在它们之间进行通信。

public class LoginPresenter {
    private LoginContract.View mView;
    private LoginContract.Model mModel;

    public LoginPresenter(LoginContract.View v){
        mView = v;
        mModel = new LoginModel();
    }

    /**
     * 防止内存泄漏
     * 如果在点击 Button 之后,Model 获取到数据之前,退出了 Activity,
     * 此时由于 Activity 被 Presenter 引用,而 Presenter 正在进行耗时操作,
     * 会导致 Activity 的对象无法被回收,造成了内存泄漏
     *
     * 解决的方式很简单:在 Activity 退出的时候,把 Presenter 对中 View 的引用置为空即可。
     * 即在当前的Activity的onDestroy里,分离异步任务对Activity的引用,就能避免 Activity Leak。
     */
    public void detachView() {
        mView = null;
    }

    public void login(String uName, String pwd){
        mView.onShowPB(true);
        mModel.doLogin(uName, pwd, new LoginContract.Callback() {
            @Override
            public void success() {
                if(null == mView) return;
                mView.onShowPB(false);
                mView.onLoginSuccess();
            }

            @Override
            public void failure(String msg) {
                if(null == mView) return;
                mView.onShowPB(false);
                mView.onLoginFailure(msg);
            }
        });
    }
}

 

//Model: 数据层,负责从网络或数据库中获取数据.
public class LoginModel implements LoginContract.Model {
    @Override
    public void doLogin(final String uName, final String pwd, final LoginContract.Callback callback) {
        //请求数据,并和用户输入的数据匹配
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                String nameFromNet = "lym";
                String pwdFromNet = "123456";
                if (nameFromNet.equals(uName) && pwdFromNet.equals(pwd)){
                    callback.success();
                } else {
                    callback.failure("请检查用户名/密码");
                }
            }
        }, 2000);
    }
}


public class LoginActivity extends Activity implements LoginContract.View {
    private LoginPresenter mPresenter;
    private EditText et_uname, et_pwd;
    private ProgressBar pb_show;

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

        et_uname = findViewById(R.id.et_uname);
        et_pwd = findViewById(R.id.et_pwd);
        pb_show = findViewById(R.id.pb_show);

        mPresenter = new LoginPresenter(this);
    }

    @Override
    protected void onDestroy() {
        //在 Activity 退出的时候,把 Presenter 对中 View 的引用置空
        if(null != mPresenter) {
            mPresenter.detachView();
        }
        super.onDestroy();
    }

    public void login(View v){
        mPresenter.login(et_uname.getText().toString(), et_pwd.getText().toString());
    }

    @Override
    public void onShowPB(boolean show) {
        pb_show.setVisibility(show ? View.VISIBLE: View.GONE);
    }

    @Override
    public void onLoginSuccess() {
        Toast.makeText(this, "success", Toast.LENGTH_SHORT).show();
    }

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

5.关于内存泄漏,使用弱引用。关于空指针,可以使用动态代理,在attatchView()的时候, 生成Activity的代理类, 在每个Activity方法被调用之前判空下 Activity, 如果Activity存在, 就让这个代理类执行更新ui的方法, 如果被销毁了, 就啥都不做。

public abstract class BasePresenter <V extends IView> {
    private WeakReference<V> mWeakReference; //弱引用, 防止内存泄漏
    private V mProxyView;
    /**
     * 关联V层和P层
     */
    public void attach(V view){
        mWeakReference = new WeakReference<>(view);
        MvpViewHandler h = new MvpViewHandler(view);
        mProxyView = (V)Proxy.newProxyInstance(view.getClass().getClassLoader(), view.getClass().getInterfaces(), h);
    }

    /**
     * 断开V层和P层
     */
    public void detach(){
        if (isViewAttached()){
            mWeakReference.clear();
            mWeakReference = null;
        }
    }

    /**
     * @return P层和V层是否关联.
     */
    public boolean isViewAttached(){
        return null != mWeakReference && null != mWeakReference.get();
    }

    public V getView(){
        return mProxyView;
    }
    private class MvpViewHandler implements InvocationHandler{
        private IView mView;
        public MvpViewHandler(IView view){
            mView = view;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (isViewAttached()){ //如果V层没被销毁, 执行V层的方法.
                return method.invoke(mView, args);
            }
            return null;//P层不需要关注V层的返回值
        }
    }
}

6. 参考:

Android MVP Pattern

一个小例子彻底搞懂 MVP

Android MVP Presenter 中引发的空指针异常

动态代理模式-Android项目采用Mvp模式开发的一些问题

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值