Android MVPVM架构实践

来源:互联网 发布:避孕套 知乎 编辑:程序博客网 时间:2024/06/15 16:17

简书地址:http://www.jianshu.com/p/b53e1450ca8f

前言

写Android也有一段时间了,始终没有找到一种优雅流畅的Android架构模式,前不久看了google关于mvp架构的范例,甚好,建议仔细观摩一下:https://github.com/googlesamples/android-architecture。这篇文章也是参考了google的写法加上自己的理解和实践写出来的,供大家参考参考,如有不当的地方欢迎指正。

什么是MVPVM?

MVPVM=Model+View+Presenter+ViewModel
在没有使用类似MVP架构的时候,逻辑一般都直接写在了Activity或者Fragment里,导致View层很臃肿,业务逻辑、UI操作和数据耦合到了一起,结构混乱。现在,View层要处理的逻辑全部委托给Presenter处理,View层专注实现UI,Presenter专注实现业务逻辑,它们之间通过View Interface和Presenter Interface交互。在google推出databinding后,View Interface的部分功能可以转移到ViewModel中去,进一步降低View层的臃肿。
* View层:实现View Interface,对外提供showDialog、showToast之类的方法
* ViewModel层:以databinding为基础,对外提供控制xml界面的方法
* Presenter层:实现Presenter Interface,处理业务逻辑
* Model层:服务器数据对应数据模型类

实践

把下面这个页面(用户信息页面)以MVPVM模式写出来
用户信息页面
项目结构如下:
用户信息页面类结构

业务流程如下:
业务流程
View层触发了一个请求用户信息的event,然后View层将这个event交给Presenter层来处理,Presenter向服务器请求数据,Presenter拿到数据(数据被封装到了model里)后进行逻辑处理,然后根据需求操纵ViewModel进行UI更新。

1.Model层——UserInfoModel

以后台返回数据格式是json为例,这里的Model层就是一一对应的后台返回的数据。

