一、理论


对于Android项目来说,一个好的架构模式对于后期新的需求的提出、维护、更新代码等各个方面都是十分有利的。

那么对于Android项目来说,有哪些可选的架构模式呢,传统的MVC模式,微软的MVVM模式和MVP模式。


MVC模式:在Android方面,View层与Controller高度耦合了,基本上都是Activity充当了,软件工程的软件设计思想就是得高内聚,低耦合,所以这个Activity几百直至上千行是非常繁琐的,维护起来难度是加大了,虽然说考验个人代码能力,但是软件设计的思想不就是让写代码、维护、更新、添加模块更加地方便嘛,所以这个就相对来说缺点是比较大了。

MVVM模式:MVVM模式最早是微软公司提出,并且了大量使用在.NET的WPF和Sliverlight中。2005年微软工程师John Gossman在自己的博客上首次公布了MVVM模式,对于大型的图形应用程序,视图状态较多,ViewModel的构建和维护的成本都会比较高,所以Android方面选择MVVM考虑得不是很多。

MVP模式:它的核心就是隔离了Model层与View层,Model层与View不直接接触,他们通过Presenter层进行数据交互,Android基本都是靠页面与用户交互,所以这就在一定程度上相对于MVC和MVVM要优越很多,所以这里就剖析一下MVP的一个小Demo。



二、实战


MVP模式的思想如下图:

android mvp模式与mvc android mvvm mvp_数据

View层(Activity)界面需要数据,请求数据,到Presenter里面去找,Presenter层本身不生产数据,它只是数据的搬运工,所以它到Model层里面去找,

Model层里面,从数据库拉数据,从网络请求数据,解析数据等等无论哪种方式,得到数据,然后交给Presenter,最后再显示到View层(Activity)界面上。


