从最简单的Android MVP讲起

mvp是一个老生常谈的话题了,网上太多讲MVP的文章了。但有的文章不是结合了rxjva,retrofit等开源项目,就是讲的太过复杂。所以我会写一个最简单的mvp demo。来帮助大家理解mvp的本质。

大多数时候,问题都可以拆解为,WHTA,WHY,HOW;什么是MVP,为什么使用MVP,如何使用MVP。

WHAT:先说什么是MVP,

20150622212916054.jpg

上面这张图是网上找的,其实这张图已经很明显了,MVP和MVC的区别,在于以前的View层不仅要和model层交互,还要和controller层交互。而在mvp中,view层只和presenter层交互,而model层也和presenter交互,presenter构成了view层和model层的桥梁,也解耦了view层和model层。这一点很关键。我认为,这也是mvp的本质:

解耦view层和model层,让view层和model层通过presenter层进行通信。换个说法就是让诸如网络请求,数据库读写的逻辑,从activity中剥离出来。activit只负责页面的展示,不关心model层的逻辑。

WHY:之后来谈谈为什么要使用mvp

如果大家阅读过github上的一些开源的android项目,例如telegram,一款即时通讯软件,如果你对它不了解,你可以看一下我之前写的一篇文章在Android Studio上编译自己的Telegram;或者TweetLanes,一个第三方的功能完整的Twitter客户端。这些开源项目都存在一个问题,就是一个activity文件中一两千行,甚至三四千行代码。当然,我想这个在android项目中并不是一个奇怪的现象,当你的项目足够复杂,没有什么是不可能。在基于传统android架构的mvc模式中。model层很多时候只是一个bean类。而view层只是一个xml文件,controller层也就是activity层几乎承担了诸如网络请求,数据库,更新UI等所有的工作,全在activity里完成。这也就导致了activity文件十分庞大臃肿。但是,问题接踵而至。多人维护这样的一个项目是很痛苦的,一个几千行的activity,假如某个人改动了其中的一行,可能会导致其他修改他的人非常痛苦,因为牵扯太多的逻辑了。所以,人们开始尝试把几千行的代码,分成很多模块。网络请求放在一个模块,UI更新放在一个模块,其他的东西放另一个模块。之后就有了MVP。

HOW:在android中如何使用MVP模式

这里我新建了一个项目,项目里有3个class和一个interface,这就是最简单的mvp模式了。


main.png

用到的第三方框架只有常用的okhttp,AndroidManifest里只申请了一个Internet的权限,这里就不放出来了。


xml.png

MainActivity的xml文件也很简单,最上面有个edittext,输入你要请求的网址,中间有个button,点击开始请求,下面有个textview,展示你请求到的内容。大概的逻辑就是这样。这样的逻辑如果是按以前的写法,很简单,几行代码就能搞定,但我现在会用mvp来写。
之前说要剥离逻辑,那么就先从activity谈起。activity在mvp模式中,只是负责界面的展示,其他逻辑放在其他类里,那么怎么进行通信呢?通过接口就行了。所以我们这里先定义一个activity的ui逻辑接口。
public interface MainCallBack {
    void getMessage(String message);
    void error();
}

之后model层获取的数据,将会通过接口传递到activity中,进行展示。从某种意义上来说,接口其实就是mvp中的view层。因为activity只是这个接口的实现而已。那么我们接下来看一下model层是怎么写的。

public class MainModel {
    public Call getData(String url) {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url(url).build();
        return client.newCall(request);
    }
}

同样很简单,这个应该只是okhttp的基础用法。这里我们没有调用enqueue方法传入callback,是为了之后在presenter中使用,当然你也可以在这里调用,不过这样一来,你又要在model中声明一个接口。好了,“view层”和model层都有了,那么最后只要再有presenter层,那么mvp就完成了。

public class MainPresenter {
    private MainCallBack callBack;
    private MainModel model;

    public MainPresenter(MainCallBack callBack) {
        this.callBack = callBack;
        model=new MainModel();
    }

    public void getUrlData(String url){
        model.getData(url).enqueue(new Callback() {
            @Override
            public void onFailure(@NonNull Call call, @NonNull IOException e) {
                callBack.error();
            }

            @Override
            public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
                callBack.getMessage(response.body().string());
            }
        });
    }

}

这是一个典型的mvp模式中的presenter层:接口通过构造函数传入,model直接在构造函数中实例化。presenter中调用model的getdata方法,并传入对应的参数,之后在okhttp的回调中调用对应的ui接口,那么一次mvp模式下的网络请求就完成了。
具体操作可以看一下activity中的代码:

