Android设计之初,就遵循的MVC模式,此时的模块划分是:
但因为布局文件又不能完全承担自己的职责,我们被迫将一部分更新View的代码写在了Activity(也就是Controller)中,导致Controller中的代码混乱,不利于阅读和维护,既有Controller的逻辑,也有View层逻辑。
想象一下一个业务场景:在界面上点击一个Button,登录一个网站并将登录结果在界面上呈现。
这个需求,我们需要有一个界面,来放点击按钮,和登录结果的TextView,这就是View层。再新建一个LoginHelper类来处理网络数据,这就是Model层。剩下一个Controller就是Activity,在Activity中,通过button的OnClickListener
触发Model层的网络请求后,将登录的结果返回给Activity,Activity可以直接将结果显示到界面的TextView上。
是不是很清晰,各司其职。但当你的需求中,包含需要动态改变界面控件隐藏和显示、界面颜色变化、动画控制,你也只能把这些View相关的代码写在Controller的Activity中。
造成这种混乱的根本原因,是作为View层的布局文件,本身控制能力太弱,View层需要动态改变的东西,不得不依赖Contoller提供控制。
为了解决Controller逻辑混乱的问题,于是有了MVP模式。MVP模块划分:
MVC中的作为View层的xml不是控制力弱吗?直接把Activity和Fragment划分给View层,控制力是不是爆表!!!这就是MVP模式。
而Presenter作为Controller的替代者,接替了它的工作。什么工作?当然是连接Model和View层。
Presenter通常由两部分组成:
MVP的工作原理是,通过View层(Activity或者Fragment)创建对应的Presenter,并相互持有彼此的引用。在Presenter中,创建Model实例,并获取Model处理后的数据,通过接口通知View层更新界面。
MVP的一大好处是,通过接口,让Model和View层解耦,可以实现面向接口编程的目的,这对于大型项目的开发,和各模块单独测试帮助很大。
那么,MVP有什么缺点呢?
MVP的缺点是,额外增加了一个接口,会增加额外的维护成本。真是的应用场景是,需求不断变化,接口也需要做对应调整。
该问题的解决方法是,在定义接口时,尽可能的斟酌接口,合理利用继承、实现,合理的使用设计模式。
对于MVVM来说,它的模块分为:
本文的最终目的就是这个,以一个下载代码的例子说明。只贴核心代码,Demo源码见:
public class Model {
public interface OnDownloadListener {
void onDownloadSuccess();
void onDownloading(int progress);
void onDownloadFailed();
}
public static void downloadFile(final String url, final String dir, final OnDownloadListener listener) {
// ...
listener.onDownloading(progress);
// ...
}
}
为了显而易见,我将唯一属于Model模块的类直接命名为Model。该类使用Okhttp网络框架,用于下载文件。
OnDownloadListener
接口用于通知ViewModel下载的结果,所以ViewModel需要实现该接口。public static void downloadFile(final String url, final String dir, final OnDownloadListener listener)
:用于具体的下载逻辑,供外界调用。url
为需要下载的地址,dir
为下载文件存放的目录,listener
为下载监听。listener.onDownloading(progress);
:下载过程中,调用监听器,通知View层更新下载进度。在该例中,数据只有来自网络的数据流,而Model类负责下载和存储数据,作为Model层存在。
public class ViewModel implements Model.OnDownloadListener {
private static final String TAG = "ViewModel";
private static final String URL = "http://172.0.6.55/test_for_live.mp4";
public ObservableInt mDownloadProgress = new ObservableInt(0);
public void onClick(View view) { // 点击事件处理函数
Log.i(TAG, "onClick");
Model.downloadFile(URL, "/mvvm/", this); // 连接Model
}
@Override
public void onDownloadSuccess() {
Log.i(TAG, "onDownloadSuccess");
}
@Override
public void onDownloading(int progress) {
mDownloadProgress.set(progress); // 进度更新变量
}
@Override
public void onDownloadFailed() {
Log.i(TAG, "onDownloadFailed");
}
}
本例中,ViewModel类作为ViewMode层的为一类,方便起见也起名叫ViewModel,希望不要搞混了。
ViewMode层的主要工作是将View层和Model层连接起来,主要靠如下关键调用实现:
public void onClick(View view)
:该函数定义后,将会被绑定在View层的Dowload按钮上,点击DowLoad按钮,就会执行Model层的下载逻辑。
Model.downloadFile(URL, "/mvvm/", this);
:DownLoad按钮点击后,立马执行下载,由该函数调用可以看到Model是如何与ViewModel连接的。函数调用后,下载进度,将会通过OnDownloadListener
的回调接口通知到ViewModel层。
当然,这里是静态调用。使用类实例的方式调用应该更加常见一些。
mDownloadProgress.set(progress);
:定义了一个ObservableInt对象mDownloadProgress,该对象将和View层绑定,当下载进度更新时,mDownloadProgress值变化将直接触发View层的刷新。
View层的布局文件:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ProgressBar
android:id="@+id/pb"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="0dp"
android:layout_height="10dp"
android:max="100"
android:progress="@{viewModel.mDownloadProgress}"
android:progressDrawable="@drawable/progressbar_preview"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toTopOf="@id/bt_download"
app:layout_constraintWidth_percent="0.875" />
<Button
android:id="@+id/bt_download"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{viewModel::onClick}"
android:text="DownLoad"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent" />
androidx.constraintlayout.widget.ConstraintLayout>
<data class="MvvmBinding">
<variable
name="viewModel"
type="com.superli.demo.ViewModel" />
data>
layout>
View层的Activity:
public class MVVMActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
binding.setViewModel(new ViewModel());
}
}
View层的工作主要就是将ViewModel和自己绑定,同时通过ViewMode间接和Model中的数据绑定,这种绑定关系并不直观,但显然是存在的。
以上就是MVVM代码原因各层的划分方式了。
MVC的问题是View层和Controller层在Activity中融合到一起了,原因是xml布局文件的能力太弱,无法控制一些动态的内容。在MVVM中,data binding通过数据绑定的方式解决了这个问题,使View层和Model可以各司其职。但当你需要处理一些特殊的逻辑时,还是会让View层出现不必要的代码。
比如在本例中,我现在想控制ProgressBar
,做一个移动或者随便什么样式的动画,我将不得不在Activity中做控制,但控制的逻辑原则上应该交给ViewModel啊。类似的情况还有很多。
关于data binding的使用,Google官方探索除了一套MVP + data binding的道路,原理大概是通过data binding解决数据绑定的问题,使用Presenter和Model交互。相关sample源码见:
有兴趣可以去研究一下。
文中术语 | 含义 |
---|---|
事件 | 即Event |