好,理论太枯燥,看Demo:(Demo地址

首先是包结构:

android mvp模式与mvc android mvvm mvp_数据_02

包结构看完,然后就是一条主线思路:View -> Presenter -> Model -> Presenter -> View


所以,先看ProfileActivity类与IProfileView,本着面向对象六大原则之一的开闭原则,对扩展开放,对修改关闭,所以用接口来写具体要实现的方法,对于具体的操作在实现类里面实现,所以ProfileActivity类与IProfileView应该是这样的:

public class ProfileActivity extends AppCompatActivity implements IProfileView {

    private IProfilePresenter iProfilePresenter;
    private CircleImageView mIvAvatar;
    private TextView mTvNickname;
    private TextView mTvResume;

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

    /**
     * 初始化视图以及事件
     */
    private void initView() {
        //把当前对象传进去,以免Activity被销毁
        iProfilePresenter = new ProfilePresenterImpl(this);
        mIvAvatar = (CircleImageView) findViewById(R.id.ivAvatar);
        mTvNickname = (TextView) findViewById(R.id.tvNickname);
        mTvResume = (TextView) findViewById(R.id.tvResume);

        findViewById(R.id.btnRequest).setOnClickListener((v -> requestData(Contants.getExampleUrl)));
        findViewById(R.id.btnReset).setOnClickListener(v -> {
            mIvAvatar.setImageResource(R.mipmap.ic_launcher);
            mTvNickname.setText("Google第1号员工");
            mTvResume.setText("这个人很懒,什么都没有留下");
        });
    }

    @Override
    public void requestData(String url) {
        iProfilePresenter.requestData(url);
    }

    @Override
    public void displayData(Profile profile) {
        Glide.with(this)
                .load(R.mipmap.plnus)
                .into(mIvAvatar);
        mTvNickname.setText(profile.getNickName());
        mTvResume.setText(profile.getResume());
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //解除绑定
        ((ProfilePresenterImpl) iProfilePresenter).unBind();
    }
}

这个Activity的布局activity_profile.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=".test.profile.view.ProfileActivity">

    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/ivAvatar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.2" />

    <TextView
        android:id="@+id/tvNickname"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="Google第1号员工"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/ivAvatar" />

    <TextView
        android:id="@+id/tvResume"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="这个人很懒,什么都没有留下"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tvNickname" />

    <Button
        android:id="@+id/btnRequest"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="request"
        android:textAllCaps="false"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btnReset"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="reset"
        android:textAllCaps="false"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/btnRequest" />
</android.support.constraint.ConstraintLayout>

一个View界面,也就是一个Activity,在整个Android项目中承担着View的角色,跟用户交互的一个界面,当然Fragment也是可以的,只是这里用Activity来举例子,Fragment的话,换成具体实现IProfileView接口就好了,所以在这里,Activity就是具体的View接口的实现类,IProfileView是怎样的呢,它里面只有一个方法,就是需要实现的具体的业务逻辑方法,代码就是这样:

public interface IProfileView {

    /**
     * 请求数据
     *
     * @param url
     */
    void requestData(String url);

    /**
     * 显示数据
     *
     * @param profile
     */
    void displayData(Profile profile);
}

这就是整个View层的东西,从Activity里面可以看出来,直接从requestData来发起请求数据,然后直接用Presenter的接口调用ProfilePresenterImpl里面的方法,也就是IProfilePresenter接口里面的待实现的方法,IProfilePresenter与ProfilePresenterImpl代码如下,先看ProfilePresenterImpl类:

public class ProfilePresenterImpl implements IProfilePresenter, ProfileBizImpl.OnDataChangeListener {

    private IProfileView iProfileView;
    private IProfileBiz iProfileBiz;

    /**
     * 创建对象的时候用接口的实现类
     * 实例化View层与Model层的接口
     *
     * @param iProfileView
     */
    public ProfilePresenterImpl(IProfileView iProfileView) {
        this.iProfileView = iProfileView;
        this.iProfileBiz = new ProfileBizImpl(this, this);
    }

    @Override
    public void refresh(Profile profile) {
        iProfileView.displayData(profile);
    }

    @Override
    public void requestData(String url) {
        iProfileBiz.getRequest(url);
    }

    /**
     * 解除Presenter与View、Model
     * 两者之间的绑定
     */
    public void unBind() {
        iProfileView = null;
        iProfileBiz = null;
    }
}

然后再是IProfilePresenter接口代码:

public interface IProfilePresenter {

    /**
     * View层通过Presenter层的该方法
     * 到Model层去请求数据
     *
     * @param url
     */
    void requestData(String url);
}

从Presenter层的接口以及其实现类来看,ProfilePresenterImpl类实现了IProfilePresenter接口,并重写了方法,然后直接被View层调用,请求从View层已经传到了Presenter里面,然后现在Presenter层需要做的就是把请求继续往Model层传,现在看Model层里面的具体实现,在刚刚包结构里面可以看到,有一个bean包和一个biz包,bean包,实体类包嘛,放实体类数据的,biz(business)包就是具体的业务逻辑类的包,好,先看biz包的类,也就一个IProfileBiz接口和一个ProfileBizImpl接口实现类,先看ProfileBizImpl接口实现类:

public class ProfileBizImpl implements IProfileBiz {

    private IProfilePresenter iProfilePresenter;
    private OnDataChangeListener onDataChangeListener;

    public ProfileBizImpl(OnDataChangeListener onDataChangeListener, IProfilePresenter iProfilePresenter) {
        this.iProfilePresenter = iProfilePresenter;
        this.onDataChangeListener = onDataChangeListener;
    }

    @Override
    public void getRequest(String url) {
        String response = HttpUtil.getRequest(url);
        Profile profile = parseResponseToBean(response);
        onDataChangeListener.refresh(profile);
    }

    /**
     * 解析请求下来的json数据
     *
     * @param response
     * @return
     */
    private Profile parseResponseToBean(String response) {
        //这里执行具体的数据解析操作
        //用Gson或者fastjson或者手动解析都可以
        //这里为了方便就直接随便返回一个不报错的值就行了
        //这里假装返回一个解析好的Profile实体类
        return new Profile("https://avatars3.githubusercontent.com/u/16745807?s=460&v=4", "Plnus", "没有梦想跟咸鱼有什么区别");
    }

    /**
     * Presenter层请求的数据回调
     */
    public interface OnDataChangeListener {
        void refresh(Profile profile);
    }
}

然后再是IProfileBiz的接口,里面就业务逻辑空方法:

public interface IProfileBiz {

    /**
     * 请求数据
     *
     * @param url
     */
    void getRequest(String url);
}

这就是Model层,等等,忘了一个bean包好像,bean包里面就一个实体类,就这个:

public class Profile {

    private String avatarUrl;
    private String nickName;
    private String resume;

    public Profile(String avatarUrl, String nickName, String resume) {
        this.avatarUrl = avatarUrl;
        this.nickName = nickName;
        this.resume = resume;
    }

    public String getAvatarUrl() {
        return avatarUrl;
    }

    public void setAvatarUrl(String avatarUrl) {
        this.avatarUrl = avatarUrl;
    }

    public String getNickName() {
        return nickName;
    }

    public void setNickName(String nickName) {
        this.nickName = nickName;
    }

    public String getResume() {
        return resume;
    }

    public void setResume(String resume) {
        this.resume = resume;
    }
}

好了,这就是Model层,从Model层来看,从Presenter传过来的请求到了Model里面了,然后就直接Model层具体去执行具体的业务逻辑,比如网络请求数据等等,但是网络请求完的数据怎么返回View层呢,开始的思路是从Model层还会往回传的,所以这里面就给ProfileBizImpl里面定义了一个回调接口OnDataChangeListener,在Presenter层里面实现了,只要数据请求到,立马调用refresh(Profile profile)方法,把Profile实体类塞进去,然后Presenter就接收到了,就在ProfilePresenterImpl重写的refresh(Profile profile)方法里面,这个时候直接调用View层的接口,然后直接把这个Profile实体类数据传出去就好,这里就用到了View层的display(Profile profile)方法,直接在界面进行显示。


三、总结


总结看一下:

View层做了什么,请求数据,显示数据,就两件事,跟界面显示息息相关;

Presenter层做了什么,把View层的请求数据往里传,把Model层的数据往外传;

Model层坐了什么,具体的数据逻辑操作,数据业务操作;


由此可见,这种模式将各个层的任务解耦得非常充分,基本每个类的代码也不多,看起来不累,对于阅读代码是非常有利的,只是接口调用,优点难以理清思路,当把思路理清楚了之后,发现这种模式,用得恰到好处,只要调用完数据,然后把显示数据写完,其他的都不用管,都交给Model层和Presenter来操作,所以对于这种解耦思路,个人觉得是非常好的。


对于Android的MVP模式的理解以及小Demo的认识就到这里了,如有不对之处望大家指出,谢谢。

Demo项目的地址在文中了,如有需要,可自行下载。