Android中的MVP模式及性能优化

作者: 帅气陈吃苹果 | 来源:发表于2017-09-18 15:51 被阅读150次

    声明:作者原创,转载注明出处。欢迎指正,不接受批评。

    作者:帅气陈吃苹果

    一、MVC

    Model:模型,处理业务逻辑。

    View:视图,呈现用户界面。

    Controller:控制器,处理用户交互。

    这里写图片描述这里写图片描述
    (图片来源:MVC图片

    二、MVP

    Model:模型,处理业务逻辑。

    View:视图,呈现用户界面。

    Presenter:中间者,负责调控View和Model之间的交互。

    这里写图片描述这里写图片描述

    MVP是MVC模式经过改良演变而来,二者都是用来分离UI、数据、业务和UI逻辑和的软件开发模式,controller/presenter负责交互的处理,model负责提供数据和逻辑处理,view负责显示和接收数据。

    区别是:MVP模式中,View和Model不直接进行交互,而是采用Presenter这个中间者,通过绑定View和Model的接口,进行间接的交互。而在MVC中,View和Model是可以直接进行通信的。

    三、MVP For Android

    架构的意义之一在于,让应用程序提高可扩展性。

    大部分的Android应用采用的都是如下的开发模式:

    这里写图片描述这里写图片描述

    (图片来源:《Amdrid MVP详解(上)》

    Activity既承担着View显示用户界面的任务,又包含了Controller处理业务逻辑的任务,因此Android中的MVC并不严格。

    当项目规模大到一定程度,Activity就会像一个臃肿的胖子,行动不便。

    行动不便体现在,当项目需求变更时,由于View和Model之间耦合度过高,导致代码改动变得复杂而庞大,不利于项目的功能扩展,也不便于进行单元测试。

    在Android中,UI是线程不安全的,也就是只能在MainThread中才能进行UI更新,所以对View和Model的分离是合理的。

    四、示例

    这个示例采用我上一篇博客《Bmob后端云初体验》的Demo,使用Bmob后端云实现一个登陆注册的例子。如果你感兴趣可以点击阅读,当然,不读也没多大影响。

    首先,先看一下项目结构:

    这里写图片描述这里写图片描述

    1.MyUser.class

    public class MyUser extends BmobObject {
        //用户名
        private String userName;
    
        //密码
        private String userPwd;
    
        public MyUser() {
    
        }
    
        public MyUser(String name, String pwd) {
            this.userName = name;
            this.userPwd = pwd;
        }
    
        public String getUserName() {
            return this.userName;
        }
    
        public void setUserName(String userName) {
            this.userName = userName;
        }
    
        public String getUserPwd() {
            return this.userPwd;
        }
    
        public void setUserPwd(String userPwd) {
            this.userPwd = userPwd;
        }
    }
    

    2.IUserView.class

    对用户输入进行数据抽象,得到View的接口。

    public interface IUserView {
    
    /**
     * 获取用户输入的用户名
     */
    String getUserName();
    
    /**
     * 获取用户输入的密码
     */
    String getUserPwd();
    
    /**
     * 加载进度对话框
     */
    void showLoading();
    
    /**
     * 隐藏进度对话框
     */
    void hideLoading(); 
    }
    

    3.IUserModel.class

    对需要用到的数据进行抽象,得到Model的接口,通过回调的方式进行过程的判断(在这里体现为,当用户注册或登陆时,可分为操作开始、操作成功、因用户原因导致的操作失败、因系统原因导致的操作失败四个过程)。

    public interface IUserModel {
    
        /**
         * 用户登录
         * @param name
         * @param pwd
         * @param listener
         */
        void checkUser(String name,String pwd,OnUserOperationListener listener);
    
        /**
         * 用户注册
         * @param name
         * @param pwd
         * @param listener
         */
        void registerUser(String name,String pwd,OnUserOperationListener listener);
    
        interface OnUserOperationListener {
    
            /**
             * 操作开始
             */
            void onOperationBegin();
    
            /**
             * 操作成功
             */
            void onSuccess();
    
            /**
             * 因用户原因,导致操作失败
             */
            void onUserFailed();
    
            /**
             * 因系统原因,导致操作失败
             */
            void onSysFailed();
        }
    }
    

    4.IUserModelImpl.class

    接着对Model接口进行实现,其中涉及到Bmob后端云的数据服务,可以看成是在这里进行业务逻辑的具体操作(包括网络请求、后台进程、数据加载等)。

    public class IUserModelImpl implements IUserModel {
    
        /**
         * 由model进行具体的业务逻辑操作,检查用户名和密码
         * @param name
         * @param pwd
         */
        @Override
        public void checkUser(String name, String pwd, final OnUserOperationListener listener) {
            //开始检查过程
            listener.onOperationBegin();
            BmobQuery<MyUser> userQuery = new BmobQuery<MyUser>();
            userQuery.addWhereEqualTo("userName",name);
            userQuery.addWhereEqualTo("userPwd",pwd);
            userQuery.findObjects(new FindListener<MyUser>() {
                @Override
                public void done(List<MyUser> list, BmobException e) {
                    if(e == null) {
                        if(list.size() == 1) {
                            listener.onSuccess();
                        } else {
                            listener.onUserFailed();
                        }
                    } else {
                        listener.onSysFailed();
                    }
                }
            });
        }
    
        @Override
        public void registerUser( String name, String pwd, final OnUserOperationListener listener) {
            listener.onOperationBegin();
            MyUser mUser = new MyUser();
            mUser.setUserName(name);
            mUser.setUserPwd(pwd);
            mUser.save(new SaveListener<String>() {
                @Override
                public void done(String s, BmobException e) {
                    if(e == null) {;
                        listener.onSuccess();
                    } else {
                        listener.onSysFailed();
                    }
                }
            });
        }
    }
    

    5.UserPresenter.class

    然后创建一个中间者Presenter,持有View和Model的引用,通过对View和Model的绑定,将原本在View中的那些繁杂的操作指定给Model去实现,而不是View直接与Model进行交互。

    public class UserPresenter extends BasePresenter<IUserView>{
    
        //model
        private IUserModel mUserModel;
    
        //view
        private IUserView mUserView;
    
        /**
         * 实例化view
         * @param mUserview
         */
        public UserPresenter(IUserView mUserview) {
            super();
            this.mUserModel = new IUserModelImpl();
            this.mUserView = mUserview;
        }
    
        /**
         * bind view and model for user login
         *
         * @param context   上下文环境
         * @param name  用户名
         * @param pwd   密码
         */
        public void check(final Context context, String name, String pwd) {
            //显示进度对话框
            mUserView.showLoading();
            if(mUserModel != null) {
                mUserModel.checkUser(name, pwd, new IUserModel.OnUserOperationListener() {
                    @Override
                    public void onOperationBegin() {
                    }
    
                    @Override
                    public void onSuccess() {
                        mUserView.hideLoading();
                        Toast.makeText(getApplicationContext(),
                                "登录成功!",
                                Toast.LENGTH_SHORT).show();
                        Intent intent = new Intent(getApplicationContext(),MainActivity.class);
                        context.startActivity(intent);
                    }
    
                    @Override
                    public void onUserFailed() {
                        mUserView.hideLoading();
                        Toast.makeText(getApplicationContext(),
                                "用户名或密码错误,请重新输入!",
                                Toast.LENGTH_SHORT).show();
                    }
    
                    @Override
                    public void onSysFailed() {
                        mUserView.hideLoading();
                        Toast.makeText(getApplicationContext(),
                                "登录失败,请检查网络设置!",
                                Toast.LENGTH_SHORT).show();
                    }
                });
            }
        }
    
        /**
         * bind view and model for user register
         *
         * @param context
         * @param name
         * @param pwd
         */
        public void register(final Context context,String name,String pwd) {
            //显示进度对话框
            mUserView.showLoading();
            if(mUserModel != null) {
                mUserModel.registerUser(name, pwd, new IUserModel.OnUserOperationListener() {
                    @Override
                    public void onOperationBegin() {
                    }
    
                    @Override
                    public void onSuccess() {
                        mUserView.hideLoading();
                        Toast.makeText(getApplicationContext(),
                                "注册成功!",
                                Toast.LENGTH_SHORT).show();
                        ((Activity) context).finish();
                    }
    
                    @Override
                    public void onUserFailed() {
                        mUserView.hideLoading();
                        Toast.makeText(getApplicationContext(),
                                "用户名或密码不合法,请重新输入!",
                                Toast.LENGTH_SHORT).show();
                    }
    
                    @Override
                    public void onSysFailed() {
                        mUserView.hideLoading();
                        Toast.makeText(getApplicationContext(),
                                "你可能长得太丑,网络都看不下去了 ^_^ ",
                                Toast.LENGTH_SHORT).show();
                    }
                });
            }
        }
    }
    

    6.LoginActivity.class

    这里写图片描述这里写图片描述

    Activity就是View层中很典型的一个体现,所以要让他实现抽象出来的View接口。在View层中,只与中间者Presenter进行交互。

    public class LoginActivity extends BaseActivity<IUserView,UserPresenter> implements View.OnClickListener,IUserView{
    
        private EditText editName;
    
        private EditText editPwd;
    
        private Button btnLogin;
    
        private Button btnToRegister;
    
        //进度对话框
        ProgressDialog progressDialog;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            //初始化BmobSDK,
            Bmob.initialize(this, "4fd01c1c4eaca3d97c85e36494554549");
            setContentView(R.layout.activity_login);
            initView();
        }
    
        /**
         * 控件初始化
         */
        private void initView() {
            editName = (EditText) findViewById(R.id.edit_login_name);
            editPwd = (EditText) findViewById(R.id.edit_login_pwd);
            btnLogin = (Button) findViewById(R.id.btn_login);
            btnToRegister = (Button) findViewById(R.id.btn_to_register);
            btnLogin.setOnClickListener(this);
            btnToRegister.setOnClickListener(this);
    
            progressDialog = new ProgressDialog(this);
            progressDialog.setTitle("登陆");
            progressDialog.setMessage("正在登陆...");
            progressDialog.setCancelable(false);
        }
    
        /**
         * 重写按钮的点击事件
         * @param view
         */
        @Override
        public void onClick(View view) {
            switch (view.getId()) {
                case R.id.btn_login:
                    userLogin();
                    break;
                case R.id.btn_to_register:
                    toRegister();
                    break;
                default:
                    break;
            }
        }
    
        /**
         * 去注册
         */
        private void toRegister() {
            Intent intentReg = new Intent(LoginActivity.this,RegisterActivity.class);
            startActivity(intentReg);
        }
    
        /**
         * 登陆
         */
        private void userLogin() {
            //初始化中间者
            mPresenter = new UserPresenter(this);
            //通过中间者进行用户名和密码的检查
            mPresenter.check(this,getUserName(),getUserPwd());
        }
    
        @Override
        public String getUserName() {
            return editName.getText().toString();
        }
    
        @Override
        public String getUserPwd() {
            return editPwd.getText().toString();
        }
    
        @Override
        public void showLoading() {
            progressDialog.show();
        }
    
        @Override
        public void hideLoading() {
            progressDialog.hide();
        }
    
        @Override
        protected UserPresenter createPresenter() {
            return new UserPresenter(this);
        }
    
    }
    

    因为Presenter是用过View和Model的接口对View、Model进行访问的,它持有他们的引用。

    存在这样一种情况,当Activity通过Presenter在Model进行业务逻辑的具体实现操作时,很可能这些操作是耗时的,假设有一个耗时长达5s的操作,而在这5s里,如果Activity被销毁(用户离开此界面),而Presenter还持有对View的引用,就会造成内存泄漏了。

    如果不对这个问题进行处理,当一个应用有很多个Activity,假设一个Activity需要进行十个耗时操作,那么将严重降低应用的性能。

    所以,需要让Activity继承一个父类BaseActivity,在这个BaseActivity中,当onCreate()方法执行,则关联Presenter,当onDestroy()执行,则解除对Presenter的关联,让Presenter继承一个BasePresenter,当系统内存不足时,优先释放Model,而不是View,这样用户体验才好。就好像一个爱臭美的人要被打时说,有事好商量,别打脸行么。当敌人来势不汹,门面重要。

    7.BaseActivity.class

    public abstract class BaseActivity<V,T extends BasePresenter<V>> extends AppCompatActivity {
    
        protected T mPresenter;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_base);
            //创建Presenter
            mPresenter = createPresenter();
            //关联View
            mPresenter.attachView((V) this);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            //解除关联
            mPresenter.detachView();
        }
    
        protected abstract T createPresenter();
    }
    

    8.BasePresenter.class

    public abstract class BasePresenter<T> {
    
        //当内存不足时,释放内存
        protected WeakReference<T> mViewRef;
    
        /**
         * bind view with presenter
         * @param view
         */
        public void attachView(T view) {
            mViewRef = new WeakReference<T>(view);
        }
    
        public void detachView() {
            if(mViewRef != null) {
                mViewRef.clear();
                mViewRef = null;
            }
        }
    
        protected T getView() {
            return mViewRef.get();
        }
    }
    

    9.RegisterActivity.class

    这里写图片描述这里写图片描述

    可以看到,在这里,我们的登录界面和注册界面所需要的数据时一样的,那么,可以看出MVP的优势之一:

    当项目需求变动,如数据的展现方式不一样而数据本身不存在变动时,我们只需要新建一个Activity或Fragment,在这个Activity或Fragment里同样对Presenter进行关联就可以了,而Presenter层和Model层的代码都不需要变动,这就是可扩展性的体现。

    public class RegisterActivity extends BaseActivity<IUserView,UserPresenter> implements IUserView{
    
        private EditText editName;
    
        private EditText editPwd;
    
        private Button btnRegister;
    
        private UserPresenter mUserPresenter;
    
        //进度对话框
        ProgressDialog progressDialog;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_register);
            initView();
        }
    
        /**
         * 控件初始化
         */
        private void initView() {
            editName = (EditText) findViewById(R.id.edit_register_name);
            editPwd = (EditText) findViewById(R.id.edit_register_pwd);
            btnRegister = (Button) findViewById(R.id.btn_register);
            btnRegister.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    userRegister();
                }
            });
    
            progressDialog = new ProgressDialog(this);
            progressDialog.setTitle("注册");
            progressDialog.setMessage("正在注册...");
            progressDialog.setCancelable(false);
        }
    
        /**
         * 用户注册,即添加一行数据
         */
        private void userRegister() {
            mUserPresenter = new UserPresenter(this);
            mUserPresenter.register(this,getUserName(),getUserPwd());
        }
    
        @Override
        public String getUserName() {
            return editName.getText().toString();
        }
    
        @Override
        public String getUserPwd() {
            return editPwd.getText().toString();
        }
    
        @Override
        public void showLoading() {
            progressDialog.show();
        }
    
        @Override
        public void hideLoading() {
            progressDialog.hide();
        }
    
        @Override
        protected UserPresenter createPresenter() {
            return new UserPresenter(this);
        }
    }
    

    10.MainActivity.class

    这里写图片描述这里写图片描述

    登录之后的主界面就是显示一段文本,没什么特别的。

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    }
    

    总结:

    MVP For Android对View层和Model实现了解耦,有利于提高应用的扩展性、健壮性,便于进行单元测试,但增加了很多代码量。

    除了MVP,还有MVVM有待学习。

    不同的项目有不同的业务需求,要根据具体需求和项目规模进行开发模式的选择。

    源码下载:Github下载

    个人博客:帅气陈吃苹果

    相关文章

      网友评论

        本文标题:Android中的MVP模式及性能优化

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