[譯] Data Binding 庫使用的經驗教訓

由 [Unsplash](https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 平臺的用戶 [rawpixel](https://unsplash.com/photos/uQkwbaP0UrI?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 拍攝

Data Binding 庫(下文中以『DB 庫』詞語來指代)提供了一個靈活強大的方式來綁定數據到 UI 界面。可是要用一句陳詞濫調:『能力越大,責任越大』,僅僅是使用數據綁定,並不意味着你能夠避免成爲一個優秀 UI 開發者。html

過去的幾年我一直在 Android 開發中使用 data binding 庫,本文會寫出我這一路上了解到的與它有關的一些內容細節。前端

儘量使用 bindings

自定義 binding adapter 是一種給 View 控件輕鬆提供自定義功能的好方法。和許多開發者同樣,我對 binding adapter 研究得稍微深刻,最終總結出一套包含 15 種不一樣用途的適配器的類集。java

最糟糕的實踐是這類適配器,它們生成格式化的字符串並設置到 TextViews 控件,這些適配器一般僅在同一個佈局文件中使用:android

雖然這可能看起來很聰明,可是有三大缺點:ios

  1. 優化它們的過程太痛苦。除非你把代碼組織得很是好,不然你可能會有一個包含全部適配器方法的大文件,這與代碼內聚和解耦原則相違背。git

  2. 你須要使用 instrumentation 工具來作測試。根據定義,你的 binding adapter 不會有返回值,它們接收一個輸入參數後設置 view 的屬性。這就意味着你必須使用 instrumentation 來測試你的自定義邏輯,這樣會使得測試變得既緩慢又難以維護。github

  3. 自定義 binding adapter 代碼(一般)不是最佳選項。若是你查看內建文本綁定[參考這裏],你將會看到已經作了許多檢查來避免調用 TextView.setText(),這樣就節省了被浪費的佈局檢測。我以爲本身陷入了這樣的思惟困境:DB 庫將會自動優化個人 view 更新。它確實能夠作到,但僅限於你使用被謹慎優化的內建 binding adapter的狀況。後端

相反的,把你的方法的邏輯抽象爲內聚類(我稱之爲文本建立者類),而後將它們傳遞給 binding。這樣你就能夠調用你的文本建立者類並使用內建 view binding:app

這樣咱們能夠從內建的綁定操做過程當中提升效率,而且咱們能夠很是輕鬆地對建立格式化字符串的代碼進行單元測試。less

讓你的自定義 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 的重度使用者,還有 EpoxyData Binding,意思就是在 DiffUtil 中會額外有一些變化相關的計算髮生。因此若是你的 UI 也有大量的 RecyclerView 組成,你能夠相似上文描述不費事地獲取計算這方面的優化。

小步迭代

但願這篇文章強調了一些能夠優化數據綁定實現方案中的一些小事。瞭解『DB 庫』的內部機制能夠幫助你提升數據綁定效率,並提升你的 UI 性能。

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索