MVP项目Android资源收录MVP

Android开发中的MVP架构以及性能优化

2017-02-28  本文已影响2274人  小楠总

前言

为什么要做架构设计?
一个APP越做越大的时候,随着业务、需求越来越复杂,为了系统的扩展性更好,这时候就需要考虑架构问题。
当然,小公司里面基本不会涉及到这些,一是因为项目比较小,主要是完成功能为主,而且需求不多,需要修改的地方不多;二是很有可能你做完这个项目之后就不干了,根本就不用考虑以后的扩展。因此掌握诸如架构设计、性能优化、NDK(Java不能解决,效率低,安全性不好)、RN(兼顾了性能又能即使更新)等知识,是我们通往大公司或者在大公司中充分发挥所必须掌握的。
我们要不断地扩展自己的编程视野,不能说什么都没搞过,这样的话即使天资再厉害也不行。另外我们还需要往底层研究,否则的话容易被淘汰。

MVP概念篇

聊到MVP的时候首先我们会聊到MVC,MVC的出现就是为了解决诸如Android开发之类的有界面的编程。随着程序的功能、需求不断增加,依然要保持架构的清晰、可扩展性。MVC最先是由微软提出来的,因此我放出下面这种图:

MVC.jpeg

在MVC中:Model和View代表着业务逻辑与展示方式,Model和View往往是互相引用,改变展示方式的时候很有可能也要修改业务逻辑层,即修改Controller。

因此为了去除这种弊端,需要做解耦,我们的MVP应运而生,至于后面还有MVVM之类的,不在我们的讨论范围之内。

在MVP中,我们先看一下这三者的概念:
Model:业务逻辑,工作职责是:加载数据。
View:视图,工作职责是:控制显示数据的方式。
Presenter:中间者,绑定Model、View。

注意:在Android中,Activity往往当成是View的实现类。

MVP的架构图如下所示:

MVP.png

虽然MVP的使用比较麻烦了些,但是它的有点也是很明显的,Model和View充分解耦,修改一个不会牵扯到另外一个。
另外,视图、业务逻辑也有可能会变,因此视图、业务逻辑抽取成接口,改变不同的实现类即可。 Presenter中只持有Model和VIew的引用,可以随时更换它们的实现类,从而实现对扩展是开放的。
因此MVP相对于MVC来说,规范比较明确,在系统架构上扩展性更加强。

栗子篇

举个栗子.jpeg

好了,上面说了那么多,然并卵?没关系,我们举个栗子,上代码哈!

先瞄一眼我们整个栗子demo的项目架构,我们姑且把这个demo叫做栗子一号吧。

Demo的MVP架构.png

下面我们分步骤来介绍。

1、作为一个APP,界面的显示需要数据,因此我们需要先有数据。我们先创建一个包,专门放Model,如此类推。因为在MVP中,我们的数据以及显示都是通过接口的方式来实现的,因此我们需要创建接口:IMainModel.java,前面的大写字母I代表接口类型。
public interface IMainModel {

        void loadData(OnLoadCompleteListener listener);

        interface OnLoadCompleteListener {
        void onComplete(String data);
        }

    }

IMainModel接口主要负责加载数据,并且在加载完成的时候回调。

2、然后我们需要实现IMainModel接口,我们先做第一个版本,从本地加载数据。

        public class MainModelImpl implements IMainModel {

            @Override
            public void loadData(OnLoadCompleteListener listener) {
            String data = "我是从本地加载的数据";
            listener.onComplete(data);
            }

        }

3、接着我们需要View的接口,在这里我们定义了两个比较简单的方法,一个是加载数据的时候显示的进度条,然后是显示加载出来的数据。

        public interface IMainView {

            void showLoading();
            void showData(String data);

        }