public class UserInfoModel implements Serializable {    /**     * head : string     * headBackground : string     * name : string     * sex : int 1:男 2:女     * nationality : int 1:中国 2:美国     * specialty : string     * advantage : string     * createTime : long     */    @SerializedName("head")    private String head;    @SerializedName("headBackground")    private String headBackground;    @SerializedName("name")    private String name;    @SerializedName("sex")    private int sex;    @SerializedName("nationality")    private int nationality;    @SerializedName("specialty")    private String specialty;    @SerializedName("advantage")    private String advantage;    @SerializedName("createTime")    private long createTime;    //下面是各个字段的get和set方法,不列出来了    ...}

2.ViewModel层——UserInfoViewModel

ViewModel相当于操作xml的代言人,任何xml显示的更新都要通过ViewModel来进行,要注意在写ViewModel的时候要完全按照xml来写,比如一个TextView要显示和隐藏,我会在ViewModel里定义一个int字段来表示;TextView要显示内容,我会在ViewModel里定义一个String字段来表示。ViewModel和xml之间用databinding绑定起来,操作ViewModel就相当于操作xml。如果对databinding不熟悉的请参考databinding google官方文档(不用科学上网也能看哦,Google给中国开发者的福利):
https://developer.android.google.cn/topic/libraries/data-binding/index.html

public class UserInfoViewModel extends BaseObservable {    private int headBackgroundRes;    private int headImageRes;    private String name;    private String sex;    private String nationality;    private String specialty;    private String advantage;    private String createTime;    @Bindable    public int getHeadImageRes() {        return headImageRes;    }    public void setHeadImageRes(int headImageRes) {        this.headImageRes = headImageRes;        notifyPropertyChanged(BR.headImageRes);    }    @Bindable    public int getHeadBackgroundRes() {        return headBackgroundRes;    }    public void setHeadBackgroundRes(int headBackgroundRes) {        this.headBackgroundRes = headBackgroundRes;        notifyPropertyChanged(BR.headBackgroundRes);    }   ...}

看一下xml中如何使用ViewModel的:

<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:bind="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools">    <data>        <variable            name="viewModel"            type="com.tc.mvpvmdemo.userinfo.UserInfoViewModel" />    </data>    <LinearLayout        android:id="@+id/activity_user_info"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:orientation="vertical"        tools:context="com.tc.mvpvmdemo.userinfo.UserInfoActivity">        <LinearLayout            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:orientation="vertical"            android:padding="10dp"            bind:backgroundResource="@{viewModel.headBackgroundRes}"            tools:background="@mipmap/bg_trump">            <ImageView                android:layout_width="80dp"                android:layout_height="80dp"                android:layout_gravity="center_horizontal"                bind:imageResource="@{viewModel.headImageRes}"                tools:src="@mipmap/ic_head" />            <TextView                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:layout_gravity="center_horizontal"                android:layout_marginTop="20dp"                android:text="@{viewModel.name}"                android:textColor="@android:color/white"                android:textSize="18sp"                tools:text="川普" />        </LinearLayout>        <android.support.v4.widget.Space            android:layout_width="match_parent"            android:layout_height="20dp" />        <LinearLayout            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:background="@android:color/white"            android:orientation="horizontal"            android:padding="8dp">            <TextView                android:id="@+id/textView"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:text="性  别"                android:textSize="18sp" />            <TextView                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:layout_marginLeft="15dp"                android:text="@{viewModel.sex}"                android:textColor="@color/colorPrimaryDark"                android:textSize="18sp"                tools:text="男" />        </LinearLayout>        <LinearLayout            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:background="@android:color/white"            android:orientation="horizontal"            android:padding="8dp">            <TextView                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:text="国  籍"                android:textSize="18sp" />            <TextView                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:layout_marginLeft="15dp"                android:text="@{viewModel.nationality}"                android:textColor="@color/colorPrimaryDark"                android:textSize="18sp"                tools:text="美国" />        </LinearLayout>        <LinearLayout            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:background="@android:color/white"            android:orientation="horizontal"            android:padding="8dp">            <TextView                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:text="特  长"                android:textSize="18sp" />            <TextView                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:layout_marginLeft="15dp"                android:text="@{viewModel.specialty}"                android:textColor="@color/colorPrimaryDark"                android:textSize="18sp"                tools:text="表情包" />        </LinearLayout>        <LinearLayout            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:background="@android:color/white"            android:orientation="horizontal"            android:padding="8dp">            <TextView                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:text="优  势"                android:textSize="18sp" />            <TextView                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:layout_marginLeft="15dp"                android:text="@{viewModel.advantage}"                android:textColor="@color/colorPrimaryDark"                android:textSize="18sp"                tools:text="女儿" />        </LinearLayout>        <LinearLayout            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:background="@android:color/white"            android:orientation="horizontal"            android:padding="8dp">            <TextView                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:text="创建时间"                android:textSize="18sp" />            <TextView                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:layout_marginLeft="15dp"                android:text="@{viewModel.createTime}"                android:textColor="@color/colorPrimaryDark"                android:textSize="18sp"                tools:text="2016.12.16 12:34" />        </LinearLayout>    </LinearLayout></layout>

可以发现大部分字段ViewModel和Model是能对上的,有的人会图简单把Model和ViewModel合二为一,不过不建议这么做,不要在Model或者ViewModel中写任何逻辑,因为ViewModel可以把前后端分离,也就是说,只要后台接口定好了,Presenter就可以使用Model、View和ViewModel将整个业务逻辑写完。即使最后后台字段变化,影响的也只是Presenter中的处理逻辑,如果将Model和ViewModel合在一起,当后台字段变化,除了修改Presenter、Model外,xml和ViewModel也得修改,得不偿失。

3.Presenter层——UserInfoPresenter

Presenter层是处理业务逻辑的核心,它处理由View层转移过来的事件,逻辑处理完毕后操作View和ViewModel更新UI。IUserInfo描述了Presenter和View层的接口定义,Presenter和View层就是通过这些接口进行交互的。

public interface IUserInfo {    interface IView {        void updateTitle(String title);//更新页面的标题        void showDialog(String content);//显示一个dialog        void closeDialog();//关闭dialog    }    interface IPresenter {        void onViewInit();//页面初始化后执行    }}

Presenter和View层只关心对方提供了那些接口,而不关心对方的具体实现细节,我可以通过接口的不同实现来实现不同的UI和不同的业务逻辑。

public class UserInfoPresenter implements IUserInfo.IPresenter {    private IUserInfo.IView mView;    private UserInfoViewModel mViewModel;    public UserInfoPresenter(IUserInfo.IView iView, UserInfoViewModel viewModel) {        this.mView = iView;        this.mViewModel = viewModel;    }    @Override    public void onViewInit() {        mView.updateTitle("用户信息");        requestData();    }    private UserInfoModel mockTrump() {        UserInfoModel trump = new UserInfoModel();        trump.setHead("xxx.jpg");        trump.setHeadBackground("xxxx.jpg");        trump.setName("川普");        trump.setNationality(2);        trump.setSex(1);        trump.setSpecialty("表情包");        trump.setAdvantage("漂亮的女儿");        trump.setCreateTime(System.currentTimeMillis());        return trump;    }    private void requestData() {        mView.showDialog("正在获取数据");        //不建议这么用Handler,这里只是模拟网络请求的延迟        new Handler().postDelayed(new Runnable() {            @Override            public void run() {                mView.closeDialog();                updateUi(mockTrump());            }        }, 2000);    }    private void updateUi(UserInfoModel model) {        mViewModel.setName(model.getName());        mViewModel.setHeadImageRes(R.mipmap.ic_head);        mViewModel.setHeadBackgroundRes(R.mipmap.bg_trump);        String sex;        switch (model.getSex()) {            case 1:                sex = "男";                break;            case 2:                sex = "女";                break;            default:                sex = "不详";        }        mViewModel.setSex(sex);        String nationality;        switch (model.getNationality()) {            case 1:                nationality = "中国";                break;            case 2:                nationality = "美国";                break;            default:                nationality = "地球";        }        mViewModel.setNationality(nationality);        mViewModel.setAdvantage(model.getAdvantage());        mViewModel.setSpecialty(model.getSpecialty());        mViewModel.setAdvantage(model.getAdvantage());        mViewModel.setCreateTime(new SimpleDateFormat("yyyy.MM.dd HH:mm").format(new Date(model.getCreateTime())));    }}

在页面初始化完成后控制权就已经由View转移到Presenter,Presenter根据逻辑执行了一次获取用户数据的网络请求,收到服务器返回的数据后通过ViewModel更新UI。

4.View层——UserInfoActivity

UserInfoActivity实现了IView接口,所以本例中View层就是UserInfoActivity,它初始化了Presenter和databinding,并持有Presenter的引用,当需要处理业务逻辑时会调用Presenter来处理。如在本例中,UserInfoActivity实现如何更新title,和如何显示和关闭一个dialog。

public class UserInfoActivity extends AppCompatActivity implements IUserInfo.IView {    ActivityUserInfoBinding binding;    UserInfoPresenter presenter;    ProgressDialog dialog;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        binding = DataBindingUtil.setContentView(this, R.layout.activity_user_info);        UserInfoViewModel viewModel = new UserInfoViewModel();        presenter = new UserInfoPresenter(this, viewModel);        binding.setViewModel(viewModel);        presenter.onViewInit();    }    @Override    public void updateTitle(String title) {        this.setTitle(title);    }    @Override    public void showDialog(String content) {        if (dialog == null) {            dialog = new ProgressDialog(this);        }        dialog.setMessage(content);        dialog.show();    }    @Override    public void closeDialog() {        if (dialog != null && dialog.isShowing()) {            dialog.dismiss();        }    }}

最后附上demo地址:https://github.com/wunianhub/Android-MVPVM

0 0
原创粉丝点击