DataBinding是谷歌發佈的jetpack庫裏的重要一員,經過使用DataBinding來實現MVVM架構,能夠有效避免了MVP架構裏新建文件過多的繁雜問題,而且每次數據源更新時再也不須要開發人員來調用控件的set方法更新數據了,同時支持雙向綁定也能讓控件之間互相刷新,能夠減小控件之間的監聽,從而減緩陷入回調地獄的進度。今天筆者給你們分享一下DataBinding從入門基本使用,到深刻源碼的一整個過程。若是你歷來沒用過DataBinding,那麼恭喜你看完就是jetpack大佬了。哈哈開個玩笑樂呵樂呵,jetpack深似海,普通人沒有個三五年的研究是沒辦法精通的,這只不過是滄海一粟而已,不過但願看完能對你們有所幫助。java
步驟一 首先定義好一個普通的JavaBean,這就是控件須要的數據源,不須要作任何的特殊處理。數據庫
步驟二 修改佈局成databinding佈局數組
進入activity的佈局文件,鼠標放到根佈局上,而後同時按住alt+enter打開系統提示框。因爲咱們以前在build.gradle中設置了打開dataBinding,因此當前會出現Convert to data binding layout選項,顧名思義就是轉換成dataBinding支持的佈局模式,選擇該選項便可。安全
選擇好了該模式之後,佈局文件大概就會變成這樣子。佈局最外層給自動加上了layout便籤,包裹了咱們最初的根佈局。其次,系統自動生成了<data></data>和以前的根佈局同級。bash
接下來只須要在data標籤裏面配置好系統自動生成類的類名,以及<variable>屬性便可。其中variable中name有兩個做用,第一是使用該數據源,第二個是用來自動生成更新數據時調用的方法名,type指定好前面咱們寫好的那個bean便可。在控件上使用時只須要經過@{name.field}的方式設置在控件上便可,固然這裏以TextView爲例,ImageView須要特殊處理,後面會講到。架構
通過以上的準備工做,接下來就要開始正式使用了。我這裏打開了一個線程,而後每過一秒變化一下數據源,注意每次修改了數據源,最後都須要調用setTestVariable方法才能生效app
運行效果以下:異步
效果符合預期,每過一秒就自動更新數據,而咱們並無對TextView進行setText操做ide
咱們成功地實現了在數據源變化的時候自動設置到ui上面,也僅僅是實現了功能而已,可是這種方法很是地笨拙,每次數據源更新的時候還要給dataBinding從新設置數據源,操做很麻煩,那有沒有隻須要寫一遍就能夠初始化數據源,從而一勞永逸的辦法呢?答案是有的,接下來,咱們來看看方式2。佈局
xml文件不須要變,按照方式1的寫法就能夠了,只須要在Bean的字段的get方法上面加上@Bindable註解,而後在字段的set方法裏調用一下notifyPropertyChanged方法便可,須要注意的是,必需要繼承BaseObservable纔會有該方法。其中@Bindable是告訴DataBinding在能夠調用getName方法來獲取name的值,notifyPropertyChanged方法是告知DataBinding當前數據已修改,快去調用get方法獲取最新數據吧!因此不用從新設置數據源的緣由你們也都看出來了,其實就是在給Bean設值的時候通知了DataBinding,而後DataBind自動去更新了。
接下來在使用時,就不須要從新給DataBinding設置數據源了
當時這種方式也有缺陷,就是Bean裏面每一個字段的set和get方法都須要進行修改,加上@Bindable和notifyPropertyChanged方法,不然將會自動更新失敗。只要是須要人工修改的地方,那麼在實際業務中不可避免地可能出現問題。那麼有沒有容錯性更強的方式呢,固然是有的,下面說下方式3,也是最經常使用的方式。
修改Bean便可,將屬性包裝成ObservableField,該屬性的類型改爲泛型的形式給出,以下圖
在使用時的方式也須要稍加修改,須要先獲取到ObservableFiled屬性,而後調用其set方法來設置新值。這種方法是否是比前面那2種要好很多呢,既不須要每次賦值的時候給DataBinding賦值,也不須要記得給Bean裏每一個字段的get方法加上@Bindable,以及set方法上手動加上調用notifyPropertyChanged方法,這種方式也是我的以爲最好用的方式。
若是數據源是存放在Map或者List時,DataBinding提供了相應封裝好的類能夠直接使用,Map -> ObservableMap,List -> ObservableList,不過這兩個都是接口,實例化的時候用ObservableArrayMap和ObservableArrayList便可。
咱們來簡單嘗試一下Map進行存值和使用吧,首先在Bean中添加一個數據源爲ObservableMap類型。
這裏還須要相應添加一個key,由於java的字符串是不支持單引號和雙引號一塊兒使用的,在xml中使用時須要。
接下來就是對map進行賦值了,這裏須要注意,map和key須要同時設置,否則在xml中綁定了是拿不到值的。
在使用了DataBinding的xml中可使用簡單運算符操做,好比,咱們能夠用取到的數字源進行操做後再設置到控件上,這裏舉一個例子,咱們在獲取的值後面加一個單位。須要注意的是,在這裏拼接字符串須要使用的並非單引號,而是數字1左邊的那個鍵。
除了上面用到的字符串拼接之外,還支持另一些操做,具體見下圖,注意並非全部的符號都支持。
若是但願某個控件跟用戶輸入的值改變之後,其餘和其使用同一屬性的控件也跟着同步過去,那麼就須要用到雙向綁定,具體作法是將@{}改爲@={}。以下圖,在將EditText的值與數據源雙向綁定了之後,當用戶在輸入框中輸入內容之後,下面的TextView內容依然會發生變化。
若是沒有使用雙向綁定,那麼效果是這樣的,輸入框的修改不會同步到文本框
而雙向綁定之後是這樣的,輸入框無論是刪除仍是輸入,都會把結果同步到文本框上
有些控件的屬性並非直接顯示在控件上的,而是須要通過處理甚至是第三方API的處理才能使用。又或者是想要覆蓋系統自身的屬性,也能夠,總之就是這裏定義的對屬性的BindingAdapter註解處理方式會覆蓋系統的方式。
好比想要將url設置到ImageView上,須要經過Picasso或者Glide來將url下載轉成bitmap來設置,這個時候就須要特殊處理。
首先在Bean裏新增一個String類型的url字段,而後在ImageView上自定義一個url屬性,將其綁定到url字段上。也能夠定義多個,這裏多定義了一個error屬性。
最後處理一下實際的將url設置到ImageView上的操做
以RV爲例,接下來講一下如何將數據設置到列表控件中,這也是很是常見的操做了。因爲列表控件是有Adapter的,至關於設置數據時多傳了一層。
這是一個並無使用DataBinding的簡單RecyclerView示例,裏面有3種不一樣佈局,固然這裏代碼寫得很不規範,由於這個不是重點。
運行之後大體的效果以下,爲了區分三種不一樣的Item,我將每一個Item顯示的TextView個數進行了區分,方便你們看懂這是不一樣的item。
接下來咱們要作讓它綁定到DataBinding,實現當數據源變化時不調用nofigyDataSetChanged方法來實現Item數據發生變化。
使用步驟參照以前基本使用來便可,第一步新建一個Bean做爲DataBinding獲取數據庫的地方。第二步將那3個item都變成layout包裹的DataBinding佈局。
第三步是將當前佈局交給DataBinding進行託管
第四步是須要修改一個ViewHolder的實現,因爲以前是將View託管到了ViewDataBinding中,因此須要View的時候能夠從託管平臺DataBinding來獲取。這裏保存下binding是爲了在onBindViewHolder中綁定數據用,要記住如今須要數據都是從ViewDataBiding中獲取了,而不能直接從values數組裏去獲取,否則數據修改了是刷新不了的。這裏實現一個接口,是爲了偷懶,在onBindViewHolder中統一處理。
最後在onBindViewHolder中調用系統的setVariable方法對DataBinding進行賦值,是否是節省了不少代碼哈哈。
最後,驗證一下數據修改時,會自動刷新到rv上吧,我這裏開了個線程,每秒修改一下rv上的數據源,注意我只是修改了數據源並無notifyDataSetChanged哈。
讓咱們來看一下最終的效果,符合預期。
咱們在Activity中使用DataBinding的時候,須要進行初始化,代碼大體是這麼寫的:
DataBindingUtil.setContentView(this, R.layout.activity_main)複製代碼
咱們點進去看下都作了什麼操做,能夠看到很簡單,首先調用了Activity的setContentView方法,這個是不使用DataBinding的時候也須要調用的,其次是調用了bindToAddedViews方法,調用的時候將最外層的Framlayout容器以及佈局的LayoutId給傳了進去。
在bindToAddedViews方法中,通過一個抽象類的中轉,最終是調用到了DataBinderMapperImpl類,該類的具體代碼以下:
ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
switch(layoutId){
case LAYOUT_AAA:
return new AaaBindingImpl(component, view);
case LAYOUT_BBB:
return new BbbBindindImpl(component, view)
}
}複製代碼
這段代碼十分地簡單,就是初始化了相應的DataBinding類,沒什麼好說的,還有就是保存好了全部的佈局文件id、Variable名、到一個map中,一直findUseage你會發現最終是在DataBindingUtil類中的convertByIdToString方法使用,該方法是暴露給開發人員的public方法,說明是預留給咱們使用的功能,查看方法提示大概是能夠用來和日誌有關係?
首先點進去看下,它是ViewDataBinding該抽象類中的抽象方法。ViewDataBinding類是系統生成的抽象類
接下來咱們看看它的實現類有哪些,這很明顯是每一個DataBinding的佈局文件生成了一個,命名是以佈局文件的首字母大寫而後駝峯的方式,最後拼上了BindingImpl,固然這個名字是能夠自定義的,注意看第二個實現類名不同,這是由於咱們在佈局文件中
<data class = "DataBindingTest">自定義了其名字。
接下來咱們隨便點進去一個類吧,查看一下其setVariable方法
發現是調用了setItem1Variable方法,這個方法固然也是自動生成的,是根據variable的name屬性生成的。咱們能夠看到這裏首先將數據傳給了根據name生成的屬性,這就是爲了保存起來給後面使用的。
接下來看看從調用notifyPropertyChanged()到最終數據通知到View上的整個流程吧,otifyPropertyChanged方法一直點下去會執行到如下代碼:
在這裏mNotifier是在setVariable的時候初始化的,具體貼一個堆棧就一目瞭然了
在這裏callback也是在CallbackRegistry類中添加的,具體也是執行setVariable方法的時候,同時也貼一個堆棧就一目瞭然了。固然這也是最少要執行一次setVariable的緣由之一,若是不執行連回調都沒有初始化固然沒辦法通知。
明白了mNotifier和callback這兩個關鍵對象的初始化時機,接下來再看看具體是怎麼通知的吧。前面看到在notifyPropertyChanged()方法執行時會調用到
mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2);複製代碼
執行這行代碼就至關於執行到了
繼續往下面看
發送了一個handler,那handler接收之後作了什麼操做呢?
handler執行到的方法是mRebindRunnable.run()方法。
接下來咱們看看這個Runnable的具體實現,其定義就在當前類裏,具體以下:
裏面核心的方法是這個executePendingBindings(),而後是走到了executeBindings()方法
executeBindings()是一個抽象方法,接下來咱們看下它的實現,正是系統給咱們生成的BindingImpl類,是否是頗有親切感呢!
接下來就簡單了,咱們看下實現類裏究竟作了什麼呢?
其實就是調用了一下DataBinding裏封裝好的TextView.setText()方法而已,至此就能通了。
最後來總結一下大體流程,爲了讓你們更好地看懂,我這裏手動畫了2個圖,分別是callback註冊流程,以及數據刷新流程
callback註冊流程:
數據刷新流程:
那麼看完有同窗就要問了,爲何不直接更新呢,爲啥要經過Handler繞這麼大個圈子?我我的以爲可能跟實際作起來要考慮的因素不少有關,包括當前線程未知等緣由,下面也列舉一下我找到的相關因素代碼吧,不得不佩服谷歌工程師代碼的健壯性真的是強大。不過要是安卓源碼出問題,那沒人能評估出損失,嚴謹一點固然是好。
部分健壯性處理代碼以下:
1.須要保證當前View已經被加載才能調用
2.須要兼容API<16的狀況
3.須要保證DataBinding已經在UI線程初始化,若是是子線程中則沒有更新UI的能力
4.須要保證異步安全,因此在requestBind()時進行了加鎖
綜合各方面來看,因爲各類限制比較複雜,並且還存在可能須要切換線程的狀況,因此這裏使用了Handler來發送消息的方式。
和BindingImpl類同樣,BR也是系統自動生成的類之一。生成的內容極其簡單,以項目中全部Variable標籤名以及使用到的Bean中字段名來命名生成好了相關靜態屬性,至關因而提供好Id用來方便地更新數據,我懷疑就是以前那個Keys數組生成的,由於值如出一轍。
咱們知道,在雙向綁定的時候,數據會自動刷新控件,同時控件內容有變化,那數據也會同步更新。那麼問題來了,在控件內容變化的時候,刷新數據,刷新完數據控件內容又變化,控件內容變化又刷新數據。。。這樣不是進入了死循環了嗎?若是你真這麼認爲,那你也過小看谷歌工程師了,人家只用了一行代碼便解決了這個問題:
看到沒,在DataBinding的setText方法中作了處理,若是文本內容不變不算數據有更新,不會設置到控件上,至關於這一趟流程白跑了而已。
本次咱們從DataBinding的三種基本使用方法,延伸到一些常見的高級用法。而後從源碼的角度剖析了DataBinding的主要流程,其中每一張源碼圖都是筆者debug一步一步截下來的,保證了流程不會有問題。其次最核心的setVariable()流程總結圖也是筆者縱觀整個流程,一個一個類的畫出來的,筆者寫完已頭昏眼花,不過只要能對你們使用和理解DataBinding有所幫助的話,無論怎麼樣就都值了,犧牲本身成就他人,咱IT工程師不都是實在人麼。關於標題,看不懂本文的就不勸你轉行了,畢竟每行都不容易。不過兼職擺攤確實是不錯,有想去擺攤的同窗歡迎一塊兒探討哈!