4、在上面的概念中提到,我们的Activity是作为View的实现类的,因此Activity需要实现View的接口,并且实现View接口的抽象方法。在这里,为了简单起见,假设我们的数据是在Model中通过网络加载的,加载中的时候我们显示一个Toast,加载成功的时候我们把数据显示在TextView上。

        public class MainActivity extends AppCompatActivity implements IMainView {

            private MainPresenter mPresenter;
            private TextView tv_test;

            @Override
            protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            tv_test = (TextView) findViewById(R.id.tv_test);

            }

            @Override
            public void showLoading() {
            Toast.makeText(this, "正在拼命加载中。。。", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void showData(String data) {
            tv_test.setText(data);
            }
            
        }

5、走完上面的流程之后,我们需要有一个中间者Presenter,绑定View(Activity)以及我们的Model,如下所示。Presenter是一个类,它需要持有View以及Model的接口,而不是具体实现。在Presenter构造的时候初始化Model对象,接收View对象(Activity)。最后提供一个fetch方法,绑定二者,执行具体的业务逻辑,这里不再赘述。

        public class MainPresenter {

            private IMainModel mModel;
            private IMainView mView;

            public MainPresenter(IMainView view) {
            mModel = new MainModelImpl();
            mView = view;
            }

            public void fetch() {
            mView.showLoading();
            mModel.loadData(new IMainModel.OnLoadCompleteListener() {
                @Override
                public void onComplete(String data) {
                mView.showData(data);
                }
            });
            }

        }

6、最后,在Activity的onCreate方法中实例化我们的Presenter对象。

        public class MainActivity extends AppCompatActivity implements IMainView {

            private MainPresenter mPresenter;
            private TextView tv_test;

            @Override
            protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            tv_test = (TextView) findViewById(R.id.tv_test);

            //实例化Presenter
            mPresenter = new MainPresenter(this);
            mPresenter.fetch();
            }

            @Override
            public void showLoading() {
            Toast.makeText(this, "正在拼命加载中。。。", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void showData(String data) {
            tv_test.setText(data);
            }

        }

下面瞄一下栗子一号的效果吧:

栗子一号.png

MVP在系统扩展中的作用

上面只是介绍了MVP的基本使用,下面需要介绍的才是在项目中使用MVP的时候最屌的地方。
随着我们栗子一号的不断成长,迭代更新,发展壮大,我们的项目组提出了新的需求。

需求1:我们的数据不能在本地加载了,需要改为通过在网络中加载。

我们先分析一下MVP架构,我们加载数据的逻辑是通过Model的实现类来实现的,因此直接通过替换不同Model实现类就可以实现这一个需求。这就是面向对象的OCP原则,就是程序对修改关闭,扩展开放。
好了,废话不多说,是时候对栗子一号进行手术了。

根据我们的分析,直接创建一个新的Model实现类即可,我们起名为MainModeNetlImpl,修改loadData的业务逻辑,改为从网络加载数据,。实际项目中可能是一些很复杂的代码,所以这里为了说面MVP的优点,通过简单的栗子来说明。如果项目中没有MVP或者MVC架构的话,需要修改的时候就会比较麻烦了,MVP(MVC)的分层思想使得我们的项目整体架构比较清晰。

        public class MainModeNetImpl implements IMainModel {

            @Override
            public void loadData(OnLoadCompleteListener listener) {
            String data = "我是从网络加载的数据";
            listener.onComplete(data);
            }

        }

然后在我们的Presenter中替换掉我们的Model实现类即可,如下所示。

        public class MainPresenter {

            private IMainModel mModel;
            private IMainView mView;

            public MainPresenter(IMainView view) {
        //        mModel = new MainModelImpl();
            mModel = new MainModeNetlImpl();
            mView = view;
            }

            public void fetch() {
            mView.showLoading();
            mModel.loadData(new IMainModel.OnLoadCompleteListener() {
                @Override
                public void onComplete(String data) {
                mView.showData(data);
                }
            });
            }

        }

然后这是栗子脱变之后的栗子二号:

栗子二号.png
扩展:当然,如果你的项目经理脑抽了,改来改去,一时从网络加载,一时又觉得不好又从本地加载;或者程序在不同的情况之下需要从不同的地方加载数据。这里我们就可以使用策略模式,根据不同的情况来从不同的地方加载数据。所以我们可以在Presenter中作如下修改:
        public MainPresenter(IMainView view, boolean isFromNet) {
        if (isFromNet) {
            mModel = new MainModeNetImpl();
        } else {
            mModel = new MainModelImpl();
        }
        mView = view;
        }

好吧,也许过了几天项目经理又抽风了(嘘,不要说太大声),又要改改改!

需求2:我们的数据展示方法需要改变了,比如说要把数据展示到不同的控件上面等等,那么我们可以直接新建一个不同的Activity,实现View的接口,作出相应的修改即可,这里不再赘述了。

MVP提高篇

在上面使用MVP中会有一些问题:

问题1:每次使用都要手动newPresenter,写很多重复的代码,能否抽取出基类MVPBaseActivity,简化我们的代码?
问题2:上面的写法中会不会带来内存泄漏的问题?

针对问题2,我在这里作出一些分析:我们的View(Activity)是持有Presenter的引用的,而Presenter中又持有Model的引用,这样就会形成一条引用链,如下图所示:

引用链.png

我们的Model去加载数据的时候可能是十分耗时间的,一旦加载过程中Activity销毁了,那么就会造成内存泄漏问题。比如说我们的栗子中,正在加载数据的时候,用户觉得不爽直接按下返回键销毁Activity了,但是因为存在上面的引用链,Activity是不能够被正常被回收的。

为了模拟这一个现象,我们在Model加载数据的时候通过Handler去做延时5秒,在加载过程中旋转屏幕,使得Activity重建,代码如下所示:

        public class MainModeNetImpl implements IMainModel {

            private Handler mHandler = new Handler();

            @Override
            public void loadData(final OnLoadCompleteListener listener) {

            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                String data = "我是从网络加载的数据";
                listener.onComplete(data);
                }
            }, 5000);

            }

        }

