Android学习笔记19 框架模式MVC MVP与MVVM

MVC、MVP、MVVM,作为Android开发中重要的三种框架模式,能够理解并能较好地将其运用在自己的项目中是很重要的。本文主要是自己对Android中这三种框架模式的学习总结,其中重点介绍了MVP模式,如有错误或者表达不当之处,欢迎指出。

一、概述
二、MVC
三、MVP
   1.简介
   2.示例
   3.改进
四、MVVM
五、参考

一、概述

设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。常见的有单例模式,适配器模式,代理模式等等。MVC,MVP等等不属于设计模式,而是框架模式,那么框架模式与设计模式的区别是什么呢。

简单来说,框架模式是一种思想,框架是框架模式的具体实现,是对代码的重用,而设计模式是设计重用。设计模式是对在某种环境中反复出现的问题以及解决该问题的方案的描述,它比框架更抽象;框架可以用代码表示,也能直接执行或复用,而对模式而言只有实例才能用代码表示;设计模式是比框架更小的元素,一个框架中往往含有一个或多个设计模式,框架总是针对某一特定应用领域,但同一模式却可适用于各种应用。可以说,框架是软件,而设计模式是软件的知识。

二、MVC

MVC全称是Model-View-Controller,即模型-视图-控制器,它用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC三部分具体如下:

  • Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。  
    通常模型对象负责从网络中或者在数据库中存取数据。

  • View(视图)是应用程序中处理数据显示的部分。  
    通常视图是依据模型数据创建的。

  • Controller(控制器)是应用程序中处理用户交互的部分。  
    通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。

MVC的目的主要是将数据模型和视图分离开,并以控制器作为连接二者的桥梁以实现解耦,下面这张图很清楚地展示了整体结构:

在Android开发中,MVC的应用比较经典,比如layout文件夹下的各种布局文件就对应于view层,从网络或者数据库获取数据就对应于model层,controller层就是各种activity。

三、MVP

MVP,全称是Model-View-Presenter,它针对MVC做了一些改进,解除了Android中View和Model的耦合,使得写出的代码逻辑更加清晰,可扩展性更好。

简介

在Android中,传统的MVC中的View,对应的是各种Layout布局文件,但是这些布局文件中并不像Web端那样强大,能做的事情非常有限。Controller对应的是Activity,但是在实际的项目中也会有很多UI操作在这一层,做了很多View中应该做的事情,当然Controller中也包含Controller应该做的事情,比如各种事件的派发回调,而且在一层中我们会根据事件再去调用Model层操作数据,所以这种MVC的方式在实际项目中,Activity所在的Controller是非常重的,各层次之间的耦合情况也比较严重,不方便单元测试。

MVC的进化版——MVP把Layout布局和Activity作为View层,增加了Presenter,Presenter层与Model层进行业务的交互,完成后再与View层交互(也就是Activity)进行回调来刷新UI。这样一来,所有业务逻辑的工作都交给了Presenter中进行,使得View层与Model层的耦合度降低,Activity中的工作也进行了简化。下面通过一个简单的例子来展示一些它的用法,当然想要灵活地掌握MVP这种框架模式还是需要不断地实践的。

示例

在这个例子中,我们的主要功能是在一个Activity中获取新闻数据,并将其展示在页面上,下面是几部分主要的代码。

  • Model层

1、接口NewsModel

public interface NewsModel {

    //给定新闻数据的url获取数据
    void getNewsFromNet(String url, NewsModelImpl.NewsCallBack newsCallBack);
}

2、NewsModelImpl实现数据获取


public class NewsModelImpl implements NewsModel {

    private String newsData;

    @Override
    public void getNewsFromNet(final String url, final NewsCallBack newsCallBack) {

        //模拟获取数据耗时操作
        new Thread() {
            @Override
            public void run() {

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
               //模拟从url获取数据
                newsData = url + "湄公河票房突破十亿 1天前";
                newsCallBack.onSuccess(newsData);
            }
        }.start();

    }


    public interface NewsCallBack {
        void onSuccess(String mNewsData);
    }
}
  • Presenter
public class NewsPresenter {
    //View的引用
    private NewsView mNewsView;
    //Model的引用
    private NewsModel mNewsModel = new NewsModelImpl();

    private Handler handler = new Handler();

    public NewsPresenter(NewsView mNewsView) {
        this.mNewsView = mNewsView;
    }

    public void getNewsData() {

        mNewsView.showLoading();

        mNewsModel.getNewsFromNet("", new NewsModelImpl.NewsCallBack() {
            @Override
            public void onSuccess(final String mNewsData) {

                handler.post(new Runnable() {
                    @Override
                    public void run() {

                        mNewsView.displayNews(mNewsData);
                        mNewsView.dismissLoading();

                    }
                });

            }
        });
    }
}
  • View层

1、逻辑抽象

public interface NewsView {

    //加载中
    void showLoading();

    //展示新闻
    void displayNews(String newsData);

    //取消加载
    void dismissLoading();

    //加载失败
    void displayFailing();

}

2、具体实现

public class NewsActivity extends AppCompatActivity implements NewsView {

    private TextView tvData;
    private String data;
    private ProgressDialog progressDialog;
    private NewsPresenter newsPresenter;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tvData = (TextView) findViewById(R.id.tv_data);