public class MainActivity extends AppCompatActivity implements MainCallBack {
    private TextView resultTextView;
    private MyHandler handler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = findViewById(R.id.btn);
        handler = new MyHandler(this);
        final EditText editText = findViewById(R.id.et_url);
        resultTextView = findViewById(R.id.tv_result);
        final MainPresenter presenter = new MainPresenter(this);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String url = editText.getText().toString();
                presenter.getUrlData(url);
            }
        });

    }

    @Override
    public void getMessage(String message) {
        Message msg = handler.obtainMessage(0, message);
        handler.sendMessage(msg);
    }

    @Override
    public void error() {
        Message msg = handler.obtainMessage(1, "error");
        handler.sendMessage(msg);
    }

    private static class MyHandler extends Handler {
        private WeakReference<MainActivity> reference;

        private MyHandler(MainActivity activity) {
            reference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            MainActivity activity = reference.get();
            switch (msg.what) {
                case 0:
                    activity.resultTextView.setText(msg.obj.toString());
                    break;
                case 1:
                    activity.resultTextView.setText(msg.obj.toString());
                    break;
            }
        }
    }
}

运行结果如下两图:


success.png

error.png

当网址错误的时候我直接在textview中显示“error”,当然你也可以自由选择显示什么。因为okhttp的回调是在子线程完成的,要刷新ui必须在主线程刷新,所以我这里写了一个handler。当然你也可以在model层进行线程调度。比如一个基于okhttp的开源框架,okgo就是这么做的


okgo.png

他在初始化的时候声明了一个主线程的handler,之后调用handler的post方法,完成从子线程到主线程的切换。当然这都是后话了。
看完这些, 你应该了解了mvp是怎么样的一回事。万变不离其宗,在此基础上扩展,你可以加入任何你喜欢的框架, rxjava,retrofit等。
或许看到这里还是会有人有疑问,我在一个activity中可以完成的事,写成这么几个类有必要吗?虽然我在前面已经解释过了,但我还是会结合我工作中的例子再解释一遍:我之前曾参与开发过一个和机票有关的app,如果有人接触过就知道,机票类app十分的繁杂。流程繁多,规则复杂,而且不允许出错。订票,退票,改签,实名认证,乘机人等,都有一系列的相当复杂的流程。以提交订单为例子,你可能需要提交很多东西,提交退票申请、提交订票申请、提交改签申请等等。逻辑都是先进行一系列检测和判断,弹出dialog框,把数据发送到服务器端,之后视服务器返回结果,进行跳转或者再弹出dialog框。如果你和之前一样,把所有逻辑写在一个activity里面,那么不同的提交,是写在不同的activity类里面的,为了省时间,大概很多人都会选择复制黏贴重复的逻辑。但在这个过程中,可能会产生很多问题,比如你又复制漏了一段代码之类的。如果使用mvp的话,只需要每个activity类都继承相同的ui接口,或者使用相同的model和presenter,那么最后产生的效果其实是一样的。但这样可以让代码更容易维护,修改一个地方的时候,所有地方就都变了。

那么,是否所有地方都需要mvp呢?答案是否定的。我认为,设计模式的关键在于用在正确且合适的地方。我在一个项目中,可能会同时使用mvp,mvc,mvvm。因为我知道这里应该用什么,如果一个逻辑不具有复用的可能,同时也非常简单,那么使用mvp就是多此一举。如果一个页面,带有id的view非常多,那么使用mvvm也就是android的databinding特性可以节约你大量的时间。

最后给出几点使用mvp的建议,经供参考:

1.因为mvp会存在大量的接口,所以当你设计接口的时候,务必深思熟虑,因为一个接口的改动,会导致所有继承了接口的类都会发生变动
2.不要在接口中声明接口,或者在接口中声明抽象类。你可能会觉得这样让逻辑更紧密,代码更好读。但其实这样一来反而违背了mvp解耦的本质,让代码更加复杂了。你可以把这样的代码拆解成多个接口和多个model。因为一个presenter中可以有多个model,也可以传入多个interface。同样,一个activity也可以继承多个interface,并声明多个presenter来完成多个逻辑。
3.对view层的接口,model和presenter都进行一定程度的继承,也就是每个view的接口,每个model和presenter都有一个父类,完成一些基础逻辑。这样可以当你必须要对整体进行变动的时候,改动起来要容易一些。
4.不要为了继承而继承,我见过有的人过度架构,代码里面甚至出现了空的接口和空的抽象类,这样做毫无意义。代码是为逻辑服务的,并且是现有的功能逻辑,为了不知道哪个版本会产生的改变,而预留一堆空接口和空抽象类,在我看来毫无意义。
5.适当的参考一些现有的成熟的mvp项目,看看别人是如何设计接口以及整体逻辑的,这会有一定的帮助。但完全照搬是不可取的。因为mvp是一种十分依赖项目的架构,其他项目上的mvp设计不一定适合你的项目。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,277评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,777评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,946评论 0 245
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,271评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,636评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,767评论 1 221
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,989评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,733评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,457评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,674评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,155评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,518评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,160评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,114评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,898评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,822评论 2 280
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,705评论 2 273

推荐阅读更多精彩内容