安卓MVP架构模式

来源:互联网 发布:鹰击长空2知乎 编辑:程序博客网 时间:2024/06/05 04:01

一、架构演变概述

我记得我找第一份工作时,面试官问我“android是否属于MVC架构模式,简述一下”。确实,Android的整体设计结构就是MVC的设计模式,在J2EE的开发中,使用的也是MVC模式,MVC模式是一个经典,经历了几十年的考验。Android项目中的MVC架构:

  • View:是应用程序中处理数据显示的部分,对应于layout文件下的布局文件
  • Model:业务逻辑和实体模型
  • Controllor:是应用程序中处理用户交互的部分,Activity来充当。

看似分工明确,但是也给我们带来了不少问题,如果一个页面的业务逻辑非常复杂,我们的Activty需要大量的逻辑处理代码,使得Activity既像View又像Controllor,又当爹又当妈,工作量非常大。造成代码的阅读星非常差。

为了解决这个问题,MVP模式就在Android领域诞生了。补充:我查看资料发现MVP模式最早是微软设计出来的,应用在Visual Stuido平台,不得不说微软虽然闭源,但是很niubility。

二、MVP概述

mvp

上面的图,精炼的概述了MVP架构之间的通讯流程,在android中。

  • 模型(Model):业务逻辑处理,负责处理数据的加载或者存储,比如从网络或本地数据库获取数据等;
  • 视图(View):负责界面数据的展示,与用户进行交互,就是Activity;
  • 主导器(Presenter):相当于协调者,是模型与视图之间的桥梁,将模型与视图分离开来。通过Presenter进行它们之间的交互,隔离了M、V之间的直接交互。

这样的架构,就让我们的Activity更像View,降低了M、V之间的耦合度。

三、MVP实践

我们先看下我们的效果图:

logo

这是一个简答的登陆页面,在这个登陆页面中,我们就触发一个事件,就是点击登陆事件。我们先看看我们的工程组织架构。

structure

我们分别创建了:bean、model、presenter、view四个包,为了解放Activity,我们把以前Activity里的大量逻辑进行拆分。

1、bean包

