MVVM架构模式已经非常火了,我还是只知其一不知其二,搜了很多帖子学习,这里整理一下学习笔记。
a.便于理解,先把一些字面意思放在这里:
Model 模型(数据层)
是应用程序中处理程序数据逻辑的部分,通常负责从本地数据库存取,远程存取数据。
View 视图(UI显示)
是应用程序中处理数据显示的部分,通常视图都是依据模型来创建的。
Controller 控制者,管理者,调度员
在应用程序中处理用户交互的部分,通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。
Presenter 主持人
ViewModel 视图模型
Repository 仓库,存放处
1.MVC
MVC的模式优点在于分离UI与业务职责,增加可测试性与可扩展性,但缺点是,View即依赖于Controller又依赖于Model,在修改UI内容时,也需要修改对应的Model,降低架构的灵活性,View和Model的职责存在部分重叠,耦合度高,实际操作中很难按照MVC模式严格去分离。
2.MVP
Model-View-Presenter ;MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示。
MVP模式的优点是模型与视图完全分离,我们可以修改视图而不影响模型,因为在这个模式下,所有的交互都发生在Presenter内部,也利于脱离用户接口来测试业务逻辑。缺点是会导致视图和Presenter的交互过于频分,Presenter与具体的View没有直接关联,而是通过定义好的接口进行交互,这会导致有大量的接口生成,造成代码繁具,很难维护,这也是很多像我一样从入坑到放弃这种模式的原因。
3.MVVM
MVP的特质是“依赖倒置”,MVVM的特质是“数据驱动”。
学这个的目的,是因为这句话,显然说明二者并没有谁演化自谁的关系了,JetPack MVVM是MVVM模式在Android开发中的具体落实,“数据驱动”这个理解有点难,看帖子不理解就找个教程写个Demo理解一下,B站是个学习网站,很多优质UP主提供了帮助,这里贴一下一个小Demo:
篮球比赛计分器
一个计分器,点击加分向对应的队加相应的分数,点击回退取消上一步的操作,点击重置将两队的比分清零。
首先定义MyViewModel,继承自ViewModel,在这里我们定义数据,并处理逻辑。
public class MyViewModel extends ViewModel {
// 定义两队的分数
// MutableLiveData可修改的LiveData,因为分数需要变化,LiveData
private MutableLiveData<Integer> aTeamScore;
private MutableLiveData<Integer> bTeamScore;
//缓存两队当前分数,用于回退一步
private int aBack, bBack;
public MutableLiveData<Integer> getATeamScore() {
if (aTeamScore == null) {
aTeamScore = new MutableLiveData<>();
aTeamScore.setValue(0);
}
return aTeamScore;
}
public MutableLiveData<Integer> getBTeamScore() {
if (bTeamScore == null) {
bTeamScore = new MutableLiveData<>();
bTeamScore.setValue(0);
}
return bTeamScore;
}
/**
* 给A队加分,在添加之前保留A、B两队的分数,用于在执行undo操作时使用
* @param p 要加的分数
*/
public void aTeamAdd(int p) {
aBack = aTeamScore.getValue();
bBack = bTeamScore.getValue();
aTeamScore.setValue(aTeamScore.getValue() + p);
}
public void bTeamAdd(int p) {
aBack = aTeamScore.getValue();
bBack = bTeamScore.getValue();
bTeamScore.setValue(bTeamScore.getValue() + p);
}
/**
* 清零
*/
public void reset() {
aBack = aTeamScore.getValue();
bBack = bTeamScore.getValue();
aTeamScore.setValue(0);
bTeamScore.setValue(0);
}
public void undo() {
aTeamScore.setValue(aBack);
bTeamScore.setValue(bBack);
}
}
布局文件的内容除了常规控件的内容,也就是使用了JetPack提供的DataBinding,篇幅有限这里仅仅对新生事物粘贴一下:
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
<variable
name="data"
type="com.nxhope.community.state.MyViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.BasketballScoring">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:text="@{String.valueOf(data.getATeamScore())}"
android:textColor="#E91E63"
android:textSize="80sp"
app:layout_constraintBottom_toTopOf="@+id/guideline10"
app:layout_constraintEnd_toStartOf="@+id/guideline7"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/guideline9"
tools:text="120" />
<Button
style="@style/button_one"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:onClick="@{()->data.aTeamAdd(1)}"
android:text="+1"
app:layout_constraintBottom_toTopOf="@+id/guideline11"
app:layout_constraintEnd_toStartOf="@+id/guideline7"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/guideline10" />
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="60dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:onClick="@{()->data.undo()}"
app:layout_constraintBottom_toTopOf="@+id/guideline14"
app:layout_constraintEnd_toStartOf="@+id/guideline7"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/guideline13"
app:srcCompat="@drawable/ic_baseline_undo_24" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
布局文件转换为data-bingding layout这步可以不用手写,windows用户选中布局的根节点alt+enter就会提示出一个选项“convert to data binding layout" 的内容, 转换后会多出来<data>节点中的内容,我们只需要name中命名,在type中导入我们定义好的ViewModel即可。
剩下的事情就是为控件绑定ViewModel提供的数据或者操作了,TextView中
android:text="@{String.valueOf(data.getATeamScore())}"
为A队分数显示区域绑定数据,Button中
android:onClick="@{()->data.aTeamAdd(1)}"
绑定了为A队加1的操作,当然加2加3就类似了,ImageButton绑定了我们在ViewModel中定义的undo()操作,也就是回退一步。可以看到,到目前为止,我们好像只关心了布局文件和ViewModel的内容,对于页面Activity反而还没下手,不像常规操作,之所以这样,是因为Activiy中真的没啥可处理了,看代码:
public class BasketballScoring extends AppCompatActivity {
private MyViewModel viewModel;
private ActivityBasketballScoringBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//DataBinding
binding = DataBindingUtil.setContentView(this, R.layout.activity_basketball_scoring);
//ViewModel
viewModel = new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).get(MyViewModel.class);
binding.setData(viewModel);
binding.setLifecycleOwner(this);
}
}
Activity中我们只是做了将布局和ViewModel绑定,当然这里还写了一些LiveData和Lifecycle的内容(我还没研究透彻,这里按下不表,听下回分说),不是重点,重点是,Activity中太简洁了,常见的FindViewById( ),setOnclickListener( ),setText( )统统不见踪影,以及我们可能要定义的常量,Bean什么的也没了,这就很香了。
玩玩这个Demo,比如删除布局文件中的一些内容,常规方式可能就会出现空指针,控件找不到造成的Crash,但现在不会了,MVVM模式用于解决“视图调用一致性问题”这句话也诚不欺我,这时候再拿出KunMinX大佬的图:
再品品,想总结些内容,品出来的是:“我是小白,特别的白”、
帖子前面提到了Repository ,这里面并没有说,本着的是不懂就不装懂的原则,况且程序员有的东西懂了怎么用,不一定会表达,继续学习,Android推出的JetPack内容是时候学学了:
Jetpack
网友评论