Android中的MVP模式及性能优化

声明:作者原创,转载注明出处。

作者:帅气陈吃苹果

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

个人博客:帅气陈吃苹果

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容