- 原文地址:Data Binding — Lessons Learnt
- 原文作者:Chris Banes
- 譯文出自:掘金翻譯計劃
- 本文永久連結:github.com/xitu/gold-m…
- 譯者:Mirosalva
- 校對者:DevMcryYu
Data Binding 庫(下文中以『DB 庫』詞語來指代)提供了一個靈活強大的方式來繫結資料到 UI 介面。但是要用一句陳詞濫調:『能力越大,責任越大』,僅僅是使用資料繫結,並不意味著你可以避免成為一個優秀 UI 開發者。
過去的幾年我一直在 Android 開發中使用 data binding 庫,本文會寫出我這一路上了解到的與它有關的一些內容細節。
儘可能使用 bindings
自定義 binding adapter 是一種給 View 控制元件輕鬆提供自定義功能的好方法。和許多開發者一樣,我對 binding adapter 研究得稍微深入,最終總結出一套包含 15 種不同用途的介面卡的類集。
最糟糕的實踐是這類介面卡,它們生成格式化的字串並設定到 TextViews
控制元件,這些介面卡通常僅在同一個佈局檔案中使用:
雖然這可能看起來很聰明,但是有三大缺點:
-
優化它們的過程太痛苦。除非你把程式碼組織得非常好,否則你可能會有一個包含所有介面卡方法的大檔案,這與程式碼內聚和解耦原則相違背。
-
你需要使用 instrumentation 工具來做測試。根據定義,你的 binding adapter 不會有返回值,它們接收一個輸入引數後設定 view 的屬性。這就意味著你必須使用 instrumentation 來測試你的自定義邏輯,這樣會使得測試變得既緩慢又難以維護。
-
自定義 binding adapter 程式碼(通常)不是最佳選項。如果你檢視內建文字繫結[參考這裡],你將會看到已經做了許多檢查來避免呼叫
TextView.setText()
,這樣就節省了被浪費的佈局檢測。我覺得自己陷入了這樣的思維困境:DB 庫將會自動優化我的 view 更新。它確實可以做到,但僅限於你使用被謹慎優化的內建 binding adapter的情況。
相反的,把你的方法的邏輯抽象為內聚類(我稱之為文字建立者類),然後將它們傳遞給 binding。這樣你就可以呼叫你的文字建立者類並使用內建 view binding:
這樣我們可以從內建的繫結操作過程中提高效率,並且我們可以非常輕鬆地對建立格式化字串的程式碼進行單元測試。
讓你的自定義 binding 介面卡變得高效
如果你確實需要使用自定義介面卡,因為你所需的功能不存在,請儘量使其變得高效。我的意思是使用所有標準的 Android UI 優化:儘可能避免觸發測量/佈局操作。
這可以像檢查當前使用的檢視以及你設定的內容一樣簡單。這裡有一個我們為 android:drawable
重新實現了標準 ImageView adapter 的樣例:
遺憾的是,檢視並不總是能夠顯示我們需要檢查的狀態。這裡有一個在 TextView 上設定切換最大行的示例。它通過改變 TextView 的 maxLines
屬性以及一個延時佈局轉換(android.view.ViewGroup)來實現切換。
之前 binding adapter 比較簡單並且總是設定了 maxLines
屬性和一個點選監聽物件。TextView 在 setMaxLines()
被呼叫後總會觸發一次佈局,這就意味著每次 binding adapter 啟動,一次佈局就會被觸發。
讓我們改變這個情況。由於此功能與 TextView 是完全分開的(我們只是在單擊時使用不同的值呼叫 setMaxLines()
),我們需要將引用儲存為當前狀態。幸運的是,『DB 庫』為我們提供了一個手工方式去在 binding adapter 中接收狀態。通過提供引數兩次:第一個引數接收當前值,第二個引數接收新值。
所以這裡我們只需比較當前的和新的 collapsedMaxLines
值。如果值實際發生了改變,我們才去呼叫 setMaxLines()
等方法。
編輯按: 感謝 Alexandre Gianquinto 在評論中提到『double parameters』功能。
謹慎對待你提供的變數
我一直在慢慢的重新設計 Tivi,使用類似 MVI 的東西,使用優秀的 MvRx 庫來使它變得規範化。這在實踐中意味著我的 fragment/view 訂閱到 ViewModel物件,並且接收 ViewStates 的例項。這些例項包含所有用於顯示 UI 的必要狀態。
這是一個展示 Tivi(連結)中類的樣例:
你可以看到它僅僅是一個簡單的資料類,包含了 UI 需要在一個 TV 秀介面上顯示的所有細節 UI 元素。
聽起來像是傳遞我們的 data binding 例項物件的完美選項,讓我們的 binding 表示式來去更新 UI,對吧?好吧這確實有效,但是有一些需要注意的地方,這是由於『DB 庫』的工作機制。
在 data binding 中你通過 <variable>
標籤宣告瞭輸入,然後在書寫 binding 表示式時在 view 屬性處引用了這些輸入變數。當任何被依賴的變數發生變化,『DB 庫』都會執行你的 binding 表示式(接著會更新 view)。這個變化檢測就是你可以免費獲取的很棒的優化。
所以回到我的場景,我的佈局最終看起來是這樣的:
所以我最終獲取一個包含所有 UI 狀態的全域性 ViewState 例項,並且你可以想象出這些狀態經常會發生變化。UI 狀態的任何輕微變化都會產生一個全新的 ViewState,並被傳遞到我們的 data binding 例項。
所以問題是什麼?由於我們只有一個輸入變數,所有的 binding 表示式將會引用變數,這就意味著『DB 庫』將無法自由選擇執行哪個表示式。在實際過程中,這意味著每次變數變化(不管多小的變化)發生時所有的 binding 表示式都會執行。
這個問題與 MVI 這點無關,特別是它只是組合狀態的 artifact,與data binding 結合在一起使用。
那麼你能怎麼做呢?
有種替代方法是在佈局中顯式宣告 ViewState 中的每個變數,然後顯式傳遞組合狀態例項中的值,如下所示:
這顯然會使開發人員維護和同步更多的程式碼,但它確實意味著『DB 庫』可以優化去執行哪些表示式。如果你的 UI 狀態不經常變化(可能在建立時有一些次)並且變數數量較少時,我會推薦使用此模式。
我個人一直在佈局中使用單個變數,傳入我的 ViewState 例項,並依賴於我們的檢視繫結合理地執行。這就是為什麼讓檢視繫結變得高效非常重要。
另一個需要注意的是 Tivi 是 RecyclerView 的重度使用者,還有 Epoxy 和 Data Binding,意思就是在 DiffUtil 中會額外有一些變化相關的計算髮生。所以如果你的 UI 也有大量的 RecyclerView 組成,你可以類似上文描述不費事地獲取計算這方面的優化。
小步迭代
希望這篇文章強調了一些可以優化資料繫結實現方案中的一些小事。瞭解『DB 庫』的內部機制可以幫助你提高資料繫結效率,並提高你的 UI 效能。
如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。
掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。