Android MVPVM架构实践

前言

写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的:




    

        
    

    

        

            

            
        

        

        

            

            
        

        

            

            
        

        

            

            
        

        

            

            
        

        

            

            
        
    

可以发现大部分字段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

你可能感兴趣的:(Android MVPVM架构实践)