浅谈Android中的MVP

转载请标明出处:
http://blog.csdn.net/hai_qing_xu_kong/article/details/51745798
本文出自:【顾林海的博客】

前言

为什么使用MVP,网上有很多说法,最主要就是减轻了Activity的责任,相比于MVC中的Activity承担的责任太多,因此有必要讲讲MVP。

MVP入门

在MVC框架中,View是可以直接读取Model模型中的数据的,Model模型数据发生改变是会通知View数据显示发生相应的改变。而在MVP中Model和View之间的没有任何联系,是两个完全独立的模块,当Model模型发生数据改变时,通过Presenter通知View视图发生相应的UI改变。因此,个人觉得:MVP才是正真的视图和模型完全分离,也就是Model模型进行业务数据处理和View视图显示没有任何关联。可以通过下图看出:

以下是MVC的框架图:

这里写图片描述

以下是MVP的框架图:

这里写图片描述

有关MVP说明的文章现在网上一大堆,最近自己也在尝试使用MVP去构建应用,关于MVP三层的定义,可以看看下面相关资料:

model

数据加工处理厂

model是整个应用或界面的数据加工处理厂,所谓数据加工厂就是对数据的获取,数据的解析,数据的存储,数据的分发,数据的增删改查等操作。意思就是凡是涉及到数据操作都是在model进行的,所以model不仅仅只是实体类的集合,同时还包含关于数据的各种处理操作。

三种数据源

数据的数据源有三种:内存,磁盘(文件或数据库等),网络。为了提升app的性能,有必要把经常访问的数据临时存入内存中;同时也为了提升app性能和为用户省流量省电,有必要把数据存入磁盘中;还有的数据是有必要从网络读取的。三个数据源不一定同时存在,比如不与网络交互的app,不存在网络数据源。所以凡是涉及到关于数据发生于三个数据源加工处理的操作的代码都要放在model中。

model为上层提供的服务:

model从黑盒的角度来看为上层(指依赖于model的层比如present)提供的服务无非就2种:model为上层提供数据,model处理上层传递的数据

model为上层提供数据:

上层会从model中去数据,那model会从三数据源中取数据,取的顺序是

  • 先内存,内存取到数据返回
  • 其次磁盘,磁盘取到数据,如有必要把数据存储在内存中,则需要进行存储,返回数据
  • 最后网络,网络取到数据,如有必要在磁盘或内存中存储,则进行存储,返回数据

上面的取数据过程是最简单的情况,复杂些还会涉及到从内存或磁盘中取到的数据是否过期,过期的话就应该从网络获取。从网络取得数据后需要把内存或磁盘的数据更新。

model处理上层传递的数据:

model接收到上层传递的数据后,model会依次把数据扔给三个数据源去处理,有可能三个数据源都会处理数据,有可能只是其中一个处理,model会把处理的结果返回。

所以model会把解析好的数据提供给上层,上层对于数据的来源完全是透明的,上层完全不需要关心数据到底是来自内存,还是磁盘甚至是网络。同理上层只需要的把数据扔给model,上层唯一做的事情就是愉快的等待处理结果。

presenter

presenter翻译成汉语的意思是主持人,提出者。从它的意思可以看出它有控制全场的作用。首先presenter是处于mvp的中间层,在view和model中起一个承上启下的作用。presenter会把view交给自己的命令进行一定的校验等操作交给model处理,会把model处理的结果交给view。

presenter封装业务:
presenter不仅起一个桥梁的作用,它还会把业务逻辑代码给包揽下来。这样就可以减轻Activity的负担了,让Activity全心全意做它的view工作。那估计就有朋友犯迷糊了,哪些代码属于业务逻辑呢?比如一些校验代码。或者可以这样想只要是不属于view和model的代码基本都可以放在presenter中。

