Android MVP模式初探

前言

很早以前就听说过MVP模式 , 也在网上找过很多文章去学习 , 但始终不得其法 , 如果只是码代码 , 很容易陷入其中不得其真意 。只有跳出来 , 从全局去看 , MVP模式比以往写的模式好得太多了 。Android的项目的结构虽然叫MVC结构, 但从我接手的几个项目来看 , 远远没有达到如WEB般的MVC , 我们往往把Activity即当Controller又当View,我现接手的项目就是如此 ,今天我用UML来画MVP模式 , 豁然开朗 , 以致第二个例子,首先用UML画好,然后才写代码,写得非常顺畅 。

用UML学MVP

~ 效果图

mvpdemo.gif

基本概念

不管是MVC还是MVP , 都是为了将数据,处理逻辑,与界面分离 , 以达到低耦合的效果 。MVP模式,主要是面向接口编程 , 将Model与View都抽象成一个接口,用一个Presenter来关联View与Model,Presenter做到一个绑定数据控制界面的中间者 。

MVC:M(Model)V(View)C(Controller)

MVP:M(Model)V(View)P(Presenter)

MVC模式首先提出应用是在web开发的时代,并滋生出一系列的框架,Java的SpringMVCSSH组合,PHP的ThinkPHPYII等等。Android MVC模式下M与C与V都有关联,目前很多项目都是这样做的。

MVC

目前接手的项目大多都是这个结构 , 这种结构使得Activity引用乱传,很容易造成内存泄漏 。

MVP模式,这个模式首先是微软提出的,在微软很多技术里面都有体现,并且还推出了MVVM的模式 。MVP模式 , 很好的将View与Model分离,使用Presenter这个中间者来组合Model与View之间的关系 ,而隔绝了View与Model直接产生关系 。

MVP

Presenter作为一个中间者,持有Model与View的两个引用,将Model与View关联起来 , 在MVP中将Model与View的操作接口化 , 通过接口回调来完成数据的传递 。

分析

上图是一个书籍列表的程序,程序本身并不复杂,就是一个书籍列表。如果我们用MVP模式去写 , 应该怎么写呢 ?首先看一下UML图

BooksList_UML

① 编写IModel接口 , 定义获取数据的方法以及显示时需要的数据对象 , 这里是一个列表,所以是一个List集合。具体实现 , 则需要创建一个Class来实现IModel接口。

② 编写IView接口,定义View显示的方法,首先定义一个加载数据进度的方法,还可以加一个加载完成的方法来显示View,接着定义一个显示列表的方法。

③ 编写实现类 , 首先编写IModel的实现类 。IModel的实现类 , 其主要功能就是获取网络数据 , 并将数据处理成对象,返回给BookLoadListener监听接口。

④ 编写Presenter , 定一个两个接口的引用,Model在创建类的时候就创建 , 而View则是通过创建Presenter对象进行传递 。在Presenter里面 , 写一个fetch()将Model与View关联起来 。

⑤ 编写IView的实现 , Activity实现IView接口 , 进行View的显示与控制。

代码分析

Model

/**
 * Created by Zeno on 2016/10/18.
 *
 * books model interface
 *
 * 主要用来加载数据 , 并进行数据处理
 */
public interface IBookModel {

    /*加载数据*/
    void loadDatas(BooksLoadListener listener);

    /*数据加载完将数据回调给调用者*/
    interface BooksLoadListener {
        void onComplate(List<Books.BooksEntity> Books);
        void onError(String msg);
    }
}

------------------------------------------------------------------------------------------------------------------------------------------------------------

/**
 * Created by Zeno on 2016/10/18.
 *
 * books model impl
 */
public class BooksModelImplV1 implements IBookModel {

    private static final String TAG = BooksModelImplV1.class.getSimpleName();
    /*请求队列*/
    private RequestQueue requestQueue;

    private static final String URL = "http://it-ebooks-api.info/v1/search/php%20mysql";

    public BooksModelImplV1() {
        requestQueue = NoHttp.newRequestQueue();
    }

    /**
     * 加载数据
     * @param listener
     */
    @Override
    public void loadDatas(BooksLoadListener listener) {
        /*创建一个请求对象*/
        Request<String> request = NoHttp.createStringRequest(URL, RequestMethod.GET);
        requestQueue.add(1,request,new BooksResponseListener(listener));
    }

    private class BooksResponseListener implements OnResponseListener<String> {

        private BooksLoadListener listener;

        public BooksResponseListener(BooksLoadListener listener) {
            this.listener = listener;
        }

        @Override
        public void onStart(int what) {

        }

        @Override
        public void onSucceed(int what, Response<String> response) {
                if (what == 1) {
                    Gson gson = new Gson();
                    Books books = gson.fromJson(response.get(), Books.class);
                    /*将请求到的数据,解析并处理,回调给Presenter*/
                    listener.onComplate(books.getBooks());
                    Log.e(TAG, "onSucceed: "+response.get() );
                }
        }

