Android开发利器之Data Binding Compiler V2 —— 搭建Android MVVM完全体的基础

时间:2021-08-03 17:42:59

Android开发利器之Data Binding Compiler V2 —— 搭建Android MVVM完全体的基础

原创声明: 该文章为原创文章,未经博主同意严禁转载。

前言: Android常用的架构有:MVC、MVP、MVVM,而MVVM是唯一一个官方提供支持组件的架构,我们可以通过Android lifecycle系列组件、DataBinding或者通过组合两者的形式来打造一个强大的MVVM架构。而DataBinding Compiler V2就是为了解决目前的MVVM架构中的缺点而诞生的。

Data Binding和LiveData的兼容问题

在DataBinding Compiler V1的环境下,DataBinding和LiveData是无法兼容的。这句话是什么意思呢?我们先来看看平时我们使用DataBinding的代码片段。

Data Binding

布局代码片段

<data>
<variable
name= "text"
type="android.databinding.ObservableField&lt;String>"/>
</data> <TextView
android:layoutwidth="matchparent"
android:layoutheight="40dp"
android:text=“@{text}“
/>

注:xml不能直接使用‘<’所以我们需要使用转义符:"<"

使用代码片段

XXXBinding binding = ...
private final ObservableField<String> text = new ObservableField<>();
binding.setText(text)
text.set(" hello word ")`

上面的代码片段是DataBinding的简单使用方法。

LiveData

我们知道LiveData是Google官方推出的生命周期感知的数据包装组件,用来搭建MVVM框架有天然的优势,能很好协调控制层与展示层生命周期不一致的问题(这里是指View层与ViewModel层)下面我们来看下使用LiveData更新UI的代码片段。

ViewModel代码片段

public class TestModel extends ViewModel {
private final MutableLiveData<String> text = new MutableLiveData<>(); public LiveData<String> getText() {
return text;
}
}

View层代码片段

viewModel. getText().observe(this, observe -> {
tvText.setText(observe);
});

当我们在ViewModel中调用 text.postValue(obj)方法时,UI层的observe方法就会收到回调,通过tvText.setText(observe);这句代码来更新tvText。

例如,我们可以在ViewModel中通过下面的代码来更新UI层

text.posValue("hello word !")

可以看出,无论是使用DataBinding还是LiveData,都能实现View层和ViewModel层解耦的目的,并且能ViewModel层中的数据变化来实现View层的更新,这就是我们常说数据驱动视图

数据驱动视图:只要数据变化, 就重新渲染视图

ObservableField与LiveData

我们知道DataBinding是通过ObservableField来实现数据的双向绑定的,而ObservableField本质上就是一个被观察者,而我们的xml布局文件和就是观察者,当ObservableField产生变化是会通知我们的布局文件更新布局(观察者模式)。

ObservableField如何实现通知布局文件更新的原理我们这里先不深入讨论,这里笔者只给出一个结论,ObservableField被View层(这里指我们的xml布局文件)以弱引用的方式引用,当ObservableField更新时,会通过监听器通知View层,并且ObservableField是对View层生命周期不敏感的。所以通过ObservableField实现数据双向绑定并不是一个完美的方案。

我们可以考虑使用LiveData来实现双向绑定。

我们先来回顾一下监听LiveData方法:

viewModel. getText().observe(this, observe -> {
tvText.setText(observe);
});

非常简单,只在调用LiveData的observe,设置一个Observer

回调监听器就可以了。

那么上文提到的Databinding与LiveData不兼容是指什么呢?

从上面的分析我们可以看出ObservableField与LiveData的使用方式完全是完全不一样的,ObservableField可以通过直接在布局文件中设置实现双向绑定。而LiveData必须通过代码设置监听器,并且需要手动调用待更新的控件才能实现控件的更新。就是说LiveData只能通知UI层有数据需要更新,更新后的数据是什么,但是并不能自动帮你实现View的更新。并且当View层的数据更新后,LiveData也没办法自动获取View层的更新。

例如:在使用EditText的时候,要获取EditText的改变,需要调用EditText的getText方法,而ObservableField只需要调用get()方法即可

LiveData在Data Binding Compiler V1下是无法使用类似ObservableField的方式实现数据绑定的(单向也不行),这就是笔者所说的DataBinding与LiveData不兼容。

当我们使用DataBinding与Lifecycle组合搭建MVVM框架的时候,需要根据业务的具体需要来选择使用LiveData还是ObservableField。类似下面的代码:

public final ObservableBoolean dataLoading = new ObservableBoolean(false);  

private final MutableLiveData<Void> mTaskUpdated = new MutableLiveData <>();

但是实际开发的时候,我们往往无法在ObservableField与LiveData中作出很好的选择,因为它们的优缺点都太明显了。

我们总结一下ObservableField与LiveData的优缺点。

** ObservableField**

优点:使用方便,能快速实现双向绑定

缺点:使用弱引用的方式与View层,并且不能根据View层的生命周期来发送通知

LiveData

优点:能根据View层的生命周期来发送通知事件

缺点:使用麻烦,与View层耦合大,并且不支持数据与View绑定

Data Binding Compiler V2

我们要说的主角就是,Data Binding Compiler V2 。

什么是Data Binding Compiler呢?

Data Binding Compiler是Data Binding的编译器,它的主要作用就是编译出我们在使用Data Binding时需要使用的辅助代码。例如:ActivityxxxBinding格式的类文件就是由Data Binding Compiler编译生成的,并且ObservableField数据双向绑定也是由编译器编译的代码提供支持的。

Data Binding Compiler V2是Data Binding的第二代编译器,这个编译器和V1编译器最大的不同就是:V1编译器只支持ObservableField系列的数据包装类与View层的双向绑定,而V2编译器能让LiveData支持Data Binding双向绑定。

我们可以看看在V2编译器环境下LiveData实现双向绑定的代码片段:

布局代码片段

<data>
<variable
name="text"
type="android.arch.lifecycle.LiveData&lt;String>"/>
</data> <TextView
android:layoutwidth="matchparent"
android:layoutheight="40dp"
android:text=“@{text}“
/>

使用代码片段

XXXBinding binding = ...
binding.setLifecycleOwner(this);
MutableLiveData<String> text = new MutableLiveData<>();
binding.setText(text);
text.postValue(" hello word ");

可以看出,在Data Binding Compiler V2 环境下,使用LiveData实现双向绑定的方法和使用Observable实现双向绑定的方法基本山是一样的。通过Data Binding Compiler V2我们能把LiveData不能实现双向绑定和使用麻烦的缺点彻底解决,并且还能保留LiveData能感知View层生命周期的优点保留下来。

如何使用Data Binding Compiler V2?

环境配置

要使用Data Binding Compiler V2 的话,可能需要升级一下开发环境,需要的配置如下。

  • Android Studio 版本需要升级到3.1 Canary 6以上
  • gradle版本需要升级到 alpha06以上
  • gradle-wrapper.properties中的distributionUrl需要改成gradle-4.4
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
  • 需要在gradle.properties文件中启用databinding V2
android.databinding.enableV2=true

当我们配置完后,重新clear一下项目就可以开启Data Binding Compiler V2了。

使用方法

我们以一个模拟登陆的例子来简单介绍如何使用Data Binding Compiler V2。

数据类

public class Account {
private MutableLiveData<String> accountNum = new MutableLiveData<>();
private MutableLiveData<String> password = new MutableLiveData<>(); Account(String accountNum, String password){
this.accountNum.setValue(accountNum);
this.password.setValue(password);
} public MutableLiveData<String> getAccountNum(){
return accountNum;
} public MutableLiveData<String> getPassword(){
return password;
} }

xml布局文件

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"> <data>
<variable
name="viewModel"
type="tang.com.databindingcompilerv2.login.LoginViewModel"/> <import type="android.view.View"/>
</data> <android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"> <android.support.design.widget.TextInputLayout
android:id="@+id/til_account_num"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> <android.support.design.widget.TextInputEditText
android:id="@+id/et_account_num"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={viewModel.account.accountNum}"
android:hint="@string/account_prompt"/> </android.support.design.widget.TextInputLayout> <android.support.design.widget.TextInputLayout
android:id="@+id/til_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/til_account_num"> <android.support.design.widget.TextInputEditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textWebPassword"
android:text="@={viewModel.account.password}"
android:hint="@string/password_prompt" /> </android.support.design.widget.TextInputLayout> <android.support.v7.widget.AppCompatButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:text="@string/login"
android:onClick="@{viewModel.login}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" /> <ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:isVisible="@{viewModel.isLoading}"
/> <TextView
android:id="@+id/tv_prompt"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:text="@{viewModel.loginPrompt}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/til_password" /> </android.support.constraint.ConstraintLayout> </layout>

ViewModel

public class LoginViewModel extends ViewModel {  

	private static final String TAG = "LoginViewModel";  

	private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>();
private final MutableLiveData<Account> account = new MutableLiveData<>();
private final MutableLiveData<String> loginPrompt = new MutableLiveData<>(); public LoginViewModel(){
account.postValue(new Account("",""));
isLoading.postValue(false);
} public void login(View view){
String loginMsg = "accountNum = " + Objects.requireNonNull(account.getValue()).getAccountNum().getValue()
+ "\npassword = " + Objects.requireNonNull(account.getValue()).getPassword().getValue();
Log.d(TAG,"\n正在登陆中....\n"
+ loginMsg);
loginPrompt.postValue("正在登陆账号:" + Objects.requireNonNull(account.getValue()).getAccountNum().getValue());
isLoading.postValue(true);
new Handler().postDelayed(() -> {
Log.d(TAG,"登陆成功....\n");
isLoading.postValue(false);
Intent intent = new Intent(view.getContext(), MainActivity.class);
intent.putExtra("hello", loginMsg);
view.getContext().startActivity(intent);
loginPrompt.postValue("");
}, 2000); } public MutableLiveData<Boolean> getIsLoading(){
return isLoading;
} public Account getAccount(){
return account.getValue();
} public MutableLiveData<String> getLoginPrompt() {
return loginPrompt;
}
}

Activity

public class MainActivity extends AppCompatActivity {  

	@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.setHello(getIntent().getStringExtra("hello") + "\n hello word !");
binding.setLifecycleOwner(this); }
}

到这里,我们就能愉快地Data Binding Compiler V2了。

从测试代码可以看出,代码和我们使用Data Binding Compiler V1的时候差不多,有区别的地方只有两点:

  1. ObservableField替换成LiveData
  2. binding对象需要调用setLifecycleOwner(LifecycleOwner lifecycleOwner

    )设置lifecycleOwner对象。

示例代码

笔者在GitHub上面建立了一个项目,以后所有的文章的测试DEMO都会上传到这个项目上,有兴趣的读者可以关注下。

这篇文章的示例在项目中的todoDatabinding文件下。

项目结构如图所示:

Android开发利器之Data Binding Compiler V2 —— 搭建Android MVVM完全体的基础

其中databindingcompilerv1为Data Binding Compiler V1下的示例代码

其中databindingcompilerv2为Data Binding Compiler V2下的示例代码

Data Binding Compiler V2 示例代码

小结

Data Binding Compiler V2主要是解决了Data Binding不能感知View层生命周期的问题。

在Android开发中我们的控制层(这里指ViewModel)的生命周期和View层组件的生命周期是不能保持一致的,大多数情况下,控制层的生命周期会比View层长。例如,我们发起网络请求的时候,在请求回调之前View有被销毁的可能,如果在View被销毁后控制层再更新View层,这个时候我们就会遇到讨厌的NPE异常。Lifecycle系列组件的主要功能就是使控制层能够感知View层的生命周期。而Data Binding Compiler V2则是为了使Data Binding能够使用Lifecycle中的LiveData从而获得感知生命周期的能力,即达成Data Binding 的lifecycle-aware。

关于我

GitHub

微信公众号:

如果你觉得这片文章对你有所启发的话,可以关注我的微信公众号哦

Android开发利器之Data Binding Compiler V2 —— 搭建Android MVVM完全体的基础

Android开发利器之Data Binding Compiler V2 —— 搭建Android MVVM完全体的基础的更多相关文章

  1. Android开发利器之ActivityTracker

    版权声明:本文为xing_star原创文章,转载请注明出处! 本文同步自http://javaexception.com/archives/113 Android开发利器之ActivityTracke ...

  2. Android开发教程 - 使用Data Binding(八)使用自定义Interface

    本系列目录 使用Data Binding(一)介绍 使用Data Binding(二)集成与配置 使用Data Binding(三)在Activity中的使用 使用Data Binding(四)在Fr ...

  3. Android开发教程 - 使用Data Binding(六)RecyclerView Adapter中的使用

    本系列目录 使用Data Binding(一)介绍 使用Data Binding(二)集成与配置 使用Data Binding(三)在Activity中的使用 使用Data Binding(四)在Fr ...

  4. Android开发教程 - 使用Data Binding(一) 介绍

    本系列目录 使用Data Binding(一)介绍 使用Data Binding(二)集成与配置 使用Data Binding(三)在Activity中的使用 使用Data Binding(四)在Fr ...

  5. Android开发教程 - 使用Data Binding Android Studio不能正常生成相关类&sol;方法的解决办法

    本系列目录 使用Data Binding(一)介绍 使用Data Binding(二)集成与配置 使用Data Binding(三)在Activity中的使用 使用Data Binding(四)在Fr ...

  6. Android开发教程 - 使用Data Binding(七)使用BindingAdapter简化图片加载

    本系列目录 使用Data Binding(一)介绍 使用Data Binding(二)集成与配置 使用Data Binding(三)在Activity中的使用 使用Data Binding(四)在Fr ...

  7. Android开发教程 - 使用Data Binding(五)数据绑定

    本系列目录 使用Data Binding(一)介绍 使用Data Binding(二)集成与配置 使用Data Binding(三)在Activity中的使用 使用Data Binding(四)在Fr ...

  8. Android开发教程 - 使用Data Binding(四)在Fragment中的使用

    本系列目录 使用Data Binding(一)介绍 使用Data Binding(二)集成与配置 使用Data Binding(三)在Activity中的使用 使用Data Binding(四)在Fr ...

  9. Android开发教程 - 使用Data Binding(三)在Activity中的使用

    本系列目录 使用Data Binding(一)介绍 使用Data Binding(二)集成与配置 使用Data Binding(三)在Activity中的使用 使用Data Binding(四)在Fr ...

随机推荐

  1. &lbrack;C&num;进阶系列&rsqb;专题一:深入解析深拷贝和浅拷贝

    一.前言 这个星期参加了一个面试,面试中问到深浅拷贝的区别,然后我就简单了讲述了它们的之间的区别,然后面试官又继续问,如何实现一个深拷贝呢?当时只回答回答了一种方式,就是使用反射,然后面试官提示还可以 ...

  2. huhamhire-hosts必备神器!

    huhamhire-hosts 不用说你就知道是干嘛用的. 上地址: https://github.com/huhamhire/huhamhire-hosts https://hosts.huhamh ...

  3. solr单机版的搭建

    一.solr单机版的搭建 1.运行环境 solr 需要运行在一个Servlet容器中,Solr4.10.3要求jdk使用1.7以上,Solr默认提供Jetty(ja),本教va写的Servlet容器程 ...

  4. shell 变量说明

    变量说明 $$Shell本身的PID(ProcessID)$!Shell最后运行的后台Process的PID$?最后运行的命令的结束代码(返回值)$-使用Set命令设定的Flag一览$*所有参数列表. ...

  5. &num;图&num; &num;SPFA&num; ----- codevs1021 玛丽卡

    codevs1021 玛丽卡 题目描述 Description麦克找了个新女朋友,玛丽卡对他非常恼火并伺机报复.因为她和他们不住在同一个城市,因此她开始准备她的长途旅行.在这个国家中每两个城市之间最多 ...

  6. &period;Net Reactor 5脱壳教程

    今天别人发来一个.Net的DLL让我脱壳,第一步自然是先扔进de4dot 我这个de4dot 是集成了  Ivancito0z / TheProxy / PC-RET 4.9mod / wuhenso ...

  7. Python标准库映射类型与可散列数据类型的关系

    这里有两个概念似懂非懂,在这里明确一下: 映射类型: Python>3.2中,collections.abc模块有Mapping和MutableMapping两个抽象基类(Python2.6~3 ...

  8. MongoDB--连接客户端和服务

    直接在官网上下载好编译好的二进制文件,安装.这里安装在c盘了 安装图形界面: 打开文件进入C:\Program Files\MongoDB\Server\3.4\bin 在bin目录下: 客服端和服务 ...

  9. tomcat热部署&period;class

    本人是在维护公司系统时遇到的问题,由于公司的系统是部署到客户服务器上,而系统中存在的问题又比较多,需要经常维护.如果每次修改完class文件后都需要去重启服务器, 那会给用户的使用造成不便,所以需要使 ...

  10. Nginx浏览目录配置及美化

    https://segmentfault.com/a/1190000012606305 在项目中有一个功能需要在浏览器页面中浏览服务器的目录.服务器使用Nginx,而Nginx提供了相应的ngx_ht ...