MVP模式最佳实践

1. 摘要

       MVP(Model-View-Presenter)模式通过Presenter解决了Activity、Fragment等在MVC(Model-View-Controller)中太臃肿的问题。
       Google给出了开源代码,向大家阐述了他们对于MVP模式的理解和建议: https://github.com/googlesamples/android-architecture。其中,todo-mvp分支则是最单纯(未使用其他任何框架)的MVP实现: https://github.com/googlesamples/android-architecture/tree/todo-mvp/
       本文基于对todo-mvp分支的理解,以更加简单的代码,阐述了作者眼中的MVP模式。

2. MVP浅析

       MVP由Model、View、Presenter组成。View最容易理解,即为Android中的各种widget控件。Presenter是为了让View和Model隔离而存在的。 Model并不仅仅是简单的bean,还包括了bean的一些业务逻辑,比如从服务器获取数据等。

3. Simplest MVP

       本项目基于google的 todo-mvp,加上笔者的理解,写了一个查询天气情况的Android Demo。实现的功能非常简单:单击Button后,模拟网络查询当前温度,并使用Toast显示出来。如果不使用MVP,这个功能十几行代码即可实现。采用这个简单例子,旨在让初学者可以抓住MVP的本质。
       代码可在: https://github.com/afunx/SimplestMVP下载。项目结构如图1所示:

                                           图1 SimplestMVP项目结构
    
       其中,BaseView和BasePresenter均为interface。但也可以用abstract class。
       BasePresenter在这里仅仅是一个接口,不包含其他方法。BasePresenter.java:
package com.afunx.mvp;

public interface BasePresenter {
}
        BaseView中有一个setPresenter方法,通过这种方式,把View注入到Presenter中。BaseView.java:
package com.afunx.mvp;

public interface BaseView<T> {
    void setPresenter(T presetner);
}
       MainContract接口定义了MainContract.View接口和MainContract.Presenter。MainContract.java:
package com.afunx.mvp.main;

import com.afunx.mvp.BasePresenter;
import com.afunx.mvp.BaseView;

import com.afunx.mvp.main.bean.TemperatureBean;

/**
 * This specifies the contract between the view and the presenter(View和Presenter的协议)
 */

public interface MainContract {

    interface View extends BaseView<Presenter> {
        /**
         * show present temperature(显示当前温度)
         *
         * @param temperatureBean temperature bean(temperature bean对象)
         */
        void showTemperature(TemperatureBean temperatureBean);
    }

    interface Presenter extends BasePresenter {
        /**
         * query present temperature from Internet and show it(从网络上查询当前温度,并显示它)
         */
        void showPresentTemperature();
    }
}
       TemperatureBean就是一个简单的记录温度的bean。TemperatureBean.java:
package com.afunx.mvp.main.bean;

/**
 * TemperatureBean restore temperature in degree centigrade(TemperatureBean存储了气温的摄氏度)
 */

public class TemperatureBean {

    private int degree;

    public int getDegree() {
        return degree;
    }

    public void setDegree(int degree) {
        this.degree = degree;
    }
}
       MainModel就是MVP中的Model,在复杂的项目中,MainModel也可以指某个模块。MainModel.java:
package com.afunx.mvp.main.model;

import java.util.Random;

import com.afunx.mvp.main.bean.TemperatureBean;
import com.afunx.mvp.main.presenter.MainPresenter;

/**
 * Main Model represents model used by the Presenter ({@link MainPresenter})
 * ,simulate query present temperature from the Internet
 */

public class MainModel {
    /**
     * simulate query temperature from the Internet
     *
     * @return TemperatureBean
     */
    public TemperatureBean queryTemperature() {
        // simulate query from Server
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // degree is in random [20,30)
        int degree = 20 + new Random().nextInt(10);

        TemperatureBean temperatureBean = new TemperatureBean();
        temperatureBean.setDegree(degree);
        return temperatureBean;
    }
}
        MainPresenter实现了MainContract.Presenter接口,并在其构造器中调用mainView.setPresenter方法,把View注入到Presenter中。MainPresenter.java:
package com.afunx.mvp.main.presenter;

import android.os.Handler;
import android.support.annotation.NonNull;

import com.afunx.mvp.main.MainContract;
import com.afunx.mvp.main.bean.TemperatureBean;
import com.afunx.mvp.main.model.MainModel;
import com.afunx.mvp.main.view.MainActivity;

/**
 * Listens to user actions from the UI ({@link MainActivity}), receives the data and updates the UI
 * (监听用户在UI上的动作,接收温度变化,并更新UI)
 */

public class MainPresenter implements MainContract.Presenter {

    @NonNull
    private final MainContract.View mMainView;

    private final MainModel mMainModel;

    public MainPresenter(@NonNull MainContract.View mainView) {
        mMainView = mainView;
        mMainModel = new MainModel();

        mainView.setPresenter(this);
    }

    @Override
    public void showPresentTemperature() {

        // mainHandler is created in UI thread(mainHandler在UI线程中创建)
        final Handler mainHandler = new Handler();

        new Thread(){
            @Override
            public void run() {
                // query temperature in non-UI thread(在非UI线程中,查询温度)
                final TemperatureBean temperatureBean = mMainModel.queryTemperature();
                mainHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        // update View in UI thread(在UI线程中更新View)
                        mMainView.showTemperature(temperatureBean);
                    }
                });
            }
        }.start();
    }
}
        MainActivity实现了MainContract.View接口。MainContract.View继承自BaseView<T>接口,故MainActivity实现了setPresenter方法。MainActivity在onCreate回调中,以new MainPresenter(this)的方式实现了View和Presenter的互相注入。对于任何一个新接触MVP的人来说,都有一点绕。多看一下 加粗红色部分解释和对应代码,慢慢就能理解了。MainActivity.java:
package com.afunx.mvp.main.view;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.afunx.mvp.R;

import com.afunx.mvp.main.MainContract;
import com.afunx.mvp.main.bean.TemperatureBean;
import com.afunx.mvp.main.presenter.MainPresenter;

/**
 * UI for ({@link MainPresenter})
 * MainPresenter对应的UI
 */
public class MainActivity extends AppCompatActivity implements MainContract.View, View.OnClickListener {

    private Button mBtnShowTemperature;
    private MainContract.Presenter mMainPresenter;

    @Override
    public void setPresenter(MainContract.Presenter presetner) {
        mMainPresenter = presetner;
    }

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

        new MainPresenter(this);

        mBtnShowTemperature = (Button) findViewById(R.id.btn_show_temperature);
        mBtnShowTemperature.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (v == mBtnShowTemperature) {
            mMainPresenter.showPresentTemperature();
        }
    }

    @Override
    public void showTemperature(TemperatureBean temperatureBean) {
        int celsius = temperatureBean.getDegree();
        String format = getString(R.string.present_temperature_text);
        String text = String.format(format, celsius);
        Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
    }
}
       MainActivity对应的布局文件activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.afunx.mvp.main.view.MainActivity">

    <Button
        android:id="@+id/btn_show_temperature"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:text="@string/show_temperature"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>
       最终效果如图2:

                                             图2 SimplestMVP效果gif                                                                         


4. 引用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值