Android中的MVP模式及性能优化

来源:互联网 发布:sql求和语句如何在多表 编辑:程序博客网 时间:2024/06/10 20:58

一、MVC

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

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

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

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

二、MVP

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

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

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

这里写图片描述

(图片来源:《不要再给MVP中Prensenter写接口了》)

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下载

个人博客:帅气陈吃苹果

1 0