原創聲明: 該文章為原創文章,未經博主同意嚴禁轉載。
前言: 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<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<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的時候差不多,有區別的地方只有兩點:
- ObservableField替換成LiveData
- binding對象需要調用setLifecycleOwner(LifecycleOwner lifecycleOwner
)設置lifecycleOwner對象。
示例代碼
筆者在GitHub上面建立了一個項目,以后所有的文章的測試DEMO都會上傳到這個項目上,有興趣的讀者可以關注下。
這篇文章的示例在項目中的todoDatabinding文件下。
項目結構如圖所示:
其中databindingcompilerv1為Data Binding Compiler V1下的示例代碼
其中databindingcompilerv2為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。
關於我
微信公眾號:
如果你覺得這片文章對你有所啟發的話,可以關注我的微信公眾號哦