        newsPresenter = new NewsPresenter(this);
        newsPresenter.getNewsData();

    }

    @Override
    public void showLoading() {

        progressDialog = ProgressDialog.show(this, "请稍候", "数据正在加载中", true, false);
        progressDialog.show();

    }

    @Override
    public void displayNews(String newsData) {

        data = newsData;
        tvData.setText(data);
    }

    @Override
    public void dismissLoading() {
        progressDialog.dismiss();
    }

    @Override
    public void displayFailing() {
        Toast.makeText(this, "加载失败", Toast.LENGTH_SHORT).show();
    }
}

上面代码的主要逻辑如下:

1、Model层主要是数据获取,我们定义了接口NewsModel,并在NewsModelImpl中实现数据获取,注意这里回调机制的用法。

2、View层的话我们先定义接口NewsView,抽象出通用的方法。

3、Presenter层的话,作为Model和View的桥梁,持有NewsModel和NewsView的引用,NewsPresenter主要完成业务逻辑的处理。

4、最后我们再看下NewsActivity,实现了接口NewsView,并在onCreate()方法里通过newsPresenter = new NewsPresenter(this);将自身传递给NewsPresenter建立引用,这样M、V、P三者的联系就建立起来了。

改进

上面的三部分代码给出了MVP模式的一个典型的例子,但是还是存在一定的问题的。考虑如下:在上面的NewsPresenter中主要是一些业务逻辑的处理,其中getNewsData()是一个较为耗时的操作,因为NewsPresenter持有了对NewsActivity的强引用,如果在请求结束之前NewsActivity被销毁了,因为网络请求还没有返回,导致NewsPresenter一直持有NewsActivity对象,使得NewsActivity对象无法被回收,发生内存泄漏。解决方法主要是** 对presenter和view层进行修改 **。在上面代码的基础上,我们做出下面的修改。

  • 添加BasePresenter
    BasePresenter主要是建立Presenter和View之间的联系,方便控制二者的关系。
public abstract class BasePresenter<T> {

    private Reference<T> mViewReference;

    //建立关联
    public void attachView(T view) {
        mViewReference = new WeakReference<>(view);
    }

    //获取关联
    protected T getView() {
        return mViewReference.get();
    }

    //是否关联
    public boolean isViewAttached() {
        return mViewReference != null && mViewReference.get() != null;
    }

    //解除关联
    public void detachView() {
        if (mViewReference != null) {
            mViewReference.clear();
            mViewReference = null;
        }
    }
}
  • 修改NewsPresenter 使其继承自BasePresenter
public class NewsPresenter extends BasePresenter<NewsView> {

    private NewsModel mNewsModel;
    private Handler handler;

    public NewsPresenter() {
        this.mNewsModel = new NewsModelImpl();
        this.handler= new Handler();
    }

    public void getNewsData() {

       //getView()获取已经关联的View
        getView().showLoading();
        mNewsModel.getNewsFromNet("", new NewsModelImpl.NewsCallBack() {
            @Override
            public void onSuccess(final String mNewsData) {

                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        getView().displayNews(mNewsData);
                        getView().dismissLoading();
                    }
                });

            }
        });
    }
}

  • 添加BaseActivity
    在onCreate()里将View与Presenter关联,onDestroy()里解除关联。
public abstract class BaseActivity<V, T extends BasePresenter<V>> extends AppCompatActivity {

    protected T mPresenter;

    @SuppressWarnings("unchecked")
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mPresenter = setPresenter();
        mPresenter.attachView((V) this);
    }

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

    protected abstract T setPresenter();

}
  • 最后是修改后的NewsActivity
public class NewsActivity extends BaseActivity<NewsView, NewsPresenter> implements NewsView {

    private TextView tvData;
    private String data;
    private ProgressDialog progressDialog;

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

        mPresenter.getNewsData();
    }

    @Override
    public void showLoading() {
        progressDialog = ProgressDialog.show(this, "请稍候", "数据正在加载中", true, false);
        progressDialog.show();
    }

    @Override
    public void displayNews(String newsData) {
        data = newsData;
        tvData.setText(data);
    }

    @Override
    public void dismissLoading() {
        progressDialog.dismiss();
    }

    @Override
    public void displayFailing() {
        Toast.makeText(this, "加载失败", Toast.LENGTH_SHORT).show();
    }

    @Override
    protected NewsPresenter setPresenter() {
        return new NewsPresenter();
    }
}

到这里,MVP的部分介绍就结束了。MVP是开发过程中比较好的框架模式,它能够将各个组件进行解耦,并且带来良好的可扩展性、可测试性、稳定性、可维护性,同时使得每个类型的职责相对单一、简单,有效地将业务逻辑和数据处理等工作从Activity里抽离出来,使得每个类尽可能简单。

四、MVVM

MVVM与MVP非常相似,唯一的区别在于View和Model进行双向绑定,两者之间有一方发生变化则会反应到另一方上。MVP中的View更新需要通过Presenter。Android中的ListView、Adapter以及数据之间的关系就像MVVM,其中Adapter就是ViewModel角色,它与View进行了绑定,又与数据集进行了绑定,当数据集合发生变化时,调用Adapter的notifyDataSetChanged之后View就直接更新,它们之间没有直接的耦合,使得ListView变得更为灵活。

五、参考

到这里,关于常见的几种框架模式就介绍完了,这里也只是自己的初步学习总结,在之后的开发实践中会继续完善,争取带来更完善的总结与思考。

相关参考:

1、何红辉 《Android源码设计模式 解析与实战》
2、MVP框架Mosby架构详解
3、Hongyang - 浅谈 MVP in Android

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

推荐阅读更多精彩内容