我們項目采用的是 kotlin && DataBinding 處理的,可能你會疑問,既然用的是 kotlin,為啥沒有用 kotlinx?新的頁面當然是用的 kotlinx 啦,但我們有相當龐大的歷史代碼,並且我們的通用 adapter 其實也是基於 DataBinding 來封裝的。所以,我們還是不得不來討(吐)論(槽)一下這個 DataBinding 的坑。事實上,這個問題在我當年面試字節跳動的時候就被問及過。
這是一個非常開放性的問題,所以看到這篇文章的小伙伴一定得帶一個有色眼鏡進行審視,下面會盡可能地列舉中筆者遇到過的坑,當然能力有限,有可能不少東西並不是 DataBinding 的問題,需要大家一起來甄別並進行補充。
DataBinding 是早些時候 Google 推出來的一個支持庫,只要勇於解決我們代碼中頻繁出現的 findViewById()
問題,在此之前,我相信大部分人都聽過或者使用過 Butterknife,截止目前,該庫都已經更新到 10.1.0 了,而且作者是 JakeWharton 大神,實為相當好用。
但我們今天不對 Butterknife 進行過多的評論,而轉移到我們的主角 DataBinding。
對於 DataBinding 的使用和介紹這里就不做講述了,建議大家還是直接到 官方文檔
進行查閱學習,今天在這兒,主要和大家分享討論一下在筆者 1 年多的 DataBinding 中踩過的一些坑。我想這些 tips 肯定不是全部,並且有些其實是個人誤操所致,不管怎么說,我們今天就舍棄對它的贊美,吐槽吐槽這個 DataBinding。
極難進行錯誤定位
oh,這應該是使用 DataBinding 的小伙伴們最最最大的坑點了,你甚至一定在各種技術輪胎和開發交流群中見過一些小伙伴對它的深惡痛絕。不管你代碼中有了什么錯誤,你一定收到的錯誤日志是一大堆 DataBinding 生成的類找不到。
可能非常多的小伙伴會對此保持疑問,這不,真正的錯誤都在 build 日志的最后能看到么?實在沒有看到,你也可以在控制台輸入 ./gradlew build --stacktrace
查看到詳細日志。
但事實真的如此么?有沒有小伙伴經歷過不管你怎么查看日志都找不到真正的錯誤代碼的。
我想一定有!雖然我們一般都要求對 commit 保持「少量多次」的原則,但這並不能保證我們時刻都得嚴格遵守,在部分業務非常紊亂的時候,我們並不希望經常做 commit,因為每一次 commit,我們也得做一次編譯確認當前的代碼沒有任何語法問題。而對於代碼量龐大、尤其是還受各種歷史原因組件化不夠徹底的項目,一次編譯基本就可以讓你去吃一個飯了。我司的項目就是如此,受限於歷史原因,組件化做的並不徹底,導致本地編譯在沒有命中緩存的情況下至少得 10 分鍾,這還是在 16G 的 mac pro 上。而且大多數人的電腦可能配置還更低,在編譯的時候很難做其他和工作相關的事情。
為了處理這個編譯問題,我司不得不編寫一個編譯腳本,把編譯操作都在服務器上進行處理,在做了一系列優化操作后,在沒命中緩存的情況下編譯 debug 包還是需要 4.5 分鍾,當然現在在編譯中我們可以做其他有意思的事情了。
好像前面這兩段說了很多無關緊要的東西,但是!我真正想要表達的是,這時候出現了錯誤,並且日志無法對錯誤進行定位,你會發現非常痛苦,你可能已經改動了數十個文件,新建了不少 XML,因為無法定位到日志,你不得不一行一行的進行語法檢查。
我在這個問題上體驗過編碼兩小時,查編譯失敗問題花費時間更多的尷尬情況,通常來說,這樣的錯誤都不容易發現,我深深的記住我有一次因為組件化歷史問題,不得不把一個組件代碼 move 到另外一個組件,然后就發生了不可預料的錯誤的悲痛場景。當你 stash
改動后就可以編譯,但 pop
出來就錯誤的時候,你才會知道什么是手段極其殘忍。
OK,目前對於這樣的情況,還是有所總結,這種情況 95% 是 XML 代碼的問題,你可以直接檢查 XML 了。
代碼極難維護
我們使用 DataBinding 的時候,必然會喜歡它的雙向綁定操作。在 XML 里面直接做一些簡單的邏輯處理,這樣的操作讓我們的代碼變得非常簡潔,並且可以省去 findViewById()
帶來的不必要的性能損耗。但這樣的操作讓后續維護功能代碼的人非常痛苦。
我們的代碼里面就有相當部分的這樣的代碼,部分頁面邏輯非常復雜,比如一個商品詳情頁,會牽扯到非常大量的信息展示和各種促銷秒殺狀態,還有不少的動效,這時候不少邏輯放在 XML 里面后,后續維護的同事在處理產品改動的時候,一定在心中暗自謾罵。
通常來說,我習慣於把這些復雜的邏輯放置在我們的 Java 代碼中。就目前來看,在代碼中查看這些復雜的交互邏輯得心應手不少。
@{} 不會去做檢查
早些時候,我連續犯過兩次低級錯誤,所以對這個問題記憶深刻。我們可以發現,XML 里面支持我們用類似 @{}
這樣的方式去為 TextView
設置顯示內容,但在 XML 里面我們並沒有檢測機制,所以極易出現原本你這個是一個 Number
類型的值,編譯器卻當做一個 resourceId
進行處理而報錯。實際上,在代碼里面設置編譯器是會直接報錯的。
根據控件 ID 在代碼里面找一個東西很難
在我們項目的早期代碼中,XML 里面的命名規范基本是 xxx_xxx_xxx 這樣的格式,但在 DataBinding 里面為我們生成的變量卻采用的是駝峰命名法,這導致我們根據一個控件 id 去對應 class 里面尋找的時候,還得自己更改為駝峰命名法命名的名字,這一度讓我們感到非常不適,所以我們后面的代碼 XML 命名規范就跟着變成駝峰命名法了。這可能和命名規范有些許出入,不過我們堅信適合自己的,才是最好的理念。
部分 XML 中的表達式在不同 gradle 版本上表現有所不同
前面說到,我們平時會采用服務器編譯,所以此前有出現過 XML 文件里面的某個屬性設置在本地編譯不過,但在服務器上甚至其他同事的電腦上可以編譯的問題。老實說,目前我並沒有找到真正的原因,我姑且把這個問題甩鍋給了此前做這個的同事。我后續更改了他的實現方式才讓這個問題得到妥善處理,但至今沒有明白問題出在哪里,因為那樣的方式我認為本身代碼是沒有什么問題的。可惜我現在沒有時間去尋找這個代碼,大概是設置一個公用的 onClickListener
的問題。
BindingAdapter 不好維護
我們通常會用到 @BindingAdapter 方式來做一些公用邏輯,而不是直接去把邏輯放在頁面通過設置屬性來使用它,這樣就會出現這些公用邏輯比較難維護,當然,這極有可能是我們項目的歷史問題,但我覺得這算是一個坑點了。不知道有沒有人出現這樣的屬性在 XML 里面沒有提示的情況。就像你自定義 View Styleable 名字不唯一一樣。
多模塊依賴問題
這個問題我之前還沒有發現,因為我們每個模塊都用到了 DataBinding,所以認為每個模塊的 gradle
都設置上 DataBinding 的配置,並不算什么令人可以吐槽的事,但看起來這個問題挺嚴重的,所以也在這分享給大家。轉自 wanAndroid 上面 xujiafeng 的回答}
DataBinding在多模塊開發的時候,有這樣一個機制:
如果子模塊使用了 DataBinding,那么主模塊也必須在 gradle 加上配置,不然就會報錯;
如果主模塊和子模塊都添加上了 DataBinding 的配置,那么在編譯時,子模塊的 XML 文件產生的 Binding 類除了在自己的 build 里會有一份外,在主模塊下也會有一份。
那么,如果主模塊與子模塊都有一個 layout 根目錄的 activity_main.xml,主模塊生成的 ActivityMainBinding 會是根據子模塊的文件生成的!這種情況我們還可以通過讓主模塊和子模塊使用不同的命名,那么下面這個問題就更要命了:如果子模塊的某個 XML 文件使用了一些第三方的控件,那么主模塊由於也會生成這個文件的 Binding 類,並且其會有第三方控件的引用,這時候由於主模塊沒有引入這些控件,就會報錯,解決辦法是在子模塊應用第三方控件的時候,使用 API 的方式應用,這樣主模塊就堅決引用到了這些第三方控件,這是這樣違背了解耦的原則。
哎,個人能力有限,就想到哪兒說到哪兒了。可能有不少其實並不是 databinding 的坑,是個人使用問題,還往明白的人能直接指出。PS:再給我一個機會,我不想在用 Databinding。
希望有其他槽點或者認為上面的東西是有更好的處理方式的小伙伴一定要在下面留言,盼復盼復~