前言
不得不說ButterKnife是一個很有學習價值的項目翁狐。我從學習源碼类溢,修改bug后,最后pull request露懒,學到了很多東西闯冷。如果你對Butterknife 源碼還不了解,建議先看一下這篇文章懈词。本文章不介紹基礎的源碼流程蛇耀,主要是深入一部分代碼,分享一些我在調(diào)試bug坎弯,修改bug的經(jīng)驗纺涤。
與其拿著一個黑盒子看著表面译暂,不停得猜測里面到底哪里出了問題,不如打開盒子看一下洒琢,看懂它的邏輯秧秉,比在外面猜要容易的多。
找個bug去練手
在前不久學習完了ButterKnife 源碼后衰抑,總有一種紙上得來終覺淺象迎,絕知此事要躬行的感覺,于是我打算去issue中找一些bug改改呛踊,順便看看自己是否完全了解了這個源碼砾淌。最后找了一個多人反映的問題Missing resource ID for OnClick annotation,就開啟源碼谭网,進行bug的定位汪厨。
簡單描述一個問題,當子module使用kotlin時愉择,對于相同的資源劫乱,如果在事件注解(例如:@OnClick)之前不使用其他注解(例如:@bindview),就會出現(xiàn)崩潰锥涕。
Bug 定位
調(diào)試后發(fā)現(xiàn)衷戈,在生成view_binding.java之后,Utils.findRequiredView() 使用原始整數(shù)而不是 R 引用层坠,發(fā)現(xiàn)這個整數(shù)在apk中R文件殖妇,沒有對應的參數(shù)值。也就是說沒有這個資源破花。
正常情況:
ButterKnife生成代碼如下谦趣,可以看到上面都是使用的R引用,所以不會出現(xiàn)資源找不到的問題
異常情況
注釋掉@BindView的代碼
ButterKnife生成代碼如下座每,這里直接使用了數(shù)字前鹅,但是這個數(shù)字,在R文件中沒有對應的值
問題來了:
1尺栖、這個整數(shù)值是哪里來的嫡纠,為什么沒有與代碼中的資源對應起來?
2延赌、為什么@OnClick之前使用了 @OnBindView 就正常呢?
3叉橱、是butterknife 生成代碼的時候出問題挫以?
4、是傳遞給butterknife的時候窃祝,就已經(jīng)是數(shù)字了掐松?
Bug 分析思路
調(diào)試后發(fā)現(xiàn)這個整數(shù)值是子module R文件里的值,但是在打包資源合并后,這個值發(fā)生了變化
打包合并資源時大磺,R文件的靜態(tài)值會被更改抡句。 所以導致 Missing resource ID 。
下圖是在反編譯apk杠愧,觀察其中的R文件和R2 文件待榔。(R2是對R的復制,把R中的靜態(tài)變量流济,更改為R2中靜態(tài)常量锐锣,變量名和值不變)
思考過程:
靜態(tài)常量,編譯期常量绳瘟,編譯時就確定值雕憔。常量值存儲在JVM內(nèi)存中的常量區(qū)中,在類不加載時即可訪問糖声。
靜態(tài)變量斤彼,需要類加載后,才能確定具體的值蘸泻。
難道是因為R2的引入琉苇,導致的?但是仔細一想蟋恬,如果是編譯期進行靜態(tài)常量值替換翁潘,那么為什么 不注釋掉@BindView的代碼,R的引用值就沒有被替換呢歼争?所以我把焦點轉(zhuǎn)移到了對View_Binding 文件生成的過程拜马。
因為ButterKnife是在編譯期根據(jù)注解生成代碼的,所以需要在編譯期調(diào)試代碼沐绒。
如果是Java俩莽,使用注解處理AnnotationProcessor,就使用遠程調(diào)試乔遮,很容易搞定扮超。但是項目是Kotlin,使用注解處理 Kapt蹋肮,和Java的遠程調(diào)試不太一樣出刷,當時搞了好久斷點沒作用。
后來決定把這個子module改為Java坯辩,先把問題找到馁龟,解決了。kapt的調(diào)試放后邊處理漆魔。結(jié)果發(fā)現(xiàn)使用Java沒有任何問題坷檩,就是Kotlin有問題却音,沒辦法,各種嘗試矢炼,功夫不負有心人系瓢,終于找到了如何調(diào)試kapt,可查看我的這篇文章 Java AnnotationProcessor 和 Kotlin Kapt 編譯期調(diào)試代碼——實踐與原理
于是開始了愉快的調(diào)試句灌,發(fā)現(xiàn)在生成Id(Butterknife 中的Id類)就已經(jīng)使用了整數(shù)
那么就會導致夷陋,在生成方法的時候,使用了數(shù)字涯塔,而不是R引用肌稻。我們希望的情況是下圖的code 是個R.引用。
為什么@OnClick之前使用了 @OnBindView 就正常呢匕荸?
因為在處理@OnBindView 傳入的是R引用爹谭,所以在生成代碼的時候,上圖的code是個R 引用榛搔。
同一個資源id(例子中的textview2)诺凡,會使用相同的Id(Butterknife 中的Id類),所以@OnClick之前使用了 @OnBindView是正常的践惑。
于是我想如果能讓上圖的code的值腹泌,不是數(shù)字,而是R引用尔觉,那么就可以解決這個問題了凉袱。于是一路往上找,看看哪里讓他發(fā)生了變化侦铜。
方法getTree 专甩,返回的JCTree 就已經(jīng)是整數(shù)了,那么跟進去getTree钉稍,最后是在一個map中獲取對應的值涤躲,那我們就繼續(xù)跟一下,這個map是怎么傳值進去的贡未。斷點打好种樱,在來一次
原來傳進來的時候就是數(shù)字
后來發(fā)現(xiàn)網(wǎng)上也有人遇到這個問題,詳見
在跟下去就是kapt的代碼了俊卤,能力有限嫩挤,跟不下去了,去網(wǎng)上查了一下kapt消恍。因為這是在注解處理器的過程俐镐,難道是kapt的原因?kapt是怎么處理kotlin文件的呢哺哼?
kapt 生成的stub中的java文件佩抹,已經(jīng)把靜態(tài)常量進行了替換。(關(guān)于kapt取董,推薦一篇文章棍苹,文章中提到的stub和我真是看到的不太一樣,可能因為kapt已經(jīng)更新了吧)
能力有限茵汰,我修改不了kapt 枢里,所以只能想著怎么在注解處理過程,讓這個整數(shù)值替換為對應的R引用
Bug的解決思路
總思路:在生成Id(Butterknife 中的Id類)類的時候蹂午,通過遍歷R引用栏豺,把整數(shù)替換為對應的R引用,
怎么實現(xiàn)呢豆胸?
1奥洼、剛開始想到通過反射來修改class文件,代碼都寫好了晚胡,每次調(diào)試都發(fā)現(xiàn)灵奖,找不到class。仔細一想估盘,這個階段還處在編譯期瓷患,還沒有生成class文件,所以這個方法行不通
2遣妥、效仿butterknife 使用語法數(shù)JCTree的方式擅编,可不可以獲取到R文件的語法樹JCTree呢?
根據(jù)env中的獲取到R 文件的Element 箫踩,就獲取到JCTree爱态,于是問題迎刃而解
代碼編譯過程中,不同階段使用不同的方式修改編譯期代碼
最后對之前學到一些知識進行歸納總結(jié)
在字節(jié)碼生成之前
1班套、JCTree 獲取一些源碼信息
2肢藐、使用JavaPot生成代碼類
在字節(jié)碼生成之后
1、可通過反射修改代碼
2吱韭、通過ASM吆豹、Javassit修改class 代碼(實際應用:Android字節(jié)碼插樁——詳細講解 附帶Demo)
圖片來自