我们创建实体UserBean,用来存放我们的用户信息,这个没什么说的。

    public class UserBean implements Parcelable{        private String name;        private String password;        public UserBean(){        }        public String getName() {            return name;        }        public void setName(String name) {            this.name = name;        }        public String getPassword() {            return password;        }        public void setPassword(String password) {            this.password = password;        }        @Override        public int describeContents() {            return 0;        }        @Override        public void writeToParcel(Parcel dest, int flags) {            dest.writeString(name);            dest.writeString(password);        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

2、view包

我们首先从View(视图层)看,view包下存放了我们视图层中的操作,它不涉及任何业务逻辑,主要是针对我们的页面进行数据的获取与设置。我们针对这个登陆功能,进行分析:

  • 姓名和密码的获取
  • 姓名和密码的保存

我们抽取出页面View层需要做的事情,接下来我们定义一个IUserView接口,里面包含我们抽取出来的方法。

    /**     * 抽离View层,用于View页面的数据获取之类     * @author Administrator     *     */    public interface IUserView {        public String getUserName();        public String getUserPsd();    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

我们只进行了获取,没有做保存。总结:View层的任务就是抽象页面的数据,提取出来写成方法。

3、model包

在view层,我们的数据获取有了着落,现在我们就开始处理我们的业务逻辑。根据页面得知,我们就模拟一个登陆功能,所以它就一个登陆事件。我们创建IUserModel接口用于包含我们处理的业务逻辑。

    /**     * 业务逻辑处理     * @author Administrator     *     */    public interface IUserModel {        /**         *提取的一个登陆方法,当然还可以有其它方法,比如获取数据,保存用户信息之类         * @param name  用户名         * @param pwd   密码         * @param loginListener 登陆监听         */        public void login(String name,String pwd,ILoginListener loginListener);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

有了业务逻辑的接口规范,我们还需要去实现它,给与我们想要的逻辑。所以就有了UserModel类:

    public class UserModel implements IUserModel{        @Override        public void login(String name, String pwd, ILoginListener loginListener) {            if(TextUtils.isEmpty(name) || TextUtils.isEmpty(pwd)){                loginListener.onError();                return;            }            try {                Thread.sleep(3000);            } catch (InterruptedException e) {                e.printStackTrace();            }            if("dsw".equals(name) && "123".equals(pwd)){                loginListener.onSucess();            }else{                loginListener.onFail();            }        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

我们为了方便我们对业务逻辑处理的监听,所以我们创建了一个接口,用来判断我们的登陆事件。

    /**     * 登陆接口的监听,方便我们在View层中控制,便于给出提示     * @author Administrator     *     */    public interface ILoginListener {        //登陆成功        public void onSucess();        //登陆失败        public void onFail();        //数据不完成        public void onError();    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

我们用来监听业务处理的判断,比如成功了我们弹出一个Toast之类的。

4、presenter包

数据有了,对数据的业务处理也有了,这里我们的Presenter就闪灵登场了。记住是Presenter、Presenter、Presenter(重要的东西说三次)。它用来连接我们的M、V层,让二者打通任督二脉实现整个流程。所以为了实现二者的联系,它的内部必然会有M、V的实例。来看看IUserPresenter:

    public class IUserPresenter {        //数据源        private IUserView userView;        //处理业务逻辑        private IUserModel userModel;        public IUserPresenter(IUserView userView){            this.userView = userView;            userModel = new UserModel();        }        /**         * 登陆方法,进行M,V层的关系建立         * @param loginListener         */        public void login(ILoginListener loginListener){            userModel.login(userView.getUserName(), userView.getUserPsd(), loginListener);        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

我们只需要在Activity中创建一个IUserPresenter类,然后调用login方法进行登录即可。在这段代码中,IUserView是我们的数据源,我们通过它的方法用于从页面获取数据,所以我们的Activity必须实现这个IUserView接口,然后传递给IUserPresenter。在IUserPresenter中调用IUserModel的实现业务逻辑的处理。

5、最后的Activity处理

    public class MainActivity extends Activity implements IUserView {        private EditText et_name,et_pwd;        private Button btn_login;        //指示器与View层进行交互        private IUserPresenter userPresenter;        @Override        protected void onCreate(Bundle savedInstanceState) {            super.onCreate(savedInstanceState);            setContentView(R.layout.activity_main);            et_name = (EditText) findViewById(R.id.et_name);            et_pwd = (EditText) findViewById(R.id.et_psw);            btn_login = (Button) findViewById(R.id.btn_login);            userPresenter = new IUserPresenter(this);            btn_login.setOnClickListener(new OnClickListener() {                @Override                public void onClick(View v) {                    userPresenter.login(loginListener);                }            });        }        @Override        public String getUserName() {            return et_name.getText().toString();        }        @Override        public String getUserPsd() {            return et_pwd.getText().toString();        }        /**         * 登陆监听接口         */        private ILoginListener loginListener = new ILoginListener() {            @Override            public void onSucess() {                Toast.makeText(getApplication(), "登陆成功", Toast.LENGTH_SHORT).show();            }            @Override            public void onFail() {                Toast.makeText(getApplication(), "登陆失败", Toast.LENGTH_SHORT).show();                    }            @Override            public void onError() {                Toast.makeText(getApplication(), "数据不完整,请重新输入", Toast.LENGTH_SHORT).show();            }        };    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

在activity中,我们实现IUserView接口,来实现该view层的数据获取。然后创建IUserPresenter类,调用login方法进行登录。同时实现对业务逻辑的处理监听。

至此,我们就完成了整个的流程,看看我们的效果图:

mvpresult

源码本来打算上传的熬csdn的,结果上传不了,所以果断github 
强烈建议大家手动敲一遍代码,加深理解。

总结图:

conclusion

MVP的优点

1、模型与视图完全分离,我们可以修改视图而不影响模型 
2、可以更高效地使用模型,因为所有的交互都发生在一个地方——Presenter内部 
3、我们可以将一个Presenter用于多个视图,而不需要改变Presenter的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁。 
4、如果我们把逻辑放在Presenter中,那么我们就可以脱离用户接口来测试这些逻辑(单元测试)

MVP的缺点

由于对视图的渲染放在了Presenter中,所以视图和Presenter的交互会过于频繁。还有一点需要明白,如果Presenter过多地渲染了视图,往往会使得它与特定的视图的联系过于紧密。一旦视图需要变更,那么Presenter也需要变更了。比如说,原本用来呈现Html的Presenter现在也需要用于呈现Pdf了,那么视图很有可能也需要变更。

========================================================== 
作者:mr_dsw

转载注明出处,分享是进步的源泉。

==========================================================

原创粉丝点击