AndroidAndroid知识Android高级技术

Android MVP进阶

2016-08-24  本文已影响2426人  2ece9f02c806

上一篇:Android MVP初探

上一篇文章讲了最简单的一个Android MVP,这个只是用来让初学者了解MVP。网络上大部份讲MVP的只到那一步了@_@所以我就写了这篇进阶(还没有看上一篇的请走这里

上一篇其中存在一些问题:

一. View层ILoginView 中showLoading、hideLoading这两个方法,在其它页也会用到,比如再来一个IRegisterView 注册页面,它同样也要showLoading、hideLoading,对于这些共有的方法抽出来放到一个基础接口里,其它接口继承它,看代码:

public interface MvpView {
    /**
     * 显示loading对话框
     *
     * @param msg
     */
    void showLoading(String msg);

    /**
     * 隐藏loading对话框
     */
    void hideLoading();

    /**
     * 显示错误信息
     *
     * @param errorMsg
     */
    void showError(String errorMsg);
}

这里写一个MvpView接口,包含三个方法,这三个方法大部份页面都会有(当然还有其它共有的方法也可以往上加)
改造ILoginView如下:

public interface ILoginView extends MvpView {
    /**
     * 从UI中获取用户输入的用户名
     *
     * @return
     */
    String getUsername();

    /**
     * 从UI中获取用户输入的密码
     *
     * @return
     */
    String getPassword();

    /**
     * 显示结果
     *
     * @param result
     */
    void showResult(String result);
}

ILoginView继承了MvpView,公有的方法在MvpView里,这里只存放特有的方法。

二. Presenter层LoginPresenter存在一个隐藏 的安全隐患:
当其成员loginView对应的页面不存在了,那么在Callback的onSuccess或都onFailure方法中直接调用loginView的hideLoading和showResult方法会报空指针异常。(特别是对于Fragment这种,有时候实例还在但是View已经销毁了,如ViewPager+Fragment这种)
针对这个问题,将Presenter层改造下:

public interface Presenter<V extends MvpView> {
    /**
     * Presenter与View建立连接
     *
     * @param mvpView 与此Presenter相对应的View
     */
    void attachView(V mvpView);

    /**
     * Presenter与View连接断开
     */
    void detachView();
}

写一个基础接口Presenter它接收MvpView的子类,有两个方法,attachView,detachView分别用于与MvpView建立与断开连接。
然后写一个对Presenter的基础实现类BasePresenter:

public class BasePresenter<V extends MvpView> implements Presenter<V> {
    /**
     * 当前连接的View
     */
    private V mvpView;

    /**
     * Presenter与View建立连接
     *
     * @param mvpView 与此Presenter相对应的View
     */
    @Override
    public void attachView(V mvpView) {
        this.mvpView = mvpView;
    }

    /**
     * Presenter与View连接断开
     */
    @Override
    public void detachView() {
        this.mvpView = null;
    }

    /**
     * 是否与View建立连接
     *
     * @return
     */
    public boolean isViewAttached() {
        return mvpView != null;
    }

    /**
     * 获取当前连接的View
     *
     * @return
     */
    public V getMvpView() {
        return mvpView;
    }

    /**
     * 每次调用业务请求的时候都要先调用方法检查是否与View建立连接,没有则抛出异常
     */
    public void checkViewAttached() {
        if (!isViewAttached()) {
            throw new MvpViewNotAttachedException();
        }
    }

    public static class MvpViewNotAttachedException extends RuntimeException {
        public MvpViewNotAttachedException() {
            super("请求数据前请先调用 attachView(MvpView) 方法与View建立连接");
        }
    }
}

BasePresenter是所有Presenter的父类,它实现的Presenter接口,并增加
checkViewAttached方法用于在调用业务前检查当前Presenter是否与MvpView建立连接
isViewAttached方法用于判断是否与MvpView处于连接状态
getMvpView获取当前所连接的MvpView对象

再然后把之前的LoginPresenter改造下:

public class LoginPresenter extends BasePresenter<ILoginView> implements ILoginPresenter {

    private IUserModel userModel;

    public LoginPresenter(IUserModel userModel) {
        this.userModel = userModel;
    }

    /**
     * 登录
     */
    @Override
    public void login() {
        checkViewAttached();
        getMvpView().showLoading("登录中...");
        userModel.login(getMvpView().getUsername(), getMvpView().getPassword(), new Callback() {
            @Override
            public void onSuccess() {
                if (isViewAttached()) {
                    getMvpView().hideLoading();
                    getMvpView().showResult("登录成功");
                }
            }

            @Override
            public void onFailure(String errorMsg) {
                if (isViewAttached()) {
                    getMvpView().hideLoading();
                    getMvpView().showResult(errorMsg);
                }
            }
        });
    }
}

LoginPresenter继承自BasePresenter并实现ILoginPresenter接口。
相比之前,去掉了成员 loginView,这个通过getMvpView()直接获得。
然后就是login方法中针对空指针隐患的解决:
先调用checkViewAttached()确保View存在才往下走。
然后在Callback回调里调用isViewAttached()判断此时View是否还存在,存在就执行下面的UI操作。

这一步以后,就是LoginActivity的修改:

public class LoginActivity extends AppCompatActivity implements ILoginView, View.OnClickListener {

    private EditText username;
    private EditText password;
    private ProgressDialog progressDialog;
    private LoginPresenter presenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        username = (EditText) findViewById(R.id.username);
        password = (EditText) findViewById(R.id.password);
        findViewById(R.id.login).setOnClickListener(this);
        progressDialog = new ProgressDialog(this);
        presenter = new LoginPresenter(new UserModel());
        presenter.attachView(this);//这里与View建立连接
    }

    @Override
    protected void onDestroy() {
        presenter.detachView();//这里与View断开连接
        super.onDestroy();
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.login:
                presenter.login();
                break;
        }
    }

    /**
     * 从UI中获取用户输入的用户名
     *
     * @return
     */
    @Override
    public String getUsername() {
        return username.getText().toString().trim();
    }

    /**
     * 从UI中获取用户输入的密码
     *
     * @return
     */
    @Override
    public String getPassword() {
        return password.getText().toString().trim();
    }

    /**
     * 显示结果
     *
     * @param result
     */
    @Override
    public void showResult(String result) {
        Toast.makeText(LoginActivity.this, result, Toast.LENGTH_SHORT).show();
    }

    /**
     * 显示loading对话框
     *
     * @param msg
     */
    @Override
    public void showLoading(String msg) {
        progressDialog.setMessage(msg);
        if (!progressDialog.isShowing()) {
            progressDialog.show();
        }
    }

    /**
     * 隐藏loading对话框
     */
    @Override
    public void hideLoading() {
        if (progressDialog.isShowing()) {
            progressDialog.dismiss();
        }
    }

    /**
     * 显示错误信息
     *
     * @param errorMsg
     */
    @Override
    public void showError(String errorMsg) {
        Toast.makeText(LoginActivity.this, errorMsg, Toast.LENGTH_SHORT).show();
    }
}

