DEVLOG 11.7 手寫(xiě)實(shí)現(xiàn)ButterKnife View和OnClick綁定
參考內(nèi)容:
(1)Android注解及反射實(shí)戰(zhàn)--手寫(xiě)B(tài)utterKnife?https://juejin.cn/post/6844904082742722567
(2)Android編譯時(shí)技術(shù)的實(shí)戰(zhàn) 打造全自動(dòng)注入框架ButterKnife
https://www.bilibili.com/video/BV1sK411K7Y3

內(nèi)容主要分為以下幾個(gè)部分:
使用APT實(shí)現(xiàn)ButterKnife View綁定
Kotlin中的Class ::java.class .class VS Java Class 以及KClass Class
使用反射實(shí)現(xiàn)點(diǎn)擊事件綁定
首先講一下手寫(xiě)B(tài)utterKnife進(jìn)行View綁定的原理。這里我們?cè)趯?shí)現(xiàn)過(guò)程中主要使用了Android或者是說(shuō)Java中提供的APT,也就是注解處理器。我們使用注解處理器的步驟是為了簡(jiǎn)化我們的代碼工作量,注解處理器通過(guò)在編譯階段掃描我們的注解生成代碼,從而簡(jiǎn)化工作量。
他的核心思路可以分成以下幾步:
寫(xiě)出對(duì)應(yīng)的注解(Annotation)
實(shí)現(xiàn)注解處理器,注解處理器需要對(duì)于不同的注解進(jìn)行分類(lèi)
根據(jù)【模板】實(shí)現(xiàn)代碼生成

Part 1 實(shí)現(xiàn)View綁定
下面簡(jiǎn)要的總結(jié)一下代碼:
Step1 定義注解:
注解處理機(jī)制其實(shí)暗含了了一種元編程的思想,一種使用不是人寫(xiě)代碼讓計(jì)算機(jī)執(zhí)行代碼,而是計(jì)算機(jī)寫(xiě)代碼讓自身執(zhí)行。不過(guò)我們還是先看看注解的定義:
這兩個(gè)注解都是我們常用的在ButterKnife中的注解。他們?cè)贏ctivity中的使用是這樣的:
可以看到,第一個(gè)關(guān)于View的綁定和我們使用的ButterKnife庫(kù)沒(méi)有什么不同,但是第二個(gè)OnClick綁定有點(diǎn)細(xì)微的區(qū)別。在這里必須要介紹一下我的項(xiàng)目結(jié)構(gòu):

因?yàn)閍nnotation-compiler需要使用javax包中的AbstractProcessor,那么這個(gè)module只能指定為java-lib,然后annotations中定義了BindView和OnClick兩個(gè)注解,其實(shí)可以在OnClick中直接指定傳入的listener的對(duì)象是View.OnClickListener,就像這樣:
但是這樣需要指定annotation module是Android library,不然無(wú)法使用Android SDK提供的類(lèi)。但是我這樣寫(xiě)了之后,發(fā)現(xiàn)annotation-compiler不能引用這個(gè)lib,非常奇怪,但是我沒(méi)有仔細(xì)debug,所以就偷懶使用了通過(guò)參數(shù)指定類(lèi)型的方式。
Step2: 使用注解處理器
實(shí)現(xiàn)一個(gè)注解處理器需要實(shí)現(xiàn)AbstractProcessor類(lèi):
我在這里沒(méi)有在AnnotationCompiler中使用@AutoService注解,因?yàn)檫@個(gè)注解對(duì)于gradle版本要求比較嚴(yán)格,我現(xiàn)在都是用Gradle 7,所以我是用了SPI技術(shù),通過(guò)在resources中指定了當(dāng)前的注解處理器是我寫(xiě)的這個(gè)類(lèi)來(lái)達(dá)到同樣的效果。SPI技術(shù)具體的使用,我還得研究一下后續(xù)再更新。
AnnotationCompiler的實(shí)現(xiàn)需要這幾個(gè)函數(shù)重載:
真正的代碼實(shí)現(xiàn)的工作在process函數(shù)中。因?yàn)槭褂昧薆indView的類(lèi),每一個(gè)類(lèi)都需要生成一個(gè)單獨(dú)的文件,具體的格式和命名可以參考這個(gè):
具體的步驟分成以下幾步:
找到使用了對(duì)應(yīng)處理的注解的類(lèi)
生成集合Map<String, List<Element>> 使用類(lèi)名為Key,一個(gè)類(lèi)中可以使用了多個(gè)注解,所以使用List<Element>
生成具體的代碼,這一塊的邏輯還是相當(dāng)清楚的,具體的代碼如下:

Part 2?Kotlin ::class.java, ::class
這里首先簡(jiǎn)單說(shuō)一下結(jié)論 ::class 會(huì)返回Kotlin中特有的KClass,::class.java就是從KClass中獲取Java中的Class,下面涉及到具體的區(qū)別會(huì)在詳細(xì)說(shuō)。另外下面的Api主要還是使用Java中的反射API。

PART 3 實(shí)現(xiàn)OnClick綁定
這塊的思路和我們使用setOnClickListener時(shí)傳入當(dāng)前類(lèi),并且將當(dāng)前類(lèi)實(shí)現(xiàn)View.OnClickListener的思路差不多,其實(shí)都是將一整個(gè)方法作為回調(diào)的具體實(shí)現(xiàn)。這里和上面的思路不同,我們并不需要對(duì)于每一個(gè)不同類(lèi)都實(shí)現(xiàn)Java代碼,而且說(shuō)實(shí)在的,因?yàn)槲覀兊幕卣{(diào)函數(shù)各種各樣,自己確實(shí)也不好在一個(gè)寫(xiě)死的【模板】中寫(xiě)入代碼。所以我們需要使用動(dòng)態(tài)代理的思路,使用動(dòng)態(tài)代理動(dòng)態(tài)地實(shí)現(xiàn)一個(gè)OnClickListener,然后再綁定到View上。具體的思路如下:
找到使用了@OnClick的方法,因?yàn)榉椒ㄖ袝?huì)傳入具體的View的resId,所以我們可以順勢(shì)把這個(gè)View找到,之后作為綁定之用。
動(dòng)態(tài)代理實(shí)現(xiàn)OnClickListener接口。這里大家稍微復(fù)習(xí)一下動(dòng)態(tài)代理:

可以看到,實(shí)際上動(dòng)態(tài)代理的核心在于invocationHandler,這個(gè)類(lèi)負(fù)責(zé)處理接口中的方法的實(shí)現(xiàn)。在我們的實(shí)現(xiàn)用,因?yàn)槲覀兪褂枚x在Activity中的注解了onClick的方法,所以具體的實(shí)現(xiàn)實(shí)際上被委托給了這個(gè)method對(duì)象,具體的代碼中就是declaredMethod。
然后接口的實(shí)現(xiàn)類(lèi),使用Proxy.newProxyInstance(),這個(gè)方法接受三個(gè)參數(shù):
(1)目標(biāo)接口的classLoader (2)第二個(gè)參數(shù)傳入目標(biāo)接口的Class對(duì)象 (3)invocationHandler,也就是目標(biāo)接口的方法實(shí)現(xiàn)。
其實(shí)動(dòng)態(tài)代理也沒(méi)有任何神秘的地方,他可以理解為是實(shí)現(xiàn)了一個(gè)代理類(lèi),這個(gè)代理類(lèi)繼承了接口,然后將具體的實(shí)現(xiàn)委托給了invocationHandler,正如廖雪峰的博客寫(xiě)的這樣:
所以這塊的具體的代碼是這樣: