Android MVVM

1.MVVM

MVVM是Model-View-ViewModel的简写,它是MVC的改进版。其中ViewModel将视图UI和业务逻辑分开,它可以取出Model的数据同时帮忙处理View中由于需要展示内容而涉及的业务逻辑。

MVVM采用DataBinding双向数据绑定,View中数据变化将自动反映到ViewModel上;反之,Model中数据变化也会自动展示在页面上。把Model和View关联起来的就是ViewModel。ViewModel负责把Model的数据同步到View显示出来,还负责把View的修改同步回Model。

MVVM核心思想是关注Model的变化,让MVVM框架利用自己的机制自动更新DOM,也就是所谓的数据-视图分离,数据不会影响视图。

MVVM的本质是数据驱动,把解耦做的更彻底,viewModel不持有view 。

9ae39e0e2ac94625ae6a65c45020f7ba.webp

View层:界面层,对应于Activity和XML,负责数据显示及用户交互。相比MVP的view,这里面的 view视图数据一般是在xml中使用DataBinding进来双向绑定数据的。View层用于监听UI事件和生命周期,通知到ViewModel;由ViewModel通知数据更新、刷新UI展示。

ViewModel层:关联层,作为中间桥梁去通知model数据层处理数据业务,并将结果回调给UI层处理UI逻辑。ViewModel层只负责业务处理,这一点跟MVP的P层功能相同;它不持有View层的引用,这一点跟MVP的P层不相同,MVP的P层会通过View层暴露的接口间接地“持有”对UI控件的引用,而ViewModel层则是完全不会引用到View层的UI控件。

Model层:数据层,包含数据实体和对数据实体的操作,和MVP的Model没有区别。所有的数据处理都在这一层完成,然后统一暴露给ViewModel使用,例如触发LiveData。

5c1b2fa4d3ce4706a19b4449c16f8977.webp

MVVM的调用关系和MVP一样。但是,在ViewModel中会有一个叫Binder,或者是Data-binding engine的东西。以前全部由Presenter负责的View和Model之间数据同步操作交由给Binder处理。开发者只需要在View的模版语法当中,指令式地声明View上的显示的内容是和Model的哪一块数据绑定的。当ViewModel对Model进行更新的时候,Binder会自动把数据更新到View上去,当用户对View进行操作(例如表单输入)时,Binder也会自动把数据更新到Model上去。这种方式就是双向数据绑定。

2.MVVM和MVP

MVP和MVVM的区别:ViewModel与View绑定后,ViewModel与View其中一方的数据更新都能立即通知到对方,而Presenter需要通过接口去通知View进行更新。

也就是说,MVVM把View和Model的同步逻辑自动化了。MVP中Presenter负责的View和Model同步不再手动地进行操作,而是交由框架所提供的Binder进行负责。只需要告诉Binder,View显示的数据对应的是Model哪一部分即可。

MVVM的优点:

低耦合。相比于MVP中Presente与View存在耦合,ViewModel与View使用双向绑定机制,耦合更低。

ViewModel里只包含数据和业务逻辑,没有UI的东西,方便单元测试。ViewModel只负责处理和提供数据,UI的改变(比如TextView替换EditText)ViewModel几乎不需要更改任何代码,只专注于数据处理就可以了。

简化测试。因为同步逻辑是交由Binder做的,View跟着Model同时变更,所以只需要保证Model的正确性,View就正确,大大减少了对View同步更新的测试。

MVVM的缺点:

数据绑定使得程序较难调试,界面出现异常时,有可能是View的代码有问题,也可能是Model的代码有问题。由于数据绑定使得数据能够快速传递到其他为止,因此要debug定位出异常就比较有难度了。

过于简单的图形界面不适用,或说牛刀杀鸡。

对于大型的图形应用程序,视图状态较多,ViewModel的构建和维护的成本都会比较高。

3.MVVM实例

①model层

实体类:

