Android MVP经验谈

欢迎转载,转载时请注明出处和作者
作者:kerwin
原文地址:
http://www.jianshu.com/p/284990eebf0c

****MVP->Model-View-Presenter****,意在解决业务逻辑与视觉层的解耦问题。关于MVC与MVP的区别相关资料很多,这里不再累述,相关传送门:
MVP模式(百度百科)

从上图看出,Presenter是作为Model与View层交互的连接器存在。将上图转化为代码实现的官方实例:Google官方architecture项目

MVP模式类图

类图解析
核心的3个接口:BaseUI、BasePresenter、XContract。

****BasePresenter****,业务实现层的基础接口,所有Presenter的超父类,
方法:start(Bundle bundle),Presenter层的数据初始化,Bundle为常规页面数据传递的基本容器。

****BaseUI****,视图层的基础接口,所有UI实现类的超父类
方法:setPresenter(T presenter),用于关联UI与Presenter,主要用在Fragment与Presenter的绑定。

****XContract****,主要是为了将具体的UI和Presenter集合到一个文件中,方便集中查阅管理。

实践
需求:X页面包含3个需求。1、计算总成绩a+b,2、获取X的姓名和性别,3、展示总成绩和X的信息。

1)首先按照需求写出对应的Model类

public class XModle {

/**
 * @param a a课程分数
 * @param b b课程分数
 * @return  总分数
 */
public int sum(int a, int b) {
    return a + b;
}

/**
 * 假装这里是从服务器获取的数据
 * @param callback
 */
public void getX(Callback callback) {
    callback.onResult("kerwin", "boy");
}

public interface Callback {
    void onResult(String name, String gender);
}
}

2)创建XContract类

public interface XContract {

interface UI extends BaseUI<Presenter> {
  
}

interface Presenter extends BasePresenter {

}
}

3)根据业务需求添加UI和Presenter接口的方法

public interface XContract {
    /**
     * A课程得分的传递的参数名
     */
    String PARAM_INT_A = "a";
    /**
     * B课程得分的传递的参数名
     */
    String PARAM_INT_B = "b";

    interface UI extends BaseUI<Presenter> {

        /**
         * 通知UI,Sum已经计算出来,可以刷新对应的UI了
         */
        void refreshSumResult();

        /**
         * 通知UI,X的信息已经获取到,可以刷新X了。
         */
        void refreshX();

        /**
         * 显示loading弹窗
         * @param msg
         */
        void showLoading(String msg);

        /**
         * 关闭loading弹窗
         */
        void dismissLoading();

    }

    interface Presenter extends BasePresenter {

        /**
         * 获取X的姓名
         * @return
         */
        String getXName();

        /**
         * 获取X的性别
         * @return
         */
        String getXGender();

        /**
         * 获取总成绩
         * @return
         */
        String getSumScore();
    }

}

4)实现XPresenter类

public class XPresenter implements XContract.Presenter {

    private XContract.UI ui;

    private XModle modle;

    private int a, b;

    private int sum = 0;

    private String xName, xGender;

    public XPresenter(XContract.UI ui) {
        this.ui = ui;
        this.modle = new XModle();
    }

    @Override
    public void start(Bundle bundle) {
        a = bundle.getInt(XContract.PARAM_INT_A, 0);
        b = bundle.getInt(XContract.PARAM_INT_B, 0);
        start();
    }

    private void start() {
        sum = modle.sum(a, b);
        ui.refreshSumResult();
        ui.showLoading("正在获取X的信息");
        modle.getX(new XModle.Callback() {
            @Override
            public void onResult(String name, String gender) {
                ui.dismissLoading();
                xName = name;
                xGender = gender;
                ui.refreshX();
            }
        });
    }

    @Override
    public String getXName() {
        return "姓名:"+xName;
    }

    @Override
    public String getXGender() {
        return "性别:"+xGender;
    }

    @Override
    public String getSumScore() {
        return "总成绩"+sum;
    }
}

到这里,可以看到,我们Activity还没有开始写,但是我们的业务流程已经完成了,这就是MVP的魅力。至于页面长什么样子并不需要业务逻辑层去关注。现在我们理一下整个需求完成的程序流图。

****程序流图****

计算总分数
获取X的信息

看过程序流图后结合之前的代码,可能会问:为什么UI里面没有setXName方法,为什么Presenter里面有getXName方法。为什么Presenter从XModel里面获取到Sum返回值后不是直接调用ui.setSum(sum),而是ui.refreshSumResult()?

个人实践中发现,UI层是随时可能会变化的,但是Presenter基本不变,因为接口基本不变。所以,我推荐的MVP实践方式是,主动的在UI中从Presenter获取数据,而不是在Presenter中主动的修改UI信息,Presenter完成从Model获取数据的职责后仅需通知UI已经完成数据获取,UI想如何显示或显示什么自己决定。

****下面继续完善UI部分的实现代码****

5)新增layout文件activity_x.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="20dp">

    <TextView
        android:id="@+id/tv_sum"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/tv_x_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp" />

    <TextView
        android:id="@+id/tv_x_gender"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp" />

</LinearLayout>

6)新增XActivity类

public class XActivity extends Activity implements XContract.UI {

    private TextView tv_x_name;

    private TextView tv_x_gender;

    private TextView tv_sum;

    private XContract.Presenter presenter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_x);
        initView();
        initPresenter();
    }

    private void initView() {
        tv_sum = (TextView) findViewById(R.id.tv_sum);
        tv_x_name = (TextView) findViewById(R.id.tv_x_name);
        tv_x_gender = (TextView) findViewById(R.id.tv_x_gender);
    }

    private void initPresenter() {
        presenter = new XPresenter(this);
        presenter.start(getIntent().getExtras());
    }

    @Override
    public void setPresenter(XContract.Presenter presenter) {
        // 如果UI实现是一个Fragment,这里的代码是需要的,是Activity的这里留空即可
        this.presenter = presenter;
    }

    @Override
    public void refreshSumResult() {
        tv_sum.setText(presenter.getSumScore() + "");
    }

    @Override
    public void refreshX() {
        tv_x_name.setText(presenter.getXName());
        tv_x_gender.setText(presenter.getXGender());
    }

    @Override
    public void showLoading(String msg) {
        //显示Dialog弹窗
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void dismissLoading() {
        //关闭Dialog弹窗
    }
}

至此,所有的需求全部完成了。

****说在最后****
MVP每一层都有自己的职责,请尽量做到各司其职。
几点实践建议:
1、View层不包含任何Model层的代码以及引用
2、Presenter作为连接器,尽量不要直接调用UI层的具体UI更新的方法,仅需通知UI层自己去刷新某一个细分模块(就像上面的refreshX)

示例源码传送门

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

推荐阅读更多精彩内容