MVP在Android中简易易懂的实现案例

前言

最进想做了一个新的小项目,总想来点对自己来说新鲜的东西。后面看到Google老大推荐使用MVP架构,顿时觉得不试试看都不好说是羡慕它的小弟了。良心大哥也专门在Github推出了一个项目Android Architecture Blueprints,用来展示Android用各种的MVP框架,也能算了官网教程了。在网络收集资料看了一下大神们的分析后,自己动手试了试。那么就马上上手来看一看MVP究竟如何。(本文都是围绕项目中todo-mvp部分来介绍

MVP模式

先上一张图,然后根据实际的代码我们来讲讲Google是怎么看待MVP架构的


基类

首先丁一两个Base接口,分别是作为Presenter和View的基类

public interface BaseView<T> {
    // 为View设置Presenter
    void setPresenter(T presenter);
    // 初始化界面控件
    void initView(View view);
}
public interface BasePresenter {
    // 获取数据并改变界面显示,在todo-mvp的项目中的调用时机为Fragment的OnResume()方法中
    void start();
}

契约类

官方事例中加如契约类来统一管理View和Presenter。这样整个功能可以在契约类一目了然。老大就是老大,这一点还是很厉害的。事例如下:

public interface YourContract {
    interface View extends BaseView<Presenter>{
        //这里加View功能方法
        void showError();
        void showLoading();
        void Stoploading();

    }
    interface Presenter extends BasePresenter{
        // 同上
        void loatPosts(int PagerNum,boolean cleaing);
        void  reflush();
        void loadMore(int PagerNum);
    }
}

对了,其中BaseView中含方法setPresenter,该方法作用是在将presenter实例传入view中,其调用时机是presenter实现类的构造函数中。如下

public YourPresenter(Context context, YourContract.View view) {
        this.context=context;
        this.view=view;
        this.view.setPresenter(this);
    }

Activity的作用

在讲这个之前,不知道大家有没有注意到上没图中,Fragment是作为View层而Activity是作为Presenter的,有没有想过Google为什么要推荐这样做呢?

MVC中Activity的作用

按照我们之前的习惯或者说在MVC模式中,Activity是作为View层和用户打交道,接收用户数据的输入和输出的。特别是我们会在Activity的声明周期中写入一些逻辑来实现我们想要的效果。这样很方便,但是后果是我们的Activity特别的臃肿,想一想如果我们一些通用的功能每个Activity里都要写一次不是一件很痛苦的事情。
这个时候,Activity 不仅承担了 View 的角色,还承担了一部分的 Controller 角色,这样一来 V 和 C 就耦合在一起了,虽然这样写方便,但是如果业务调整的话,要维护起来就难了,而且在一个臃肿的 Activity 类查找业务逻辑的代码也会非常蛋疼,所以看起来有必要在 Activity 中,把 View 和 Controller 抽离开来,而这就是 MVP 模式的工作了。

MVP中View层的实现

至于为什么要选择Fragment作为View层的实现类。我看到网上有这两种说法,第一个原因是我们把activity作为一个全局控制类来创建对象,把fragment作为view,这样两者就能各司其职。第二个原因是因为fragment比较灵活,能够方便的处理界面适配的问题

MVP 把 Activity 中的 UI 逻辑抽象成 View 接口,把业务逻辑抽象成 Presenter 接口,Model 类还是原来的 Model。

来看一段代码就能知道Activity的作用了(毕竟代码能够一目了然):

     @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        if (savedInstanceState!=null){
            mainFragment= (MainFragment) getSupportFragmentManager().getFragment(savedInstanceState,"MainFragment");
            bookmarksfragment=(BookmarksFragment) getSupportFragmentManager().getFragment(savedInstanceState,"BookmarksFragment");
        }else {
            mainFragment=MainFragment.newInstance();
            bookmarksfragment=BookmarksFragment.newInstance();
        }
        new BookmarksPresenter(MainActivity.this,bookmarksfragment);
}

看了上面的代码大概就能知道Activity的作用是什么了。主要是作为全局的控制,负责创建View以及Presenter实例,并将二者联系起来。

View层的实现

   public class YourFragment extends Fragment implements YourContract.View {

    private YourContract.Presenter presenter;
    @Override
    public void onResume() {
        super.onResume();
        presenter.start();
    }
   @Override
       public void setPresenter(YourContract.Presenter presenter) {
           if (presenter!=null){
               this.presenter=presenter;
           }
       }
  @Override
    public void showError() {
        Snackbar.make(fab, R.string.loaded_failed,Snackbar.LENGTH_INDEFINITE)
                .setAction(R.string.retry, new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        presenter.reflush();
                    }
                })
                .show();
    }

  @Override
    public void showLoading() {
        refresh.post(new Runnable() {
            @Override
            public void run() {
                refresh.setRefreshing(true);
            }
        });
    }

    @Override
    public void Stoploading() {
        refresh.post(new Runnable() {
            @Override
            public void run() {
                refresh.setRefreshing(false);
            }
        });
    }
}

可以看到通过setPresenter方法获取到Presenter的实例。然后在Fragment的生命周期中调用presenter.start()方法。这样View层只负责数据给用户呈现他们看到的东西,而不去管具体是怎样实现的(ps:我觉得在onResume()方法之前调用应该都行吧,不知道是不是对的。如果有错,还请指教)

Presenter层的实现

public class YourPresenter implements YourContract.Presenter {
public YourPresenter(Context context, YourContract.View view) {
        this.context=context;
        this.view=view;
        this.view.setPresenter(this);
    }

@Override
    public void loatPosts(int PagerNum, final boolean cleaing) {
        //具体实现就不贴了,有点长
}
@Override
    public void start() {
        loatPosts(CurrentPagerNum,true);
    }

@Override
    public void reflush() {
        loatPosts(CurrentPagerNum,true);
    }

@Override
    public void loadMore(int PagerNum) {
        loatPosts(CurrentPagerNum+PagerNum,false);
    }
}

在构造方法中,Presenter将自身的事例传递给了View,这样View就能调用Presenter层的方法来处理业务逻辑了。在start()方法中,处理了数据加载。

Model层的实现

项目中model层最大的特点是被赋予了数据获取的职责,与我们平常model层只定义实体对象截然不同,实例中,数据的获取、存储、数据状态变化都是model层的任务,Presenter会根据需要调用该层的数据处理逻辑并在需要时将回调传入。这样model、presenter、view都只处理各自的任务,此种实现确实是单一职责最好的诠释。

总结

MVP的好处

  • 分离了视图逻辑和业务逻辑,降低耦合度,实现了Model和View真正的完全分离,可以修改View而不影响Modle
  • Activity 只处理生命周期的任务,代码变得更加简洁
  • 视图逻辑和业务逻辑分别抽象到了 View 和 Presenter 的接口中去,提高代码的可阅读性
  • View可以进行组件化。在MVP当中,View不依赖Model。这样就可以让View从特定的业务场景中脱离出来,可以说View可以做到对业务完全无知。它只需要提供一系列接口提供给上层操作。这样就可以做到高度可复用的View组件。
  • 利于测试驱动开发。Presenter 被抽象成接口,可以有多种具体的实现,所以方便进行单元测试。在使用MVP的项目中Presenter对View是通过接口进行,在对Presenter进行不依赖UI环境的单元测试的时候。可以通过Mock一个View对象,这个对象只需要实现了View的接口即可。然后依赖注入到Presenter中,单元测试的时候就可以完整的测试Presenter应用逻辑的正确性。

以上就是我这段时间来对MVP的理解了。如果有错误的地方,欢迎指教哦。

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

推荐阅读更多精彩内容