presenter负责刷新view:
mvc或以前的关于view的写法一般都是这样,view在接收到数据后,自己来进行view的刷新或其他操作。但是mvp中presenter负责对view进行刷新,比如从model获取的数据,presenter会根据获取的数据成功与否来通知view应该是显示成功界面还是失败界面。这样就让Activity变的更轻了,变成了听别人指挥的傻白甜了。这时候的presenter就有点主持人,掌控者的味道了。

presenter持有的线程:
Android中view的操作需要在ui线程里执行,其他耗时操作需要在普通线程执行。presenter会持有这2种线程:ui线程,普通线程。刷新view时,它切换为ui线程进行刷新,从model取数据切换为普通线程。假如使用rxjava的话,就特别简单了关于线程切换的事情。

view

view层就很好理解了,就是用户直接看到的界面,mvp中的view是很省心的,比如更新view,接收数据。这些操作它都不需要操心,也不需要知道数据到底来自哪里,给我啥我显示啥就可以了。

一个view可以同时拥有多个presenter,也可以只有一个presenter。

Android中的Activity,Fragment在mvp中是作为view来使用的,这些Activity,Fragment的责任就小了,只关心界面相关的事情足矣。各种Adapter是放在view层的。

案例展示

既然清楚了MVP的一些概念,现在就可以创建一个项目,整个项目很简单,通过点击按钮获取天气信息并显示在界面上,这里面我们使用MVP来搭建整个项目,先从Mode开始到Presenter最后View的创建。(天气接口使用的是心知天气提供的SDK http://www.thinkpage.cn/doc#info )

案例Model层

代码展示:

package weather.weatherproject.mode;

import com.thinkpage.lib.api.TPCity;
import com.thinkpage.lib.api.TPListeners;
import com.thinkpage.lib.api.TPWeatherManager;
import com.thinkpage.lib.api.TPWeatherNow;

import weather.weatherproject.WeatherApplication;

/**
 * 天气管理类
 * Created by glh on 2016-06-23.
 */
public class WeatherManager {

    public static final WeatherManager instance = new WeatherManager();

    public interface WeatherListener{
        void onSuccess(TPWeatherNow response);

        void onFailed(String errString);
    }


    /**
     * 获取指定城市的实况天气。
     *
     * @param city
     * @param listener
     */
    public void getNowWeather(String city, final WeatherListener listener) {

        WeatherApplication.weatherManager.getWeatherNow(new TPCity(city), TPWeatherManager.TPWeatherReportLanguage.kSimplifiedChinese, TPWeatherManager.TPTemperatureUnit.kCelsius, new TPListeners.TPWeatherNowListener() {
            @Override
            public void onTPWeatherNowAvailable(TPWeatherNow tpWeatherNow, String s) {
                if (tpWeatherNow != null) {
                    listener.onSuccess(tpWeatherNow);
                } else {
                    listener.onFailed(s);
                }
            }
        });
    }

}

我们知道Model层主要用于数据的输入和输出,因此这里创建了一个天气管理类,通过获取天气信息的方法获取数据,最后将数据传递给Presenter,Presenter只需要将城市名传递给Model层,而它只需监听获取信息的状态,因此内部创建了一个天气信息获取的监听接口,通过它使得Presenter进行信息获取的监听。

案例Presenter层

代码展示:

package weather.weatherproject.presenter;

/**
 * View的基础接口
 * Created by glh on 2016-06-23.
 */
public interface IView {
    void initView();
}
package weather.weatherproject.presenter;

/**
 *
 * Created by glh on 2016-06-23.
 */
public interface IPresenter<V extends IView> {
    void onStop();

    void onResume();

    void onDestroy();

    void onPause();

    void onStart();

    void init(V view);
}

IView是一个基础接口,内部定义了一个initView方法,用于View的初始化。
IPresenter也是一个基础接口,内部定义了一些 Activity或Fragment常用的生命周期方法,用于View与 Activity或Fragment的联动。

package weather.weatherproject.presenter.weather;

import weather.weatherproject.presenter.IPresenter;
import weather.weatherproject.presenter.IView;
import weather.weatherproject.view.bean.NowWeather;

/**
 * 获取天气的约定类,用于组合IWeatherView和IWeatherPresenter
 * Created by glh on 2016-06-23.
 */
public interface WeatherContract {
    interface IWeatherView extends IView {
        //获取指定城市的实况天气
        void showNowWeather(NowWeather result);

        void error(String error);
    }

    interface IWeatherPresenter extends IPresenter<IWeatherView> {
        void getWeather(String city);
    }
}

可以看到WeatherContract接口内部定义两个接口:
1、IWeatherView接口,用于Presenter与View的数据传递。
2、IWeatherPresenter接口,是连接Model与View的中间层。

package weather.weatherproject.presenter.weather;

import com.thinkpage.lib.api.TPWeatherNow;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import weather.weatherproject.mode.WeatherManager;
import weather.weatherproject.presenter.bean.BeanUtil;

/**
 * 获取天气的Presenter
 * Created by glh on 2016-06-23.
 */
public class WeatherPresenter implements WeatherContract.IWeatherPresenter {

    private WeatherContract.IWeatherView mIWeatherView;
    private WeatherManager mWeatherManager = WeatherManager.instance;
    private ExecutorService mExecutorService = Executors.newFixedThreadPool(5);

    @Override
    public void init(WeatherContract.IWeatherView view) {
        this.mIWeatherView = view;
        mIWeatherView.initView();
    }

    @Override
    public void getWeather(final String city) {
        mExecutorService.execute(new Runnable() {
            @Override
            public void run() {
                mWeatherManager.getNowWeather(city, new WeatherManager.WeatherListener() {
                    @Override
                    public void onSuccess(TPWeatherNow response) {
                        mIWeatherView.showNowWeather(BeanUtil.createNowWeather(response));
                    }

                    @Override
                    public void onFailed(String errString) {
                        mIWeatherView.error(errString);
                    }
                });
            }
        });
    }


    @Override
    public void onStop() {

    }

    @Override
    public void onResume() {

    }

    @Override
    public void onDestroy() {

    }

    @Override
    public void onPause() {

    }

    @Override
    public void onStart() {

    }

}

WeatherPresenter主要做了以下几件事情:
1、初始化了View。
2、通过View传递过来的指令向Model层请求数据。
3、监听Model层的状态,并将结果刷新到View上。
请求数据属于耗时操作因此我们开辟了线程用与请求处理,最后通过UI线程刷新View。BeanUtil用于封装我们所需的数据。

案例View层

代码展示:

package weather.weatherproject.view.base;

import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;

import java.util.HashSet;
import java.util.Set;

import weather.weatherproject.presenter.IPresenter;

/**
 * 基类Activity,所有业务界面都继承此BaseActivity。
 * Created by glh on 2016-06-23.
 */
public abstract class BaseActivity extends FragmentActivity {
    private Set<IPresenter> mAllPresenters = new HashSet<>(1);

    /**
     * 获取layout的id,具体由子类实现
     *
     * @return
     */
    protected abstract int getLayoutResId();

    /**
     * 需要子类来实现,获取子类的IPresenter,一个activity有可能有多个IPresenter
     */
    protected abstract IPresenter[] getPresenters();

    /**
     * 初始化presenters
     */
    protected abstract void onInitPresenters();

    /**
     * 事件监听
     */
    protected abstract void initEvent();

    /**
     * 从intent中解析数据,具体子类来实现
     *
     * @param argIntent
     */
    protected void parseArgumentsFromIntent(Intent argIntent) {
    }

    private void addPresenters() {

        IPresenter[] presenters = getPresenters();
        if (presenters != null) {
            for (int i = 0; i < presenters.length; i++) {
                mAllPresenters.add(presenters[i]);
            }
        }
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getLayoutResId());
        if (getIntent() != null) {
            parseArgumentsFromIntent(getIntent());
        }
        addPresenters();
        onInitPresenters();
        initEvent();
    }

    @Override
    protected void onResume() {
        super.onResume();
        //依次调用IPresenter的onResume方法
        for (IPresenter presenter : mAllPresenters) {
            if (presenter != null) {
                presenter.onResume();
            }
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        //依次调用IPresenter的onStop方法
        for (IPresenter presenter : mAllPresenters) {
            if (presenter != null) {
                presenter.onStop();
            }
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        //依次调用IPresenter的onPause方法
        for (IPresenter presenter : mAllPresenters) {
            if (presenter != null) {
                presenter.onPause();
            }
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        //依次调用IPresenter的onStart方法
        for (IPresenter presenter : mAllPresenters) {
            if (presenter != null) {
                presenter.onStart();
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //依次调用IPresenter的onDestroy方法
        for (IPresenter presenter : mAllPresenters) {
            if (presenter != null) {
                presenter.onDestroy();
            }
        }
    }
}

BaseActivity只是做了一下封装,方便我们展示界面的使用,最后我们看看展示界面WeatherActivity:

package weather.weatherproject.view.weather;

import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import weather.weatherproject.R;
import weather.weatherproject.presenter.IPresenter;
import weather.weatherproject.presenter.weather.WeatherContract;
import weather.weatherproject.presenter.weather.WeatherPresenter;
import weather.weatherproject.view.base.BaseActivity;
import weather.weatherproject.view.bean.NowWeather;

/**
 * 天气界面
 * Created by glh on 2016-06-23.
 */
public class WeatherActivity extends BaseActivity implements WeatherContract.IWeatherView {

    private WeatherPresenter mWeatherPresenter = new WeatherPresenter();

    private TextView tv_show;
    private Button btn_now_weather;

    @Override
    protected int getLayoutResId() {
        return R.layout.activity_main;
    }

    @Override
    protected IPresenter[] getPresenters() {
        return new IPresenter[]{mWeatherPresenter};
    }

    @Override
    protected void onInitPresenters() {
        mWeatherPresenter.init(this);
    }

    @Override
    protected void initEvent() {
        btn_now_weather.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(WeatherActivity.this, "onclick", Toast.LENGTH_SHORT).show();
                mWeatherPresenter.getWeather("shanghai");
            }
        });
    }

    @Override
    public void showNowWeather(NowWeather result) {
        tv_show.setText(result.toString());
    }

    @Override
    public void error(String error) {
        tv_show.setText(error);
    }

    @Override
    public void initView() {
        tv_show = (TextView) findViewById(R.id.tv_show);
        btn_now_weather = (Button) findViewById(R.id.btn_now_weather);
    }
}

最后可以看到我们的Activity非常的简洁,到了这里我们的项目已经搭建完成,这样做有以下好处:
1、学习过设计模式的人都知道,这样做基本符合了单一职责原则。
2、符合单一职责原则后,导致类与类组织更清晰。
3、View层与Model层交互需要通过Presenter层进行,这样v与m层级间的耦合性降低。
4、通过这种分层处理,每一层的测试也相对简单,维护性更高。

项目地址

MVPDemo请点击这里

本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。
阅读此文前请先阅读 Retrofit+okhttp网络框架介绍 从上文中我们已经了解通过如下代码即可得到返回给我们call 以及 response对象,今天我们通过源码来分析这个过程是如何实现的。 /** * 获取天气数据 * @param cityname * @param key * @return */ @GET ( "/weather/index" ) CallWeatherData getWeatherData( @Query ( "format" ) String format, @Query
一、Block 的类型 根据 Block 在内存中的位置分 为三种类型 NSGlobalBlock , NSStackBlock, NSMallocBlock 。 NSGlobalBlock :类似函数,位于 text 段; NSStackBlock :位于 栈内存,函数返回后 Block 将无效; NSMallocBlock :位于堆内存。 二、Block 的 copy 、 retain 、 release 操作   不同于 NSObjec 的 copy 、 retain 、 release 操作: B
前言 module 怎能少得了动画呢~ 代码解读 weex code API 接口 transition (node, options, callback) Arguments 参数node(Node):将要动画的元素。options( object ):操作选项styles( object ):指定要应用的过渡效果的样式的名称和值。color( string ):色彩的元素时,animaiton完成。transform( object ):变换函数被应用到元素。支持下列值。translate/ tran
目录 概述 这是一个关于 RecycleView 滑动事件的辅助类,该辅助类可以检测 RecycleView 滑动到顶部或者底部的状态. 可用于实现 RecycleView 加载更多或者刷新(虽然刷新可以直接用 SwipeRefreshLayout ).也可用于某些滑动相关的需求,如 FloatingActionButton 的隐藏与显示之类的. 关于 RecycleView 的滑动监听 RecycleView 本身已经提供了滑动的监听接口, OnScrollListener ,这个接口包含了以下的方法.
这个小案例建议在手机上运行。 package com.example.camera;import java.io.File;import android.net.Uri;import android.os.Bundle;import android.os.Environment;import android.provider.MediaStore;import android.app.Activity;import android.content.Intent;import android.view.Me
简介 NSScanner是一个类,用于在字符串中扫描指定的字符,尤其是把它们翻译/转换为数字和别的字符串。可以在创建NSScaner时指定它的string属性,然后scanner会按照你的要求从头到尾地扫描这个字符串的每个字符。 NSScanner官方文档 NSScanner类是一个类簇的抽象父类,该类簇为一个从NSString对象扫描值的对象提供了程序接口。 NSScanner对象把NSString 对象的的字符解释和转化成 number和string 类型的值。在创建NSScanner对象的时候为它分
目录 概述 StickHeaderItemDecoration 是用于显示固定头部的item装饰类,扩展来自系统的 ItemDecoration .本文参考了一部分 sticky-headers-recyclerview 原理 绘制头部 固定头部的 ItemDecoration 本质是在 RecycleView 上覆盖一个界面.该界面没有随着滑动变动所以看起来就像一个固定的头部. 绘制item间隔 ItemDecoration 也可以实现每个item之间的间隔的绘制(比如分隔线之类的),这种情况下就不是在
android中网络请求回来数据之后,我们要对其解析。请求的返回的结果格式如果不是自定义协议;那么返回的数据通常是xml,json,html形式的数据了。 下面就是针对上面3种格式进行解析。 xml解析使用工具:在android中推荐使用pull解析,还有其他的dom,sax解析。 json解析使用工具:推荐使用Fastjson,由阿里提供。还有其他的如JackSon,Gson解析。 html解析使用工具:推荐使用Jsoup,还有其他的如HtmlParser;关于使用这个,网络上的爬虫就是这样子的。 1.
一个SpriteKit项目在其他设备上运行都无问题(无论是真机或是模拟器),但是在iPhone6 Plus上会出现精灵对象纹理被过度放大的现象: 从上图中大家可以看到无论是主角或是道具球都过大了. 看了一下精灵图片是放在atlas纹理集文件夹中的: 可以看到PowerUp和Player都有对应缩放的版本:Player.png,Player@2x.png以及Player@3x.png. 在各个图片的属性中检查图片的尺寸也都正确,看不出神马问题… 找到SKSpriteNode对象初始化的代码看看: let o
转载请标明出处: 一片枫叶的专栏 本文中我将介绍一下我自己封装的一个小的工具类库:按钮点击事件类库。 作用: 该类库可以防止按钮重复点击,可以判断网络状态,可以判断用户登录状态,以及自定义验证条件等等。 说明: 其实现的核心原理就是通过自定义实现自身的OnClickListener类,并重写其中的onClick方法,在onClick方法中执行相应的判断逻辑之后回调我们自定义的抽象方法。 具体效果如下图所示: 使用方式 屏蔽多次点击事件 /** * 测试快速点击事件 */ fastButton.setOnC