public class Account extends BaseObservable {

    private String name;

    private int level;

    public String getName() {

        return name;

    }

    public void setName(String name) {

        this.name = name;

    }

    @Bindable

    public int getLevel() {

        return level;

    }

    public void setLevel(int level) {

        this.level = level;

        notifyPropertyChanged(BR.level);

    }

}

模拟从网络获取账号数据:

public class MyModel {

    public void getAccountData(String accountName, MCallback callback) {

        Random random = new Random();

        boolean isSuccess = random.nextBoolean();

        if (isSuccess) {

            Account account = new Account();

            account.setName(accountName);

            account.setLevel(100);

            callback.onSuccess(account);

        } else {

            callback.onFailed();

        }

    }

}

回调接口:

public interface MCallBack {

    void onSuccess(Account account);

    void onFail();

}

②View层

布局文件:

<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable

            name="viewModel"

            type="com.test.demo.MyViewModel" />

    </data>

    <LinearLayout

        android:layout_width="match_parent"

        android:layout_height="match_parent"

        android:orientation="vertical">

        <!-- @= :数据和视图双向绑定 -->

        <EditText

            android:id="@+id/ed_account"

            android:layout_width="match_parent"

            android:layout_height="wrap_content"

            android:layout_marginTop="50dp"

            android:text="@={viewModel.userInput}"

            android:hint="请输入要查询的账号" />

        <Button

            android:id="@+id/btn_get_account"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:layout_gravity="center_horizontal"

            android:layout_marginTop="80dp"

            android:onClick="@{viewModel.getData}" //在view层通过ViewModel层的getData方法获取到要显示的信息(ViewModel层的getData方法实际上调用的是Model层的方法)

            android:text="获取账号信息" />

        <TextView

            android:id="@+id/tv_result"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:layout_gravity="center_horizontal"

            android:layout_marginTop="80dp"

            android:text="@{viewModel.result}" //在view层获取ViewModel层的result,显示到界面上

            android:hint="账号信息暂未获取" />

    </LinearLayout>

</layout>

Activity文件:

public class MyActivity extends Activity {

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        ActivityMvvmBinding mvvmBinding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm);

        MyViewModel myViewModel = new MyViewModel(getApplication(), mvvmBinding);

        mvvmBinding.setViewModel(myViewModel);

    }

}

③ViewModel层

public class MyViewModel extends BaseObservable {

    private MyModel mvvmModel;

    private ActivityMvvmBinding mvvmBinding;

    private String result;

    private String userInput;

    //一般需要传入Application对象,方便在ViewModel中使用Application,比如sharedPreferences需要使用

    public MyViewModel(Application application) {

        mvvmModel = new MyModel();

    }

    public MyViewModel(Application application, ActivityMvvmBinding binding) {

        mvvmModel = new MyModel();

        this.mvvmBinding = binding;

    }

    public void getData(View view) {

        //使用了DataBinding双向绑定,不需要手动获取输入框的数据,故注释掉

// String userInput = mvvmBindingl.edAccount.getText().toString();

        myModel.getAccountData(userInput, new MCallback() { //获取信息的实际操作在Model层

            @Override

            public void onSuccess(Account account) {

                String info = account.getName() + "|" + account.getLevel();

                setResult(info); //通过接口回调拿到获取的结果

            }

            @Override

            public void onFailed() {

                setResult("获取数据失败");//通过接口回调拿到获取的结果

            }

        });

    }

    @Bindable

    public String getResult() {

        return result;

    }

    public void setResult(String result) {

        this.result = result;

        notifyPropertyChanged(BR.result);

    }

    @Bindable

    public String getUserInput() {

        return userInput;

    }

    public void setUserInput(String userInput) {

        this.userInput = userInput;

        notifyPropertyChanged(BR.userInput);

    }

}

猜你喜欢

转载自blog.csdn.net/zenmela2011/article/details/129695198