Android MVP进阶

作者: 2ece9f02c806 | 来源:发表于2016-08-24 20:03 被阅读2426次

    上一篇: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高级

    相关文章

      网友评论

      • 葉深秋:代码逻辑很清晰,点赞
      • __Berial___:如果点击登录的时候需要对网络的检查呢?应该怎么写
        2ece9f02c806:@__Berial___ 可以把网络检查放到Model层

      本文标题:Android MVP进阶

      本文链接:https://www.haomeiwen.com/subject/rqjosttx.html