        @Override
        public void onFailed(int what, Response response) {
                listener.onError("失败");
        }

        @Override
        public void onFinish(int what) {

        }
    }
}

View

/**
 * Created by Zeno on 2016/10/18.
 *
 * Books view interface
 *
 * 显示
 */
public interface IBooksView {

    /*加载进度条*/
    void showLoading();

    /*显示*/
    void showBooksList(List<Books.BooksEntity> Books);

}


--------------------------------------------------------------------------------------------------------------------------------------------------------------

public class MainActivity extends BaseActivity<IBooksView,BooksPresenterV1> implements IBooksView {

    private static final String TAG = MainActivity.class.getSimpleName();

    @InjectView(R.id.rvList)
    RecyclerView rvList;

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

        initView();

        /*中间者 , 让中间者触发加载数据*/
        /*为了防止内存泄漏,做了一些优化,将Model与View的生命周期关联起来,不懂的可以下载源码看一下*/
        mPresneter.fetch();
    }

    private void initView() {
        rvList.setLayoutManager(new LinearLayoutManager(this));
    }

    @Override
    public void showLoading() {
        Toast.makeText(MainActivity.this, "加载中....", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void showBooksList(List<Books.BooksEntity> Books) {
        Log.e(TAG, "showBooksList: "+Books.toString() );
        Toast.makeText(MainActivity.this, "加载完成", Toast.LENGTH_SHORT).show();
        /*Model请求的数据,通过Presenter回调到View*/
        rvList.setAdapter(new BooksListAdapter(Books,this));
    }

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

    @Override
    protected BooksPresenterV1 createPresenter() {

        return new BooksPresenterV1(this);
    }
}

Presenter

/**
 * Created by Zeno on 2016/10/18.
 */
public class BooksPresenterV1 extends BasePresenter<IBooksView>{

    /*持有View Model*/

    // View
    IBooksView mBooksView ;

    // model
    IBookModel mBookModel = new BooksModelImplV1();

    /*通过构造方法实现View*/
    public BooksPresenterV1(IBooksView mBooksView) {
        this.mBooksView = mBooksView;
    }

    /*
    * fetch model
    * */
    public void fetch() {
        if (mBookModel != null) {
            mBooksView.showLoading();
            mBookModel.loadDatas(new IBookModel.BooksLoadListener() {
                @Override
                public void onComplate(List<Books.BooksEntity> Books) {
                    /*当View背销毁,则不去显示数据*/
                    if(mBooksView != null) {
                        mBooksView.showBooksList(Books);
                    }
                }

                @Override
                public void onError(String msg) {

                }
            });
        }
    }
}

由上述可以看出 , MVP,将M与V分隔开来 , 使得代码与逻辑都比较清晰 , 不会出现你引用了我 , 我又引用了你 , 最后不知道谁引用了谁 , 导致对象占据内存,或出现对象空指针等问题 。

MVP模式 , 虽然项目文件会比较多 , 但是结构清晰 , 不仅小型项目可以直接使用MVP作为项目架构 , 大型项目也可以在模块或组件中使用MVP模式 。

用户登录MVP UML

因为MVP涉及的类与接口比较多 , 所以我比较喜欢先用UML图画出关系结构 , 再进行编码 ,这样即清晰明了 , 又不会对彼此之间的关系弄混 , 所以算是我目前比较喜欢的方式 。在编写完上面的例子之后 ,我接着又画了用户登录的MVP UML图。

UserLogin UML

当我画完这个UML图之后 , 基本上整个流程结构也就清晰了 , 按照上述的编写步骤很快就编写出了代码 , 不至于一开始就写就懵逼 。

结语

MVP现在在Android中应用越来越广泛了 , 所以弄清楚什么是MVP,以及怎样编写MVP , 已经是每个Android程序员所必备知识了 , 希望我的UML方式能让你有所收获 。

源码地址

MVPDemo

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,223评论 25 707
  • 一、什么是MVP在之前的开发中,采用的是android本身的MVC模式,然而随着业务逻辑的增加,软件的复杂度变高,...
    Henryhaoson阅读 593评论 3 4
  • 转载至:http://www.jianshu.com/p/9a6845b26856 “Android MVP 详解...
    SnowDragonYY阅读 10,315评论 5 241
  • 作者:李旺成 时间:2016年4月3日 “Android MVP 详解(下)”已经发布,欢迎大家提建议。 MVP ...
    diygreen阅读 128,793评论 86 1,321
  • 跟一位朋友的父亲聊天,他竟然是一个结婚20多年没跟妻子吵过架的男人,问他是如何做到的,他说很简单:“只要在与对方的...
    运安阁阁主阅读 148评论 0 0