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
- Android MVPVM架构实践
- MVC,MVP,MVPVM(一)实践之路
- MVC,MVP,MVPVM(一)实践之路
- Android混合架构的实践
- 从零开始的Android新项目3 - MVPVM in Action, 谁告诉你MVP和MVVM是互斥的
- 从0开始搭建MVP+ViewModel框架的android应用01---MVPVM诞生记
- Android架构(一)MVP架构在Android中的实践
- Google 官方Android MVP架构实践
- MVP架构在Android中的实践
- MVPVM模式介绍
- 架构实践
- 架构实践
- 架构实践
- 从友盟微社区看Android第三方SDK架构实践
- 从友盟微社区看Android第三方SDK架构实践
- 微信Android模块化架构重构实践
- 微信 Android 模块化架构重构实践
- 微信Android模块化架构重构实践
- 闭包所带来的问题
- 学习python3 01 list与tuple
- 利用反射和注解模拟ORM框架中的自动建表功能
- ADB Debugging over Bluetooth
- 算法分析与设计总结
- Android MVPVM架构实践
- 多线程——内存访问顺序 (序)
- 在eclipse中配置Python开发环境
- 分布式系统全链路应用监控系统解决方案
- dubbo+zookeeper+spring整合demo
- 从零开始搭建一个HTTPS网站
- Linux系统上hdparm工具参数详解,硬盘检查、测速、设定和优化
- 详解C#委托,事件与回调函数
- php加密解密