这里presenter的实例化只需要传入IUserModel实例,

presenter = new LoginPresenter(new UserModel());

接着关键点来了
在onCreate方法中

presenter.attachView(this);//这里与View建立连接

在onDestroy方法中

  presenter.detachView();//这里与View断开连接

至此,针对MVP进阶改造就差不多少了,但是看看LoginActivity:
showLoading,hideLoading,showError这些公共的方法应该像MvpView一样抽出来。
于是产生BaseActivity:

public class BaseActivity extends AppCompatActivity implements MvpView {

    protected ProgressDialog progressDialog;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        progressDialog = new ProgressDialog(this);
    }

    /**
     * 显示loading对话框
     *
     * @param msg
     */
    @Override
    public void showLoading(String msg) {
        progressDialog.setMessage(msg);
        if (!progressDialog.isShowing()) {
            progressDialog.show();
        }
    }

    /**
     * 隐藏loading对话框
     */
    @Override
    public void hideLoading() {
        if (progressDialog.isShowing()) {
            progressDialog.dismiss();
        }
    }

    /**
     * 显示错误信息
     *
     * @param errorMsg
     */
    @Override
    public void showError(String errorMsg) {
        Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show();
    }
}

BaseActivity实现了MvpView接口,把这些公共的方法放这里。
然后看看LoginActivity:

public class LoginActivity extends BaseActivity implements ILoginView, View.OnClickListener {

    private EditText username;
    private EditText password;
    private LoginPresenter presenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        username = (EditText) findViewById(R.id.username);
        password = (EditText) findViewById(R.id.password);
        findViewById(R.id.login).setOnClickListener(this);
        presenter = new LoginPresenter(new UserModel());
        presenter.attachView(this);//这里与View建立连接
    }

    @Override
    protected void onDestroy() {
        presenter.detachView();//这里与View断开连接
        super.onDestroy();
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.login:
                presenter.login();
                break;
        }
    }

    /**
     * 从UI中获取用户输入的用户名
     *
     * @return
     */
    @Override
    public String getUsername() {
        return username.getText().toString().trim();
    }

    /**
     * 从UI中获取用户输入的密码
     *
     * @return
     */
    @Override
    public String getPassword() {
        return password.getText().toString().trim();
    }

    /**
     * 显示结果
     *
     * @param result
     */
    @Override
    public void showResult(String result) {
        Toast.makeText(LoginActivity.this, result, Toast.LENGTH_SHORT).show();
    }
}

瞬间清爽了有木有@_@
好了,Android MVP进阶篇就到此结束了,代码看这里

下一篇:Android MVP高级

上一篇下一篇

猜你喜欢

热点阅读