在加载过程中旋转屏幕,Activity重建,通过Android Studio的Memory Monitor来Dump内存信息,我们可以看到,Activity的确不能够被正常回收,内存中存在两个MainActivity,如下图所示:

栗子内存快照.png
解决办法

前方高能,请系好安全带,老哥,稳!

为了解决内存泄漏的问题,我们在Activity创建的时候创建Presenter,在销毁的时候解除绑定。

        public abstract class MVPBaseActivity<V, P extends BasePresenter<V>> extends AppCompatActivity {

            protected P mPresenter;

            @Override
            protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            //创建Presenter
            mPresenter = createPresenter();
            //关联View
            mPresenter.attachView((V) this);
            }

            @Override
            protected void onDestroy() {
            super.onDestroy();
            mPresenter.detachView();
            }

            protected abstract P createPresenter();
        }

对应的BasePresenter代码如下:

        public abstract class BasePresenter<V> {

            protected WeakReference<V> mViewRef;

            public void attachView(V view) {
            mViewRef = new WeakReference<>(view);
            }

            public void detachView() {
            if (mViewRef != null) {
                mViewRef.clear();
                mViewRef = null;
            }
            }

            protected V getView() {
            return mViewRef.get();
            }

        }

通过上面的基类抽取就实现类Model与View的绑定以及解绑,即实现了两者生命周期的关联。并且在内存不足的时候,先干掉Model,后干掉View,给用户一个好的体验效果。

以上就是我们今天要讲述的MVP架构以及扩展,在这个过程当中,我们不当掌握了MVP的架构,还巩固了面向对象的OCP原则,设计模式中的策略模式,内存泄漏相关的知识等等。所以说知识是一个整体的体系,互相关联的,存在必有存在的意义。

另外,我在写博客的时候画了图,在面试的时候我们也许会经常被问到:源码,原理,机制性的东西,这时候我们该画图的时候画图,该比喻的比喻,画给面试官看,面试官一定会觉得焕然一新的。

如果觉得我的文字对你有所帮助的话,欢迎关注我的公众号:

公众号:Android开发进阶

我的群欢迎大家进来探讨各种技术与非技术的话题,有兴趣的朋友们加我私人微信huannan88,我拉你进群交(♂)流(♀)

上一篇下一篇

猜你喜欢

热点阅读